-- This Source Code Form is subject to the terms of the Mozilla Public -- License, v. 2.0. If a copy of the MPL was not distributed with this -- file, You can obtain one at http://mozilla.org/MPL/2.0/. {-# LANGUAGE TemplateHaskell #-} 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 Prelude import qualified Data.Map.Strict as Map -- | A policy defines a load-balancing strategy and generally -- handles host visibility. data Policy = Policy { setup :: [Host] -> [Host] -> IO () -- ^ Initialise the policy with two sets of hosts. The first -- parameter are hosts known to be available, the second are other -- nodes. -- Please note that a policy may be re-initialised at any point -- through this method. , onEvent :: HostEvent -> IO () -- ^ Event handler. Policies will be informed about cluster changes -- through this function. , select :: IO (Maybe Host) -- ^ Host selection. The driver will ask for a host to use in a query -- through this function. A policy which has no available nodes my -- return Nothing. , current :: IO [Host] -- ^ Return all currently alive hosts. , acceptable :: Host -> IO Bool -- ^ During startup and node discovery, the driver will ask the -- policy if a dicovered host should be ignored. , hostCount :: IO Word -- ^ During query processing, the driver will ask the policy for -- a rough esitimate of alive hosts. The number is used to repeatedly -- invoke 'select' (with the underlying assumption that the policy -- returns mostly different hosts). , display :: IO String -- ^ Like having an effectful 'Show' instance for this policy. } type HostMap = TVar Hosts data Hosts = Hosts { _alive :: !(Map InetAddr Host) , _other :: !(Map InetAddr Host) } deriving Show makeLenses ''Hosts -- | Iterate over hosts one by one. roundRobin :: IO Policy roundRobin = do h <- newTVarIO emptyHosts c <- newTVarIO 0 return $ Policy (defSetup h) (defOnEvent h) (pickHost h c) (defCurrent h) 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 -- | Return hosts in random order. random :: IO Policy random = do h <- newTVarIO emptyHosts g <- createSystemRandom return $ Policy (defSetup h) (defOnEvent h) (pickHost h g) (defCurrent h) 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 ----------------------------------------------------------------------------- -- Defaults 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 defCurrent :: HostMap -> IO [Host] defCurrent r = Map.elems . 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)