module Happstack.Server.Internal.TimeoutTable
( TimeoutTable
, new
, null
, insert
, delete
, killAll
, killOlderThan
, waitForActivity
)
where
import Control.Concurrent
import Control.Monad
import Data.Bits
import qualified Data.PSQueue as PSQ
import Data.PSQueue (PSQ)
import Data.Time.Clock.POSIX(POSIXTime)
import qualified Data.Vector as V
import Data.Vector (Vector)
import Data.Word
import GHC.Conc (numCapabilities)
import Prelude hiding (null)
import Data.Concurrent.HashMap (nextHighestPowerOf2)
type TT = PSQ ThreadId POSIXTime
data TimeoutTable = TimeoutTable {
_maps :: !(Vector (MVar TT))
, _activity :: !(MVar ())
}
defaultNumberOfLocks :: Word
defaultNumberOfLocks = nextHighestPowerOf2 $ toEnum $ 8 * numCapabilities
hashToBucket :: Word -> Word
hashToBucket x = x .&. (defaultNumberOfLocks1)
new :: IO TimeoutTable
new = do
vector <- V.replicateM (fromEnum defaultNumberOfLocks) (newMVar PSQ.empty)
act <- newEmptyMVar
return $ TimeoutTable vector act
null :: TimeoutTable -> IO Bool
null (TimeoutTable maps _) = do
nulls <- V.mapM (\mv -> withMVar mv $ return . PSQ.null) maps
return $ V.and nulls
insert :: Word -> ThreadId -> POSIXTime -> TimeoutTable -> IO ()
insert thash tid time (TimeoutTable maps act) = do
modifyMVar_ psqMv $ \psq -> do
let !psq' = PSQ.insert tid time psq
return $! psq'
tryPutMVar act ()
return ()
where
bucket = hashToBucket thash
psqMv = V.unsafeIndex maps $ fromEnum bucket
delete :: Word -> ThreadId -> TimeoutTable -> IO ()
delete thash tid (TimeoutTable maps act) = do
modifyMVar_ psqMv $ \psq -> do
let !psq' = PSQ.delete tid psq
return $! psq'
tryPutMVar act ()
return ()
where
bucket = hashToBucket thash
psqMv = V.unsafeIndex maps $ fromEnum bucket
killAll :: TimeoutTable -> IO ()
killAll (TimeoutTable maps _) = do
V.mapM_ k maps
where
k psqMV = modifyMVar_ psqMV $ \psq -> do
mapM_ killThread $ PSQ.keys psq
return PSQ.empty
killOlderThan :: POSIXTime -> TimeoutTable -> IO ()
killOlderThan time (TimeoutTable maps _) = do
V.mapM_ processPSQ maps
where
processPSQ psqMV = modifyMVar_ psqMV $ \psq -> do
let (psq', threads) = findOlder psq []
mapM_ killThread threads
return psq'
findOlder psq l =
let mmin = PSQ.findMin psq
in maybe (psq,l)
(\m -> if PSQ.prio m <= time
then findOlder (PSQ.deleteMin psq) ((PSQ.key m):l)
else (psq,l))
mmin
waitForActivity :: TimeoutTable -> IO ()
waitForActivity t@(TimeoutTable _ act) = do
takeMVar act
b <- null t
unless b $ (tryPutMVar act () >> return ())
threadDelay 2500000