packages feed

net-mqtt-0.8.6.0: src/Data/Map/Strict/Decaying.hs

{-# LANGUAGE RecordWildCards #-}
module Data.Map.Strict.Decaying
  ( Map,
    new,
    insert,
    delete,
    elems,
    findWithDefault,
    updateLookupWithKey,

    -- * Testing Hooks
    tick,
  )
where

import           Control.Concurrent.Async    (async, cancel, link)
import           Control.Concurrent.STM      (STM, atomically)
import           Control.Concurrent.STM.TVar (TVar, mkWeakTVar, modifyTVar', newTVarIO, readTVar, stateTVar, writeTVar)
import           Control.Exception           (mask)
import           Control.Monad               (forever, join, void, (<=<))
import           Control.Monad.Loops         (whileJust_)
import           Data.Foldable               (toList)
import qualified Data.Map.Strict.Expiring    as Map
import           Data.Maybe                  (fromMaybe, mapMaybe)
import           Data.Time                   (NominalDiffTime)
import           Data.Time.Clock.POSIX       (POSIXTime, getPOSIXTime)
import           GHC.Conc                    (threadDelay)
import           System.Mem.Weak             (deRefWeak)

data Map k a = Map {
  mapVar :: TVar (Map.Map POSIXTime k a),
  maxAge :: NominalDiffTime
}

expiry :: NominalDiffTime -> Map.Map POSIXTime k a -> POSIXTime
expiry ma = (+ ma) . Map.generation

insert :: Ord k => k -> v -> Map k v -> STM ()
insert k v (Map m ma) = modifyTVar' m (\m -> Map.insert (expiry ma m) k v m)

delete :: Ord k => k -> Map k v -> STM ()
delete k (Map m _) = modifyTVar' m (Map.delete k)

findWithDefault :: Ord k => v -> k -> Map k v -> STM v
findWithDefault d k Map{..} = fromMaybe d . Map.lookup k <$> readTVar mapVar

-- | All visible records.
elems :: Map k a -> STM [a]
elems Map{..} = toList <$> readTVar mapVar

tick :: Ord k => Map k v -> IO ()
tick Map{..} = getPOSIXTime >>= \t -> atomically $ modifyTVar' mapVar (Map.newGen t)

updateLookupWithKey :: Ord k => (k -> a -> Maybe a) -> k -> Map k a -> STM (Maybe a)
updateLookupWithKey f k (Map mv ma) = stateTVar mv (\m -> Map.updateLookupWithKey (expiry ma m) f k m)

new :: Ord k => NominalDiffTime -> IO (Map k a)
new maxAge = mask $ \restore -> do
  now <- getPOSIXTime
  var <- newTVarIO (Map.new now)
  wVar <- mkWeakTVar var $ pure ()
  let m = Map var maxAge
  link <=< restore $ async $ whileJust_ (deRefWeak wVar) (\m' -> tick' m' *> threadDelay 1000000)
  pure m
  where
    tick' m = getPOSIXTime >>= \t -> atomically $ modifyTVar' m (Map.newGen t)