packages feed

ttl-hashtables 1.4.0.0 → 1.4.1.0

raw patch · 3 files changed

+44/−31 lines, 3 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

+ Data.TTLHashTable: data TimeStamp
+ Data.TTLHashTable: instance GHC.Classes.Eq Data.TTLHashTable.TimeStamp
+ Data.TTLHashTable: instance GHC.Classes.Ord Data.TTLHashTable.TimeStamp
+ Data.TTLHashTable: instance GHC.Enum.Enum Data.TTLHashTable.TimeStamp
+ Data.TTLHashTable: instance GHC.Num.Num Data.TTLHashTable.TimeStamp
+ Data.TTLHashTable: instance GHC.Real.Integral Data.TTLHashTable.TimeStamp
+ Data.TTLHashTable: instance GHC.Real.Real Data.TTLHashTable.TimeStamp
- Data.TTLHashTable: getTimeStamp :: MonadIO m => m Int
+ Data.TTLHashTable: getTimeStamp :: MonadIO m => m TimeStamp
- Data.TTLHashTable: lookupMaybeExpired :: (Eq k, Hashable k, MonadIO m, Failable m) => TTLHashTable h k v -> k -> m (v, Int)
+ Data.TTLHashTable: lookupMaybeExpired :: (Eq k, Hashable k, MonadIO m, Failable m) => TTLHashTable h k v -> k -> m (v, TimeStamp)

Files

