module Database.VCache.Clean
( cleanStep
) where
import Control.Monad
import Control.Applicative
import Control.Concurrent
import Data.Bits
import qualified Data.Traversable as TR
import qualified Data.Map.Strict as Map
import qualified Data.List as L
import Data.IORef
import qualified System.Mem.Weak as Weak
import qualified System.Random as Random
import Database.LMDB.Raw
import Database.VCache.Types
cleanStep :: VSpace -> IO ()
cleanStep vc = do
wtgt <- readIORef (vcache_climit vc)
w0 <- estCacheSize vc
let hitRate =
if ((100 * w0) < ( 80 * wtgt)) then 0.00 else
if ((100 * w0) < (100 * wtgt)) then 0.01 else
if ((100 * w0) < (120 * wtgt)) then 0.02 else
if ((100 * w0) < (150 * wtgt)) then 0.03 else
if ((100 * w0) < (190 * wtgt)) then 0.04 else
if ((100 * w0) < (240 * wtgt)) then 0.05 else
0.06
xcln vc hitRate
updateCacheSizeEst vc 10 0.01
wf <- estCacheSize vc
bsig <- shouldSignalWriter vc
when bsig (signalWriter vc)
let bSatisfied = (max w0 wf) < wtgt
let dtSleep = if bSatisfied then 270000 else 135000
usleep dtSleep
usleep :: Int -> IO ()
usleep = threadDelay
estCacheSize :: VSpace -> IO Int
estCacheSize vc = do
csze <- readIORef (vcache_csize vc)
let avgAddr = sqrt (csze_addr_sqsz csze)
ctAddrs <- fromIntegral <$> readCacheAddrCt vc
return $! ceiling $ avgAddr * ctAddrs
readCacheAddrCt :: VSpace -> IO Int
readCacheAddrCt vc = do
cvrefs <- readMVar (vcache_cvrefs vc)
return $! Map.size cvrefs
updateCacheSizeEst :: VSpace -> Int -> Double -> IO ()
updateCacheSizeEst vc !n !alpha =
readMVar (vcache_cvrefs vc) >>= \ cvrefs ->
if Map.null cvrefs then return () else
Random.newStdGen >>= \ rgen ->
let ixs = L.take n $ Random.randomRs (0, Map.size cvrefs 1) rgen in
let readAddrSize ix =
let (_addr, tym) = Map.elemAt ix cvrefs in
let (_ty, e) = Map.findMin tym in
readVREphSize e >>= \ esz ->
return (esz * fromIntegral (Map.size tym))
in
mapM readAddrSize ixs >>= \ lSizes ->
let szTotal = L.foldl' (+) 0 lSizes in
let sqszTotal = L.foldl' (\ ssq x -> ssq + (x*x)) 0 lSizes in
let szAvgSamp = fromIntegral (szTotal `div` n) in
let sqszAvgSamp = fromIntegral (sqszTotal `div` n) in
readIORef (vcache_csize vc) >>= \ (CacheSizeEst szAvgEst sqszAvgEst) ->
let upd new old = (alpha * new) + ((1.0 alpha) * old) in
let szAvg' = upd szAvgSamp szAvgEst in
let sqszAvg' = upd sqszAvgSamp sqszAvgEst in
writeIORef (vcache_csize vc) $! (CacheSizeEst szAvg' sqszAvg')
readVREphSize :: VREph -> IO Int
readVREphSize (VREph { vreph_cache = wk }) =
Weak.deRefWeak wk >>= \ mbc -> case mbc of
Nothing -> return 2048
Just cache -> readIORef cache >>= \ c -> case c of
NotCached ->
let eMsg = "VCache bug: NotCached element found in vcache_cvrefs" in
fail eMsg
Cached _ bf ->
let lgSz = 6 + fromIntegral (0x1f .&. bf) in
return $! 1 `shiftL` lgSz
xcln :: VSpace -> Double -> IO ()
xcln !vc !hr = do
ct <- readCacheAddrCt vc
let hct = ceiling $ hr * fromIntegral ct
r <- Random.newStdGen
xclnLoop vc hct r
xclnLoop :: VSpace -> Int -> Random.StdGen -> IO ()
xclnLoop !vc !n !r =
if (n < 1) then return () else
xclnStrike vc r >>= xclnLoop vc (n1)
xclnStrike :: VSpace -> Random.StdGen -> IO Random.StdGen
xclnStrike !vc !r = modifyMVarMasked (vcache_cvrefs vc) $ \ cvrefs ->
if Map.null cvrefs then return (cvrefs, r) else do
let (ix,r') = Random.randomR (0, Map.size cvrefs 1) r
let (addr, tym) = Map.elemAt ix cvrefs
tym' <- Map.mapMaybe id <$> TR.traverse strikeVREph tym
let cvrefs' = if Map.null tym' then Map.delete addr cvrefs
else Map.insert addr tym' cvrefs
return (cvrefs', r')
strikeVREph :: VREph -> IO (Maybe VREph)
strikeVREph vreph@(VREph { vreph_cache = wk }) =
Weak.deRefWeak wk >>= \ mbCache -> case mbCache of
Nothing -> return Nothing
Just cache -> atomicModifyIORef cache $ \ c -> case c of
Cached r bf | (0 /= bf .&. 0x60) ->
let bf' = (0x80 .|. (bf 0x20)) in
let c' = Cached r bf' in
(c', c' `seq` (Just vreph))
_ -> (NotCached, Nothing)
shouldSignalWriter :: VSpace -> IO Bool
shouldSignalWriter vc =
readMVar (vcache_memory vc) >>= \ m ->
let bHoldingAllocs = not (emptyAllocation (mem_alloc m)) in
if bHoldingAllocs then return True else
readZeroesCt vc >>= \ ctZeroes ->
let ctEphAddrs = Map.size (mem_vrefs m) + Map.size (mem_pvars m) in
if (ctEphAddrs < ctZeroes) then return True else
return False
readZeroesCt :: VSpace -> IO Int
readZeroesCt vc = withRdOnlyTxn vc $ \ txn ->
mdb_stat' txn (vcache_db_refct0 vc) >>= \ stat ->
return $! fromIntegral (ms_entries stat)
emptyAllocation :: Allocator -> Bool
emptyAllocation ac = fn n && fn c && fn p where
n = alloc_frm_next ac
c = alloc_frm_curr ac
p = alloc_frm_prev ac
fn = Map.null . alloc_list
signalWriter :: VSpace -> IO ()
signalWriter vc = void $ tryPutMVar (vcache_signal vc) ()