mirror of
https://github.com/urbit/shrub.git
synced 2024-12-21 01:41:37 +03:00
65 lines
1.7 KiB
Haskell
65 lines
1.7 KiB
Haskell
module Urbit.Vere.Ames.LaneCache (cache) where
|
|
|
|
import Urbit.Prelude
|
|
|
|
import qualified Data.HashPSQ as P
|
|
|
|
import Urbit.Noun.Time
|
|
|
|
expiry :: Gap
|
|
expiry = (2 * 60) ^. from secs
|
|
|
|
bound :: Int
|
|
bound = 1_000
|
|
|
|
-- | An "upside down" time for use as a priority.
|
|
newtype New = New Wen
|
|
deriving newtype (Eq, Ord)
|
|
|
|
new :: Wen -> New
|
|
new = New . negate
|
|
|
|
wen :: New -> Wen
|
|
wen (New w) = negate w
|
|
|
|
-- | Given a new, find an older new corresponding to `expiry` ago.
|
|
lag :: New -> New
|
|
lag n = new (addGap (wen n) expiry)
|
|
|
|
trim :: (Hashable a, Ord a, Ord b) => P.HashPSQ a b c -> P.HashPSQ a b c
|
|
trim p = if P.size p > bound then P.deleteMin p else p
|
|
|
|
cache :: forall a b m n
|
|
. (Ord a, Hashable a, MonadIO m, MonadIO n)
|
|
=> (a -> m b)
|
|
-> n (a -> m b)
|
|
cache act = do
|
|
cas <- newTVarIO (P.empty :: P.HashPSQ a New b)
|
|
|
|
let fun x = P.lookup x <$> readTVarIO cas >>= \case
|
|
Nothing -> thru
|
|
Just (n, v) -> do
|
|
let t = wen n
|
|
t' <- io now
|
|
if gap t' t > expiry
|
|
then thru
|
|
else pure v
|
|
where
|
|
-- Insert a key into the map, simultaneously removing *all* stale
|
|
-- entries. Since insertion is linear in the size of the map,
|
|
-- presumably it's not horrible to do it this way. The alternative
|
|
-- would be to have a thread doing a purge every 10s or something, and
|
|
-- then we'd have to be in RAcquire.
|
|
up :: a -> New -> b -> P.HashPSQ a New b -> P.HashPSQ a New b
|
|
up k n v ps = trim
|
|
$ P.insert k n v
|
|
$ snd $ P.atMostView (lag n) ps
|
|
thru :: m b
|
|
thru = do
|
|
n <- new <$> io now
|
|
v <- act x
|
|
atomically $ modifyTVar' cas $ up x n v
|
|
pure v
|
|
|
|
pure fun
|