-- A simple local name service for threads... behaves like the Erlang
-- process registry.

module Spec.DynamicLogic.Registry where

import Control.Concurrent
import Control.Monad
import Data.IORef
import GHC.Conc
import System.IO.Unsafe

alive :: ThreadId -> IO Bool
alive tid = do
  s <- threadStatus tid
  return $ s /= ThreadFinished && s /= ThreadDied

{-# NOINLINE registry #-}
registry :: IORef [(String, ThreadId)]
registry = unsafePerformIO (newIORef [])

whereis :: String -> IO (Maybe ThreadId)
whereis name = do
  reg <- readRegistry
  return $ lookup name reg

register :: String -> ThreadId -> IO ()
register name tid = do
  ok <- alive tid
  reg <- readRegistry
  if ok && name `notElem` map fst reg && tid `notElem` map snd reg
    then atomicModifyIORef registry $ \reg' ->
      if name `notElem` map fst reg' && tid `notElem` map snd reg'
        then ((name, tid) : reg', ())
        else (reg', badarg)
    else badarg

unregister :: String -> IO ()
unregister name = do
  reg <- readRegistry
  if name `elem` map fst reg
    then atomicModifyIORef registry $ \reg' ->
      ( filter ((/= name) . fst) reg'
      , ()
      )
    else badarg

readRegistry :: IO [(String, ThreadId)]
readRegistry = do
  reg <- readIORef registry
  garbage <- filterM (fmap not . alive) (map snd reg)
  atomicModifyIORef' registry $ \reg' ->
    let reg'' = filter ((`notElem` garbage) . snd) reg' in (reg'', reg'')

badarg :: a
badarg = error "bad argument"
