module Database.CQL.IO.Cluster.Policies
( Policy (..)
, random
, roundRobin
) where
import Control.Applicative
import Control.Concurrent.STM
import Control.Lens ((^.), view, over, makeLenses)
import Control.Monad
import Data.Map.Strict (Map)
import Data.Word
import Database.CQL.IO.Cluster.Host
import Database.CQL.IO.Types (InetAddr)
import System.Random.MWC
import qualified Data.Map.Strict as Map
data Policy = Policy
{ setup :: [Host] -> [Host] -> IO ()
, onEvent :: HostEvent -> IO ()
, select :: IO (Maybe Host)
, acceptable :: Host -> IO Bool
, hostCount :: IO Word
, display :: IO String
}
type HostMap = TVar Hosts
data Hosts = Hosts
{ _alive :: Map InetAddr Host
, _other :: Map InetAddr Host
} deriving Show
makeLenses ''Hosts
roundRobin :: IO Policy
roundRobin = do
h <- newTVarIO emptyHosts
c <- newTVarIO 0
return $ Policy (defSetup h) (defOnEvent h) (pickHost h c)
defAcceptable (defHostCount h) (defDisplay h)
where
pickHost h c = atomically $ do
m <- view alive <$> readTVar h
if Map.null m then
return Nothing
else do
k <- readTVar c
writeTVar c $ succ k `mod` Map.size m
return . Just . snd $ Map.elemAt (k `mod` Map.size m) m
random :: IO Policy
random = do
h <- newTVarIO emptyHosts
g <- createSystemRandom
return $ Policy (defSetup h) (defOnEvent h) (pickHost h g)
defAcceptable (defHostCount h) (defDisplay h)
where
pickHost h g = do
m <- view alive <$> readTVarIO h
if Map.null m then
return Nothing
else do
let i = uniformR (0, Map.size m 1) g
Just . snd . flip Map.elemAt m <$> i
emptyHosts :: Hosts
emptyHosts = Hosts Map.empty Map.empty
defDisplay :: HostMap -> IO String
defDisplay h = show <$> readTVarIO h
defAcceptable :: Host -> IO Bool
defAcceptable = const $ return True
defSetup :: HostMap -> [Host] -> [Host] -> IO ()
defSetup r a b = do
let ha = Map.fromList $ zip (map (view hostAddr) a) a
let hb = Map.fromList $ zip (map (view hostAddr) b) b
let hosts = Hosts ha hb
atomically $ writeTVar r hosts
defHostCount :: HostMap -> IO Word
defHostCount r = fromIntegral . Map.size . view alive <$> readTVarIO r
defOnEvent :: HostMap -> HostEvent -> IO ()
defOnEvent r (HostNew h) = atomically $ do
m <- readTVar r
when (Nothing == get (h^.hostAddr) m) $
writeTVar r (over alive (Map.insert (h^.hostAddr) h) m)
defOnEvent r (HostGone a) = atomically $ do
m <- readTVar r
if Map.member a (m^.alive) then
writeTVar r (over alive (Map.delete a) m)
else
writeTVar r (over other (Map.delete a) m)
defOnEvent r (HostUp a) = atomically $ do
m <- readTVar r
case get a m of
Nothing -> return ()
Just h -> writeTVar r
$ over alive (Map.insert a h)
. over other (Map.delete a)
$ m
defOnEvent r (HostDown a) = atomically $ do
m <- readTVar r
case get a m of
Nothing -> return ()
Just h -> writeTVar r
$ over other (Map.insert a h)
. over alive (Map.delete a)
$ m
get :: InetAddr -> Hosts -> Maybe Host
get a m = Map.lookup a (m^.alive) <|> Map.lookup a (m^.other)