src/Data/TTLHashTable.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE GADTs, RecordWildCards #-}+{-# LANGUAGE GADTs, GeneralizedNewtypeDeriving, RecordWildCards #-} {- | Module: Data.TTLHashTable Description: Adds TTL entry expiration to the excellent mutable hash tables from the hashtables package@@ -34,6 +34,7 @@ -- hash tables in the ST monad aren't as well. If concurrent threads need to operate on the same -- table, you need to provide external means of synchronization to guarantee exclusive access -- to the table+                          TimeStamp,                           TTLHashTable,                           TTLHashTableError(..),                           Settings(..),@@ -93,10 +94,13 @@                         gcMaxEntriesRef_  :: IORef Int }                     -> TTLHashTable h k v -data Value v = Value { expiresAt :: Int,+data Value v = Value { expiresAt :: TimeStamp,                        ttl       :: Int,                        value     :: v } +-- | A representation of a point in time, used to track entry lifetime.+newtype TimeStamp = TimeStamp Int deriving (Num, Integral, Real, Enum, Eq, Ord)+ -- | The 'Settings' type allows for specifying how the hash table should behave. data Settings = Settings {                            -- | Maximum size of the hash table. Once reached, insertion of keys@@ -198,7 +202,7 @@ insertWithTTL ht@TTLHashTable {..} ttl k v = do   numEntries <- liftIO $ readIORef numEntriesRef_   now        <- getTimeStamp-  let expiresAt = now + ttl * 1000000 -- to nanoseconds+  let expiresAt = now + fromIntegral (ttl * 1000000) -- to nanoseconds   maxSize <- liftIO $ readIORef maxSizeRef_   if numEntries < maxSize     then insert' expiresAt@@ -210,7 +214,7 @@                   liftIO $ do                     H.insert hashTable_ k value                     modifyIORef' numEntriesRef_ (+1)-                    modifyIORef' timeStampsRef_ $ M.insert expiresAt k+                    modifyIORef' timeStampsRef_ $ M.insert (fromIntegral expiresAt) k  -- | like 'insertWithTTL' but ignores insertion failure insertWithTTL_ :: (Eq k, Hashable k, C.HashTable h, MonadIO m)@@ -222,17 +226,17 @@ insertWithTTL_ h ttl k = void . runMaybeT . insertWithTTL h ttl k  -checkOldest :: (Eq k, Hashable k, MonadIO m) => TTLHashTable h k v -> Int -> m (Maybe ())+checkOldest :: (Eq k, Hashable k, MonadIO m) => TTLHashTable h k v -> TimeStamp -> m (Maybe ()) checkOldest ht@TTLHashTable {..} now =     liftIO . runMaybeT $ do       (timeStamp, k) <- MaybeT . atomicModifyIORef' timeStampsRef_ $ \timeStamps ->                          case M.minViewWithKey timeStamps of                            Nothing -> (timeStamps, Nothing)                            Just ((timeStamp, k), timeStamps') ->-                             if timeStamp <= now+                             if (fromIntegral timeStamp) <= now                                then (timeStamps', Just (timeStamp, k))                                else (timeStamps, Nothing)-      MaybeT $ mutateWith (deleteExpired timeStamp) ht k+      MaybeT $ mutateWith (deleteExpired $ fromIntegral timeStamp) ht k  -- | Lookup a key in the hash table. If called straight in the IO monad it would throw a -- 'NotFound' exception, but if called under @MaybeT IO@ or @ExceptT SomeException IO@ it would@@ -246,7 +250,13 @@ lookupAndRenew :: (Eq k, Hashable k, MonadIO m, Failable m) => TTLHashTable h k v -> k -> m v lookupAndRenew = lookup' True -lookupMaybeExpired :: (Eq k, Hashable k, MonadIO m, Failable m) => TTLHashTable h k v -> k -> m (v, Int)+-- | Perform a lookup without doing any actual checks to see if the entry is still alive or+-- if it has expired. Instead, the entry expiration timestamp is returned together with the+-- value (if found). The purpose of this function is to provide a manual way to check expiration,+-- for example when performing batch lookups or sacrificing lifetime resolution for the sake+-- of performance (and thus avoiding an expensive timestamp retrieval every time a lookup is+-- performed)+lookupMaybeExpired :: (Eq k, Hashable k, MonadIO m, Failable m) => TTLHashTable h k v -> k -> m (v, TimeStamp) lookupMaybeExpired TTLHashTable {..} k =   maybe (failure NotFound) returnValue =<< liftIO (H.lookup hashTable_ k)     where returnValue Value {..} = return (value, expiresAt)@@ -262,13 +272,13 @@         (mExpire, mValue) <- mutateWith (refreshEntry now) ht k         removeTimeStamp mExpire         Value {..}        <- checkLookedUp mValue now-        liftIO $ modifyIORef' timeStampsRef_ $ M.insert expiresAt k+        liftIO $ modifyIORef' timeStampsRef_ $ M.insert (fromIntegral expiresAt) k         return value     where refreshEntry _ Nothing =               (Nothing, (Nothing, Nothing))           refreshEntry now (Just v@Value{..}) =               if expiresAt > now then-                  let v' = Value { expiresAt = now + ttl * 1000000, -- nanoseconds+                  let v' = Value { expiresAt = now + (fromIntegral ttl * 1000000), -- nanoseconds                                    ttl = ttl,                                    value = value }                   in (Just v', (Just expiresAt, Just v'))@@ -277,11 +287,11 @@           removeTimeStamp Nothing =               return ()           removeTimeStamp (Just timeStamp) =-              liftIO . modifyIORef' timeStampsRef_ $ M.delete timeStamp+              liftIO . modifyIORef' timeStampsRef_ $ M.delete (fromIntegral timeStamp)  checkLookedUp :: (MonadIO m, Failable m)                  => Maybe (Value v)-                 -> Int+                 -> TimeStamp                  -> m (Value v) checkLookedUp Nothing _               = failure NotFound checkLookedUp (Just v@Value {..}) now =@@ -301,10 +311,10 @@ delete ht@TTLHashTable {..} k = do     timeStamp <- deleteWith $ \v ->       (Nothing, v >>= return . expiresAt)-    forM_ timeStamp $ liftIO . modifyIORef' timeStampsRef_ . M.delete+    forM_ timeStamp $ liftIO . modifyIORef' timeStampsRef_ . M.delete . fromIntegral       where deleteWith fun = mutateWith fun ht k -deleteExpired :: Int -> Maybe (Value v) -> (Maybe (Value v), Maybe ())+deleteExpired :: TimeStamp -> Maybe (Value v) -> (Maybe (Value v), Maybe ()) deleteExpired _ Nothing =   (Nothing, Nothing) deleteExpired timeStamp (Just v@Value {..}) =@@ -357,10 +367,10 @@ removeExpired' ht@TTLHashTable {..} gcMaxEntries =   liftIO $ do     now          <- getTimeStamp-    (n, expired) <- atomicModifyIORef' timeStampsRef_ $ selectedEntries now+    (n, expired) <- atomicModifyIORef' timeStampsRef_ $ selectedEntries (fromIntegral now)     Prelude.mapM_ remove expired     return n-        where remove (timeStamp, k) = mutateWith (deleteExpired timeStamp) ht k+        where remove (timeStamp, k) = mutateWith (deleteExpired $ fromIntegral timeStamp) ht k               selectedEntries now m =                   let (old, active)      = M.split now m                       (selected, notYet) = splitAt gcMaxEntries $ M.toList old@@ -368,11 +378,13 @@                   in (M.union active toReinsert, (n, selected))               countFromList (n, acc) (k, v) = (n + 1, M.insert k v acc) --- | Returns a timestamp value in nanoseconds-getTimeStamp :: (MonadIO m) => m Int+-- | Returns a timestamp for the current moment (__now__). This value can be used when+-- | comparing expiration times manually as returned by 'lookupMaybeExpired' for example,+-- | when it makes sense to do so in the name of performance.+getTimeStamp :: (MonadIO m) => m TimeStamp getTimeStamp = do   (TimeSpec secs ns) <- liftIO $ getTime Monotonic-  return . fromIntegral $ (secs * 1000000000 + ns)+  return . fromIntegral $ secs * 1000000000 + ns  -- | A strict fold in IO over the @(key, value)@ records in a hash table foldM :: (MonadIO m) => (a -> (k, v) -> IO a) -> a -> TTLHashTable h k v -> m a@@ -418,12 +430,14 @@   defaultTTL <- liftIO $ readIORef defaultTTLRef_   mutateWith (mutate' now defaultTTL f) ht k -mutate' :: Int -> Int -> (Maybe v -> (Maybe v, a)) -> Maybe (Value v) -> (Maybe (Value v), a)+mutate' :: TimeStamp -> Int -> (Maybe v -> (Maybe v, a)) -> Maybe (Value v) -> (Maybe (Value v), a) mutate' now defaultTTL f mV =     let (expiresAt, ttl, mValue) = metaFrom mV         vFrom mValue' = do           value <- mValue'           return Value {expiresAt = expiresAt, ttl = ttl, value = value }     in first vFrom $ f mValue-        where metaFrom Nothing            = (now + defaultTTL * 1000000, defaultTTL, Nothing)-              metaFrom (Just Value {..})  = (now + ttl * 1000000, ttl, Just value)+        where metaFrom Nothing =+                (now + fromIntegral (defaultTTL * 1000000), defaultTTL, Nothing)+              metaFrom (Just Value {..})  =+                (now + fromIntegral (ttl * 1000000), ttl, Just value)
test/Spec.hs view
@@ -16,7 +16,6 @@  withSizedRenewableTTLHashTable :: IO (TTLHashTable Cuckoo.HashTable Int String) withSizedRenewableTTLHashTable = newWithSettings def { maxSize = 2,-                                                       renewUponRead = True,                                                        defaultTTL = 100,                                                        gcMaxEntries = 1 } @@ -50,7 +49,7 @@             threadDelay 200000 -- wait 200 mS             s' <- size ht             s' `shouldBe` 2-            _ <- find ht 1+            _ <- runExceptT $ lookupAndRenew ht 1             s'' <- size ht             s'' `shouldBe` 1             delete ht 2@@ -107,14 +106,14 @@           \ht -> do             insert ht 1 "foo"             threadDelay 50000 -- wait 50 mS-            _ <- find ht 1+            _ <- lookupAndRenew ht 1             threadDelay 50000 -- wait 50 mS-            _ <- find ht 1+            _ <- lookupAndRenew ht 1             threadDelay 50000 -- wait 50 mS-            _ <- find ht 1+            _ <- lookupAndRenew ht 1             threadDelay 50000 -- wait 50 mS-            v <- find ht 1-            v `shouldBe` Just "foo"+            v <- lookupAndRenew ht 1+            v `shouldBe` "foo"         it "Checks that lookup doesn't renew expired entry" $           \ht -> do             insert ht 1 "foo"
ttl-hashtables.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 3388338e9a83ada9651f2f035614cb640965e9628df50285184ab9166923e93a+-- hash: 4659935f4d42287b429b71c7213283dcc3e3c103e8d0527509ad3d1d73a4f6a1  name:           ttl-hashtables-version:        1.4.0.0+version:        1.4.1.0 synopsis:       Extends hashtables so that entries added can be expired after a TTL description:    This library extends fast mutable hashtables so that entries added can be expired after a given TTL (time to live). This TTL can be specified as a default property of the table or on a per entry basis. category:       Data