cached-io 1.3.2.0 → 1.4.0.0
raw patch · 4 files changed
+39/−16 lines, 4 filesdep +transformersdep ~basePVP ok
version bump matches the API change (PVP)
Dependencies added: transformers
Dependency ranges changed: base
API changes (from Hackage documentation)
- Control.Concurrent.CachedIO: cachedIO :: (MonadIO m, MonadIO t, MonadCatch t) => NominalDiffTime -> t a -> m (Cached t a)
+ Control.Concurrent.CachedIO: cachedIO :: (MonadIO m, MonadIO t, MonadMask t) => NominalDiffTime -> t a -> m (Cached t a)
- Control.Concurrent.CachedIO: cachedIO' :: (MonadIO m, MonadIO t, MonadCatch t) => NominalDiffTime -> (Maybe (UTCTime, a) -> t a) -> m (Cached t a)
+ Control.Concurrent.CachedIO: cachedIO' :: (MonadIO m, MonadIO t, MonadMask t) => NominalDiffTime -> (Maybe (UTCTime, a) -> t a) -> m (Cached t a)
- Control.Concurrent.CachedIO: cachedIOFromConfig :: forall m (t :: Type -> Type) a. (MonadIO m, MonadIO t, MonadCatch t) => Config t a -> m (Cached t a)
+ Control.Concurrent.CachedIO: cachedIOFromConfig :: forall m (t :: Type -> Type) a. (MonadIO m, MonadIO t, MonadMask t) => Config t a -> m (Cached t a)
- Control.Concurrent.CachedIO: cachedIOWith :: (MonadIO m, MonadIO t, MonadCatch t) => (UTCTime -> UTCTime -> Bool) -> t a -> m (Cached t a)
+ Control.Concurrent.CachedIO: cachedIOWith :: (MonadIO m, MonadIO t, MonadMask t) => (UTCTime -> UTCTime -> Bool) -> t a -> m (Cached t a)
- Control.Concurrent.CachedIO: cachedIOWith' :: (MonadIO m, MonadIO t, MonadCatch t) => (UTCTime -> UTCTime -> Bool) -> (Maybe (UTCTime, a) -> t a) -> m (Cached t a)
+ Control.Concurrent.CachedIO: cachedIOWith' :: (MonadIO m, MonadIO t, MonadMask t) => (UTCTime -> UTCTime -> Bool) -> (Maybe (UTCTime, a) -> t a) -> m (Cached t a)
- Control.Concurrent.CachedIO: cachedSTM :: (MonadIO m, MonadCatch m) => NominalDiffTime -> m a -> STM (Cached m a)
+ Control.Concurrent.CachedIO: cachedSTM :: (MonadIO m, MonadMask m) => NominalDiffTime -> m a -> STM (Cached m a)
- Control.Concurrent.CachedIO: cachedSTM' :: (MonadIO m, MonadCatch m) => NominalDiffTime -> (Maybe (UTCTime, a) -> m a) -> STM (Cached m a)
+ Control.Concurrent.CachedIO: cachedSTM' :: (MonadIO m, MonadMask m) => NominalDiffTime -> (Maybe (UTCTime, a) -> m a) -> STM (Cached m a)
- Control.Concurrent.CachedIO: cachedSTMFromConfig :: forall (m :: Type -> Type) a. (MonadIO m, MonadCatch m) => Config m a -> STM (Cached m a)
+ Control.Concurrent.CachedIO: cachedSTMFromConfig :: forall (m :: Type -> Type) a. (MonadIO m, MonadMask m) => Config m a -> STM (Cached m a)
- Control.Concurrent.CachedIO: cachedSTMWith :: (MonadIO m, MonadCatch m) => (UTCTime -> UTCTime -> Bool) -> m a -> STM (Cached m a)
+ Control.Concurrent.CachedIO: cachedSTMWith :: (MonadIO m, MonadMask m) => (UTCTime -> UTCTime -> Bool) -> m a -> STM (Cached m a)
- Control.Concurrent.CachedIO: cachedSTMWith' :: (MonadIO m, MonadCatch m) => (UTCTime -> UTCTime -> Bool) -> (Maybe (UTCTime, a) -> m a) -> STM (Cached m a)
+ Control.Concurrent.CachedIO: cachedSTMWith' :: (MonadIO m, MonadMask m) => (UTCTime -> UTCTime -> Bool) -> (Maybe (UTCTime, a) -> m a) -> STM (Cached m a)
Files
- CHANGELOG.md +13/−0
- cached-io.cabal +4/−3
- src/Control/Concurrent/CachedIO.hs +12/−12
- test/test-cachedIO.hs +10/−1
CHANGELOG.md view
@@ -1,8 +1,21 @@ # Revision history for cached-io +## 1.4.0.0++- **Breaking** All `cached...` functions now require `MonadMask` for the monad+ running the refresh action. This was required to support the following bug+ fix.+- Fix a subtle bug: if you used a monad with explicit early-exit semantics like+ `MaybeT m` or `ExceptT e m`, and your cached action uses this to exit early, the+ cache would get stuck in `Initializing` state and subsequent requests for the+ cached data would hang forever waiting for initialisation to complete.+ ## 1.3.2.0 - Add `Functor` instance for `Cached`.+- Add `cachedIOFromConfig` and `cachedSTMFromConfig` (and a supporting `Config`+ type), which let the freshness check inspect the cached value in addition to+ its timestamp. ## 1.3.1.0
cached-io.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.2 name: cached-io-version: 1.3.2.0+version: 1.4.0.0 synopsis: A simple library to cache IO actions description: Provides functions that convert an IO action into a cached one by storing the@@ -68,5 +68,6 @@ ghc-options: -Werror build-depends:- , tasty ^>=1.5- , tasty-hunit ^>=0.10.0.3+ , tasty ^>=1.5+ , tasty-hunit ^>=0.10.0.3+ , transformers >=0.5.6 && <0.7
src/Control/Concurrent/CachedIO.hs view
@@ -48,7 +48,7 @@ writeTVar, ) import Control.Monad (join)-import Control.Monad.Catch (MonadCatch, onException)+import Control.Monad.Catch (MonadMask, onError) import Control.Monad.IO.Class (MonadIO, liftIO) import Data.Time.Clock (NominalDiffTime, UTCTime, addUTCTime, getCurrentTime) @@ -79,7 +79,7 @@ -- either get the cached value or refresh, if the cache is older than 'interval' -- seconds. cachedIO ::- (MonadIO m, MonadIO t, MonadCatch t) =>+ (MonadIO m, MonadIO t, MonadMask t) => -- | Number of seconds before refreshing cache NominalDiffTime -> -- | IO action to cache@@ -94,7 +94,7 @@ -- either get the cached value or refresh, if the cache is older than 'interval' -- seconds. cachedIO' ::- (MonadIO m, MonadIO t, MonadCatch t) =>+ (MonadIO m, MonadIO t, MonadMask t) => -- | Number of seconds before refreshing cache NominalDiffTime -> -- | action to cache. The stale value and its refresh date@@ -108,7 +108,7 @@ -- The outer IO is responsible for setting up the cache. Use the inner one to -- either get the cached value or refresh cachedIOWith ::- (MonadIO m, MonadIO t, MonadCatch t) =>+ (MonadIO m, MonadIO t, MonadMask t) => -- | Test function: -- If @isCacheStillFresh lastUpdated now@ returns 'True', -- the cache is considered still fresh and returns the cached IO action@@ -123,7 +123,7 @@ -- The outer IO is responsible for setting up the cache. Use the inner one to -- either get the cached value or refresh cachedIOWith' ::- (MonadIO m, MonadIO t, MonadCatch t) =>+ (MonadIO m, MonadIO t, MonadMask t) => -- | Test function: -- If @isCacheStillFresh lastUpdated now@ returns 'True' -- the cache is considered still fresh and returns the cached IO action@@ -143,7 +143,7 @@ -- -- The outer IO is responsible for setting up the cache. Use the inner one to -- either get the cached value or refresh-cachedIOFromConfig :: (MonadIO m, MonadIO t, MonadCatch t) => Config t a -> m (Cached t a)+cachedIOFromConfig :: (MonadIO m, MonadIO t, MonadMask t) => Config t a -> m (Cached t a) cachedIOFromConfig = liftIO . atomically . cachedSTMFromConfig -- $stm@@ -154,7 +154,7 @@ -- | Set up a cached IO action in a transaction; producing a version of this IO -- action that is cached for 'interval' seconds. The cache begins uninitialized. cachedSTM ::- (MonadIO m, MonadCatch m) =>+ (MonadIO m, MonadMask m) => -- | Number of seconds before refreshing cache NominalDiffTime -> -- | IO action to cache@@ -165,7 +165,7 @@ -- | Set up a cached IO action in a transaction; producing a version of this IO -- action that is cached for 'interval' seconds. The cache begins uninitialized. cachedSTM' ::- (MonadIO m, MonadCatch m) =>+ (MonadIO m, MonadMask m) => -- | Number of seconds before refreshing cache NominalDiffTime -> -- | action to cache. The stale value and its refresh date@@ -176,7 +176,7 @@ -- | Set up a cached IO action in a transaction; The cache begins uninitialized. cachedSTMWith ::- (MonadIO m, MonadCatch m) =>+ (MonadIO m, MonadMask m) => -- | Test function: -- If @isCacheStillFresh lastUpdated now@ returns 'True', -- the cache is considered still fresh and returns the cached IO action@@ -188,7 +188,7 @@ -- | Set up a cached IO action in a transaction; the cache begins uninitialized. cachedSTMWith' ::- (MonadIO m, MonadCatch m) =>+ (MonadIO m, MonadMask m) => -- | Test function: -- If @isCacheStillFresh lastUpdated now@ returns 'True' -- the cache is considered still fresh and returns the cached IO action@@ -206,7 +206,7 @@ -- | Set up a cached IO action in a transaction; the cache begins uninitialized. cachedSTMFromConfig ::- (MonadIO m, MonadCatch m) =>+ (MonadIO m, MonadMask m) => Config m a -> STM (Cached m a) cachedSTMFromConfig config = do@@ -242,7 +242,7 @@ _ -> Nothing newValue <- refreshAction config previous- `onException` liftIO (atomically (writeTVar cachedT previousState))+ `onError` liftIO (atomically (writeTVar cachedT previousState)) liftIO $ do now <- getCurrentTime atomically (writeTVar cachedT (Fresh now newValue))
test/test-cachedIO.hs view
@@ -2,6 +2,7 @@ import Control.Concurrent (forkIO, threadDelay) import Control.Concurrent.CachedIO (Cached (..), cachedIO)+import Control.Monad.Trans.Except (ExceptT (..), runExceptT, throwE) import Data.Functor (void) import Data.IORef (IORef, atomicModifyIORef', newIORef, readIORef) import Test.Tasty (TestTree, defaultMain, testGroup)@@ -29,7 +30,15 @@ void . forkIO $ void action void action count <- readIORef ref- count @?= 1+ count @?= 1,+ testCase "Cache resets when ExceptT action fails via throwError" $ do+ Cached action <- cachedIO (5 * 60) (throwE () :: ExceptT () IO ())+ result1 <- runExceptT action+ result1 @?= Left ()+ -- If we do not handle the throwE correctly this will hang because the+ -- cache is waiting to finish initialising.+ result2 <- runExceptT action+ result2 @?= Left () ] increment :: IORef Int -> IO Int