{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE NumericUnderscores #-}
{-# LANGUAGE NoFieldSelectors #-}
module Cardano.Rpc.Server.Internal.TimedCache
( TimedCache
, newTimedCache
, readThroughCache
)
where
import RIO
import Data.Time.Clock (DiffTime)
data TimedCache a = TimedCache
{ forall a. TimedCache a -> MVar (Maybe (CacheEntry a))
cacheVar :: !(MVar (Maybe (CacheEntry a)))
, forall a. TimedCache a -> DiffTime
expiryTimeout :: !DiffTime
}
data CacheEntry a = CacheEntry
{ forall a. CacheEntry a -> a
cachedValue :: !a
, forall a. CacheEntry a -> Async ()
watcher :: !(Async ())
, forall a. CacheEntry a -> DiffTime
lastAccess :: !DiffTime
}
newTimedCache
:: MonadIO m
=> DiffTime
-> m (TimedCache a)
newTimedCache :: forall (m :: * -> *) a. MonadIO m => DiffTime -> m (TimedCache a)
newTimedCache DiffTime
expiryTimeout = do
cacheVar <- Maybe (CacheEntry a) -> m (MVar (Maybe (CacheEntry a)))
forall (m :: * -> *) a. MonadIO m => a -> m (MVar a)
newMVar Maybe (CacheEntry a)
forall a. Maybe a
Nothing
pure TimedCache{cacheVar, expiryTimeout}
readThroughCache
:: MonadUnliftIO m
=> TimedCache a
-> m a
-> m a
readThroughCache :: forall (m :: * -> *) a.
MonadUnliftIO m =>
TimedCache a -> m a -> m a
readThroughCache TimedCache{MVar (Maybe (CacheEntry a))
cacheVar :: forall a. TimedCache a -> MVar (Maybe (CacheEntry a))
cacheVar :: MVar (Maybe (CacheEntry a))
cacheVar, DiffTime
expiryTimeout :: forall a. TimedCache a -> DiffTime
expiryTimeout :: DiffTime
expiryTimeout} m a
doLoad =
MVar (Maybe (CacheEntry a))
-> (Maybe (CacheEntry a) -> m (Maybe (CacheEntry a), a)) -> m a
forall (m :: * -> *) a b.
MonadUnliftIO m =>
MVar a -> (a -> m (a, b)) -> m b
modifyMVar MVar (Maybe (CacheEntry a))
cacheVar ((Maybe (CacheEntry a) -> m (Maybe (CacheEntry a), a)) -> m a)
-> (Maybe (CacheEntry a) -> m (Maybe (CacheEntry a), a)) -> m a
forall a b. (a -> b) -> a -> b
$ \case
Just entry :: CacheEntry a
entry@CacheEntry{a
cachedValue :: forall a. CacheEntry a -> a
cachedValue :: a
cachedValue} -> do
now <- m DiffTime
forall (m :: * -> *). MonadIO m => m DiffTime
getMonotonicDiffTime
pure (Just entry{lastAccess = now}, cachedValue)
Maybe (CacheEntry a)
Nothing -> do
loaded <- m a
doLoad
now <- getMonotonicDiffTime
watcher <- liftIO $ asyncWithUnmask (\forall b. IO b -> IO b
unmask -> IO () -> IO ()
forall b. IO b -> IO b
unmask IO ()
watchForExpiry)
pure (Just CacheEntry{cachedValue = loaded, watcher, lastAccess = now}, loaded)
where
remainingIdleTime :: DiffTime -> DiffTime -> DiffTime
remainingIdleTime :: DiffTime -> DiffTime -> DiffTime
remainingIdleTime DiffTime
lastAccess DiffTime
now = DiffTime
lastAccess DiffTime -> DiffTime -> DiffTime
forall a. Num a => a -> a -> a
+ DiffTime
expiryTimeout DiffTime -> DiffTime -> DiffTime
forall a. Num a => a -> a -> a
- DiffTime
now
watchForExpiry :: IO ()
watchForExpiry :: IO ()
watchForExpiry =
MVar (Maybe (CacheEntry a)) -> IO (Maybe (CacheEntry a))
forall (m :: * -> *) a. MonadIO m => MVar a -> m a
readMVar MVar (Maybe (CacheEntry a))
cacheVar IO (Maybe (CacheEntry a))
-> (Maybe (CacheEntry a) -> IO ()) -> IO ()
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
Maybe (CacheEntry a)
Nothing -> () -> IO ()
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
Just CacheEntry{DiffTime
lastAccess :: forall a. CacheEntry a -> DiffTime
lastAccess :: DiffTime
lastAccess} -> do
now <- IO DiffTime
forall (m :: * -> *). MonadIO m => m DiffTime
getMonotonicDiffTime
let remaining = DiffTime -> DiffTime -> DiffTime
remainingIdleTime DiffTime
lastAccess DiffTime
now
if remaining > 0
then do
delayFor remaining
watchForExpiry
else do
isEmptied <- modifyMVar cacheVar $ \case
Maybe (CacheEntry a)
Nothing -> (Maybe (CacheEntry a), Bool) -> IO (Maybe (CacheEntry a), Bool)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Maybe (CacheEntry a)
forall a. Maybe a
Nothing, Bool
True)
Just entry :: CacheEntry a
entry@CacheEntry{lastAccess :: forall a. CacheEntry a -> DiffTime
lastAccess = DiffTime
lastAccessUnderLock} -> do
nowUnderLock <- IO DiffTime
forall (m :: * -> *). MonadIO m => m DiffTime
getMonotonicDiffTime
pure $
if remainingIdleTime lastAccessUnderLock nowUnderLock > 0
then (Just entry, False)
else (Nothing, True)
unless isEmptied watchForExpiry
getMonotonicDiffTime :: MonadIO m => m DiffTime
getMonotonicDiffTime :: forall (m :: * -> *). MonadIO m => m DiffTime
getMonotonicDiffTime = Double -> DiffTime
forall a b. (Real a, Fractional b) => a -> b
realToFrac (Double -> DiffTime) -> m Double -> m DiffTime
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> m Double
forall (m :: * -> *). MonadIO m => m Double
getMonotonicTime
delayFor :: MonadIO m => DiffTime -> m ()
delayFor :: forall (m :: * -> *). MonadIO m => DiffTime -> m ()
delayFor DiffTime
duration = Int -> m ()
forall (m :: * -> *). MonadIO m => Int -> m ()
threadDelay (Int -> m ()) -> (DiffTime -> Int) -> DiffTime -> m ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. DiffTime -> Int
forall b. Integral b => DiffTime -> b
forall a b. (RealFrac a, Integral b) => a -> b
ceiling (DiffTime -> m ()) -> DiffTime -> m ()
forall a b. (a -> b) -> a -> b
$ DiffTime
duration DiffTime -> DiffTime -> DiffTime
forall a. Num a => a -> a -> a
* DiffTime
1_000_000