module Database.Haskey.Alloc.Concurrent.FreePages.Query where
import Control.Applicative ((<|>), (<$>))
import Control.Concurrent.STM
import Control.Monad.State
import Control.Monad.Trans.Maybe
import Data.List.NonEmpty (NonEmpty((:|)))
import qualified Data.List.NonEmpty as NE
import qualified Data.Set as S
import Data.BTree.Alloc.Class
import Data.BTree.Impure
import Data.BTree.Impure.NonEmpty
import Data.BTree.Primitives
import Database.Haskey.Alloc.Concurrent.Environment
import Database.Haskey.Alloc.Concurrent.FreePages.Tree
import Database.Haskey.Utils.Monad (ifM)
import qualified Database.Haskey.Utils.STM.Map as Map
getFreePageId :: (Functor m, AllocM m, MonadIO m, MonadState (WriterEnv hnd) m)
=> S stateType ()
-> m (Maybe SomeFreePage)
getFreePageId t =
runMaybeT $ (DirtyFreePage <$> MaybeT (getFreedDirtyPageId t))
<|> (OldFreePage <$> MaybeT (getCachedFreePageId t))
<|> (OldFreePage <$> MaybeT (queryNewFreePageIds t))
getFreedDirtyPageId :: (Functor m, MonadState (WriterEnv hnd) m)
=> S stateType ()
-> m (Maybe DirtyFree)
getFreedDirtyPageId stateType =
case stateType of
DataState () -> do
s <- writerDataFileState <$> get
let (pid, s') = query s DataState
modify' $ \env -> env { writerDataFileState = s' }
return pid
IndexState () -> do
s <- writerIndexFileState <$> get
let (pid, s') = query s IndexState
modify' $ \env -> env { writerIndexFileState = s' }
return pid
where
query :: FileState t
-> (forall a. a -> S t a)
-> (Maybe DirtyFree, FileState t)
query env cons =
case S.minView (getSValue $ fileStateFreedDirtyPages env) of
Nothing -> (Nothing, env)
Just (pid, s') ->
let env' = env { fileStateFreedDirtyPages = cons s' } in
(Just pid, env')
getCachedFreePageId :: (Functor m, MonadState (WriterEnv hnd) m)
=> S stateType ()
-> m (Maybe OldFree)
getCachedFreePageId stateType =
ifM (not . writerReusablePagesOn <$> get) (return Nothing) $
case stateType of
DataState () -> do
s <- writerDataFileState <$> get
let (pid, s') = query s
modify' $ \env -> env { writerDataFileState = s' }
return pid
IndexState () -> do
s <- writerIndexFileState <$> get
let (pid, s') = query s
modify' $ \env -> env { writerIndexFileState = s' }
return pid
where
query :: FileState t -> (Maybe OldFree, FileState t)
query env = case fileStateReusablePages env of
[] -> (Nothing, env)
pid : pageIds ->
let env' = env { fileStateReusablePages = pageIds } in
(Just pid, env')
queryNewFreePageIds :: (AllocM m, MonadIO m, MonadState (WriterEnv hnd) m)
=> S stateType ()
-> m (Maybe OldFree)
queryNewFreePageIds stateType = ifM (not . writerReusablePagesOn <$> get) (return Nothing) $
case stateType of
DataState () ->
query DataState
writerDataFileState
(\e s -> e { writerDataFileState = s })
IndexState () ->
query IndexState
writerIndexFileState
(\e s -> e { writerIndexFileState = s })
where
query :: (AllocM m, MonadIO m, MonadState (WriterEnv hnd) m)
=> (forall a. a -> S t a)
-> (forall h. WriterEnv h -> FileState t)
-> (forall h. WriterEnv h -> FileState t -> WriterEnv h)
-> m (Maybe OldFree)
query cons getState setState = do
tree <- gets $ getSValue . fileStateFreeTree . getState
oldTxId <- gets $ fileStateReusablePagesTxId . getState
modify' $ \e -> e { writerReusablePagesOn = False }
tree' <- maybe (return tree) (`deleteSubtree` tree) oldTxId
modify' $ \e -> e { writerReusablePagesOn = True }
modify' $ \e -> setState e $
(getState e) { fileStateFreeTree = cons tree' }
lookupValidFreePageIds tree' >>= \case
Nothing -> do
modify' $ \e -> setState e $
(getState e) { fileStateDirtyReusablePages = S.empty
, fileStateReusablePages = []
, fileStateReusablePagesTxId = Nothing }
return Nothing
Just (txId, pid :| pageIds) -> do
modify' $ \e -> setState e $
(getState e) { fileStateDirtyReusablePages = S.empty
, fileStateReusablePages = map OldFree pageIds
, fileStateReusablePagesTxId = Just txId }
return (Just $ OldFree pid)
lookupValidFreePageIds :: (MonadIO m, AllocReaderM m, MonadState (WriterEnv hnd) m)
=> FreeTree
-> m (Maybe (TxId, NonEmpty PageId))
lookupValidFreePageIds tree = runMaybeT $
MaybeT (lookupFreePageIds tree) >>= (MaybeT . checkFreePages)
lookupFreePageIds :: (Functor m, AllocReaderM m, MonadState (WriterEnv hnd) m)
=> FreeTree
-> m (Maybe (Unchecked (TxId, NonEmpty PageId)))
lookupFreePageIds tree = lookupMinTree tree >>= \case
Nothing -> return Nothing
Just (tx, subtree) -> do
pids <- subtreeToList subtree
return . Just $ Unchecked (tx, pids)
where
subtreeToList subtree = NE.map fst <$> nonEmptyToList subtree
newtype Unchecked a = Unchecked a
checkFreePages :: (Functor m, MonadIO m, MonadState (WriterEnv hnd) m)
=> Unchecked (TxId, NonEmpty PageId)
-> m (Maybe (TxId, NonEmpty PageId))
checkFreePages (Unchecked v) = do
readers <- writerReaders <$> get
oldest <- liftIO . atomically $ Map.lookupMinKey readers
tx <- writerTxId <$> get
if maybe True (> fst v) oldest && fst v + 1 < tx
then return (Just v)
else return Nothing