second-transfer-0.10.0.1: hs-src/SecondTransfer/Sessions/Tidal.hs
{-# LANGUAGE GADTs, TemplateHaskell, OverloadedStrings #-}
{- | A simple session manager that prunes the number of connections from time to time....
-}
module SecondTransfer.Sessions.Tidal (
TidalContext (..)
, maxConnectionPerPeer_TiC
, highWaterMark_TiC
, newTidalSession
, defaultTidalContext
, tidalConnectionManager
) where
import Control.Lens
import Control.Monad.IO.Class (liftIO)
--import Control.DeepSeq (
--($!!),
-- deepseq )
import Control.Monad.Trans.Reader
import Control.Concurrent
import Control.Monad.ST
import Control.Monad (
foldM
--, mapM_
, forM_
-- , mapM
, filterM
, when
)
--import qualified Data.ByteString as B
--import qualified Data.ByteString.Builder as Bu
--import qualified Data.ByteString.Lazy as Bl
--import qualified Data.HashTable.IO as H
import qualified Data.Vector as DVec
--import qualified Data.Sequence as Sq
--import Data.IORef
import qualified Data.HashTable.ST.Cuckoo as Ht
import Data.Maybe (catMaybes)
import Data.Vector.Algorithms.Merge (sortBy)
import System.Mem.Weak
--import System.Clock (TimeSpec)
import SecondTransfer.Sessions.Config
import SecondTransfer.IOCallbacks.Types
-- | Configuration structure
data TidalContext = TidalContext {
-- | Number of admissible connections per peer
_maxConnectionPerPeer_TiC :: Int
-- | Callback to be used when a connection to a peer is lost.
-- | High water mark. When the number of connections go higher than this number,
-- some of them are pruned.
, _highWaterMark_TiC :: Int
}
makeLenses ''TidalContext
defaultTidalContext :: TidalContext
defaultTidalContext = TidalContext {
_maxConnectionPerPeer_TiC = 8
, _highWaterMark_TiC = 780
}
type ConnectionEntry = (HashableSockAddr ,Weak SessionGenericHandle)
type ConnectionList = [ConnectionEntry]
-- | State structure. Will live for the entire server lifetime
data TidalS = TidalS {
_context_TdS :: TidalContext
, _connections_TdS :: MVar ConnectionList
}
makeLenses ''TidalS
type TidalM = ReaderT TidalS IO
justRegisterNewConnection :: HashableSockAddr -> a -> SessionGenericHandle -> TidalM ()
justRegisterNewConnection sock_addr weakkey sgh =
do
connection_vector_mvar <- view connections_TdS
liftIO $ do
weakling <- mkWeak weakkey sgh Nothing
modifyMVar_ connection_vector_mvar
$ \ sq -> return $ (sock_addr, weakling) : sq
type MemoTable s = Ht.HashTable s HashableSockAddr Int
-- | Prunes any connections over the limit of connections allowed per host.
pruneSameHost :: TidalM ()
pruneSameHost =
do
-- number below, it is per peer.
max_connections <- view (context_TdS . maxConnectionPerPeer_TiC)
current_connections_mvar <- view connections_TdS
to_drop <- liftIO . modifyMVar current_connections_mvar $ \ current_connections -> do
let
countAndAdvance :: MemoTable s -> HashableSockAddr -> ST s Bool
countAndAdvance h addr = do
e <- Ht.lookup h addr
case e of
Just n ->
if n >= max_connections
then
return False
else do
Ht.insert h addr (n + 1)
return True
Nothing ->
do
Ht.insert h addr 1
return True
foldOperator :: MemoTable s -> ConnectionList -> ConnectionEntry -> ST s ConnectionList
foldOperator h drop_connections (addr, weakling) =
do
keep <- countAndAdvance h addr
if keep
then return drop_connections
else return ( (addr,weakling):drop_connections )
to_drop :: ConnectionList
to_drop = runST $ do
e <- Ht.new
foldM (foldOperator e) [] current_connections
remaining <- dropDeathConnections current_connections
return (remaining, to_drop)
-- We can invoke this function outside the lock, since it is not going to change the list .
liftIO $ dropConnections to_drop
return ()
--pruneNOldest
-- | Expects the connection list to be locked. Drops the death connections
-- and returns a list with the entries for the ones which are still alive.
dropDeathConnections :: ConnectionList -> IO ConnectionList
dropDeathConnections conns = filterM (\ (_addr, weakling) -> do
maybesomething <- deRefWeak weakling
case maybesomething of
Nothing -> return False
_ -> return True
) conns
dropConnections :: ConnectionList -> IO ()
dropConnections conns = forM_ conns $ \ (_addr, weakling) -> do
maybesomething <- deRefWeak weakling
case maybesomething of
Nothing -> return ()
(Just generic_handle ) -> do
case generic_handle of
Whole_SGH a ->
cleanlyCloseSession a
Partial_SGH _ iocallbacks ->
(iocallbacks ^. closeAction_IOC)
-- | Drops the oldest connections without activity ....
pruneOldestConnections :: Int -> TidalM ()
pruneOldestConnections how_many_to_drop =
do
-- First, create a vector with the information we are interested in ....
current_connections_mvar <- view connections_TdS
to_drop <- liftIO . withMVar current_connections_mvar $ \ current_connections -> do
sortable_conns_list <- catMaybes <$> mapM (\ (addr, weakling) -> do
w <- deRefWeak weakling
case w of
Just g -> return . Just $ (addr, g, weakling)
Nothing -> return Nothing
) current_connections
let
sortable_vector = DVec.fromList $ sortable_conns_list
with_time_spec <- DVec.mapM (\ (addr, generic_handle, weakling) -> do
last_act_time <- sessionLastActivity generic_handle
return ( (addr, weakling), last_act_time)
) sortable_vector
let
-- Now the oldest ones in the activity vector are first.
compare_with_spec ( _, t1) ( _, t2) = compare t1 t2
sorted_time_spec = DVec.modify (\ mv -> sortBy compare_with_spec mv) with_time_spec
-- This contains everybody that we can drop ...
to_drop = DVec.take how_many_to_drop sorted_time_spec
return . DVec.toList . DVec.map fst $ to_drop
liftIO $ dropConnections to_drop
whenAddingConnection :: HashableSockAddr -> SessionGenericHandle -> a -> TidalM ()
whenAddingConnection sock_addr handle key =
do
highwater_mark <- view (context_TdS . highWaterMark_TiC)
connections_mvar <- view connections_TdS
connection_count <- liftIO . withMVar connections_mvar $ \ current_connections -> do
let
connection_count = length current_connections
return connection_count
when (connection_count >= highwater_mark) $ do
-- Take measures!
pruneSameHost
pruneOldestConnections (highwater_mark `div` 3)
justRegisterNewConnection sock_addr key handle
newTidalSession :: TidalContext -> IO TidalS
newTidalSession tidal_context = do
connections <- newMVar []
return TidalS { _context_TdS = tidal_context, _connections_TdS = connections }
tidalConnectionManager :: TidalS -> NewSessionCallback
tidalConnectionManager tidals =
NewSessionCallback $ \ a b c -> runReaderT (whenAddingConnection a b c) tidals