packages feed

simpoole 0.1.0 → 0.2.0

raw patch · 5 files changed

+106/−36 lines, 5 files

Files

ChangeLog.md view
@@ -1,6 +1,11 @@ # simpoole -## 0.1.0 (Unreleased)+## 0.2.0++* Make idle timeout optional+* Overhaul newPool function to be able to produce limited and unlimited pools alike++## 0.1.0  * Expose helper functions: acquireResource, returnResource, destroyResource * Track number of idle resources as part of metrics
lib/Simpoole.hs view
@@ -9,7 +9,6 @@ module Simpoole   ( Pool   , mapPool-  , newUnlimitedPool   , newPool   , withResource   , acquireResource@@ -30,10 +29,12 @@ import           Control.Monad (forever, unless, void) import qualified Control.Monad.Catch as Catch import           Control.Monad.IO.Class (MonadIO (liftIO))+import           Data.Coerce (coerce) import           Data.Foldable (for_) import qualified Data.Sequence as Seq import qualified Data.Time as Time import           Numeric.Natural (Natural)+import           Simpoole.Internal (FailToIO (..), failToIO)  -- | Strategy to use when returning resources to the pool --@@ -41,17 +42,22 @@ data ReturnPolicy   = ReturnToFront   -- ^ Return resources to the front. Resources that have been used recently are more likely to be-  -- reused again quicker. This strategy is good if you want to scale down the pool more quickly in-  -- case resources are not needed.+  -- reused again quicker.   --+  -- This strategy is good if you want to scale down the pool more quickly in case resources are not+  -- needed.+  --   -- @since 0.1.0   | ReturnToBack   -- ^ Return resources to the back. Resources that have been used recently are less likely to be-  -- used again quicker. Use this strategy if you want to keep more resources in the pool fresh, or-  -- when maintaining the pool in order to be ready for burst workloads.-  -- This strategy can lead to no resources ever been freed when all resources are used within the-  -- idle timeout.+  -- used again quicker.   --+  -- Use this strategy if you want to keep more resources in the pool fresh, or when maintaining the+  -- pool in order to be ready for burst workloads.+  --+  -- Note: This strategy can lead to no resources ever being destroyed when all resources are+  -- continuously used within the idle timeout.+  --   -- @since 0.1.0   | ReturnToMiddle   -- ^ Return resources to the middle. This offers a middleground between 'ReturnToFront' and@@ -76,21 +82,33 @@ -- | Lets you configure certain behaviours of the pool -- -- @since 0.1.0-data Settings = PoolSettings-  { settings_idleTimeout :: Time.NominalDiffTime+data Settings = Settings -- ^ @since 0.2.0+  { settings_idleTimeout :: Maybe Time.NominalDiffTime   -- ^ Maximum idle time after which a resource is destroyed   --+  -- Setting it to @Nothing@ means that nothing will ever be destroyed based on idle time.+  --   -- @since 0.1.0   , settings_returnPolicy :: ReturnPolicy+  -- ^ Read documentation on 'ReturnPolicy' for details.+  --+  -- @since 0.1.0+  , settings_maxLiveLimit :: Maybe Int+  -- ^ Maximum number of resources that may live at the same time.+  --+  -- @Nothing@ means unlimited.+  --+  -- @since 0.2.0   }  -- | Default pool settings -- -- @since 0.1.0 defaultSettings :: Settings-defaultSettings = PoolSettings-  { settings_idleTimeout = 60 -- 60 seconds+defaultSettings = Settings+  { settings_idleTimeout = Just 60 -- 60 seconds   , settings_returnPolicy = ReturnToMiddle+  , settings_maxLiveLimit = Nothing   }  -- | Pool of resources@@ -128,8 +146,6 @@     -- ^ The resource item  -- | Create a new pool that has no limit on how many resources it may create and hold.------ @since 0.1.0 newUnlimitedPool   :: (Concurrent.MonadConc m, MonadIO m)   => m a@@ -189,21 +205,20 @@         , ()         ) -  _reaperThread <- Async.asyncWithUnmaskN "reaperThread" $ \unmask -> unmask $ forever $ do-    now <- liftIO Time.getCurrentTime+  for_ (settings_idleTimeout settings) $ \idleTimeout -> void $+    Async.asyncWithUnmaskN "reaperThread" $ \unmask -> unmask $ forever $ do+      now <- liftIO Time.getCurrentTime -    let-      isStillGood (Resource lastUse _) =-        Time.diffUTCTime now lastUse <= settings_idleTimeout settings+      let isStillGood (Resource lastUse _) = Time.diffUTCTime now lastUse <= idleTimeout -    oldResource <- Concurrent.atomicModifyIORef' leftOversRef (Seq.partition isStillGood)+      oldResource <- Concurrent.atomicModifyIORef' leftOversRef (Seq.partition isStillGood) -    unless (null oldResource) $ void $-      Async.asyncN "destructionThread" $-        for_ oldResource $ \(Resource _ value) ->-          Catch.try @_ @Catch.SomeException $ wrappedDestroy value+      unless (null oldResource) $ void $+        Async.asyncN "destructionThread" $+          for_ oldResource $ \(Resource _ value) ->+            Catch.try @_ @Catch.SomeException $ wrappedDestroy value -    Concurrent.threadDelay 1_000_000+      Concurrent.threadDelay 1_000_000    pure Pool     { pool_acquire = acquireResource@@ -215,9 +230,7 @@ -- | Similar to 'newUnlimitedPool' but allows you to limit the number of resources that will exist -- at the same time. When all resources are currently in use, further resource acquisition will -- block until one is no longer in use.------ @since 0.1.0-newPool+newLimitedPool   :: (Concurrent.MonadConc m, MonadIO m, MonadFail m)   => m a   -- ^ Resource creation@@ -228,7 +241,7 @@   -> Settings   -- ^ Pool settings   -> m (Pool m a)-newPool create destroy maxElems settings = do+newLimitedPool create destroy maxElems settings = do   basePool <- newUnlimitedPool create destroy settings   maxElemBarrier <- Concurrent.newQSem maxElems @@ -248,6 +261,26 @@     , pool_destroy = giveBackResource pool_destroy     , pool_metrics = pool_metrics basePool     }++-- | Create a new pool.+--+-- @since 0.2.0+newPool+  :: (Concurrent.MonadConc m, MonadIO m)+  => m a+  -- ^ Resource creation+  -> (a -> m ())+  -- ^ Resource destruction+  -> Settings+  -- ^ Pool settings+  -> m (Pool m a)+newPool create destroy settings =+  case settings_maxLiveLimit settings of+    Just maxLive -> failToIO $ do -- Translate the MonadFail constraint to MonadIO.+      pool <- newLimitedPool (coerce create) (coerce destroy) maxLive settings+      pure (mapPool coerce pool)++    Nothing -> newUnlimitedPool create destroy settings  -- | Use a resource from the pool. Once the continuation returns, the resource will be returned to -- the pool. If the given continuation throws an error then the acquired resource will be destroyed
+ lib/Simpoole/Internal.hs view
@@ -0,0 +1,31 @@+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++module Simpoole.Internal (FailToIO (..), failToIO) where++import Control.Monad.Catch (MonadCatch, MonadMask, MonadThrow)+import Control.Monad.Conc.Class (MonadConc)+import Control.Monad.IO.Class (MonadIO (liftIO))+import Data.Coerce (coerce)++newtype FailToIO m a = FailToIO (m a)+  deriving newtype+    ( Functor+    , Applicative+    , Monad+    , MonadIO+    , MonadThrow+    , MonadCatch+    , MonadMask+    , MonadConc+    )++instance MonadIO m => MonadFail (FailToIO m) where+  fail = liftIO . fail++  {-# INLINE fail #-}++failToIO :: FailToIO m a -> m a+failToIO = coerce++{-# INLINE failToIO #-}
simpoole.cabal view
@@ -1,6 +1,6 @@ cabal-version:      2.2 name:               simpoole-version:            0.1.0+version:            0.2.0 category:           Data, Resources synopsis:           Simple pool description:        Provides a simple pool implementation.@@ -30,6 +30,7 @@                     time   hs-source-dirs:   lib   exposed-modules:  Simpoole+  other-modules:    Simpoole.Internal  test-suite simpoole-tests   import:           warnings
test/SimpooleSpec.hs view
@@ -11,7 +11,7 @@  spec :: Spec spec = do-  describe "newUnlimitedPool" $ do+  describe "newPool" $ do     it "eventually frees all resources" $ do       counterRef <- Concurrent.newIORefN "counterRef" (0 :: Integer) @@ -22,8 +22,8 @@         destroy _ =           Concurrent.atomicModifyIORef' counterRef $ \count -> (pred count, ()) -      pool <- Pool.newUnlimitedPool create destroy-        Pool.defaultSettings { Pool.settings_idleTimeout = 1 }+      pool <- Pool.newPool create destroy+        Pool.defaultSettings { Pool.settings_idleTimeout = Just 1 }        Async.replicateConcurrently_ 200 $         Pool.withResource pool $ const $ Concurrent.threadDelay 1_000@@ -61,7 +61,7 @@           Concurrent.atomicModifyIORef' destroyedRef $ \count -> (count + 1, ())           Concurrent.atomicModifyIORef' counterRef $ \count -> (count - 1, ()) -      pool <- Pool.newUnlimitedPool create destroy Pool.defaultSettings+      pool <- Pool.newPool create destroy Pool.defaultSettings        Async.replicateConcurrently_ 200 $ do         Pool.withResource pool $ const $ Concurrent.threadDelay 1_000@@ -81,7 +81,6 @@       maxLive <- Concurrent.readIORef maxRef       fromIntegral (Pool.metrics_maxLiveResources metrics) `shouldBe` maxLive -  describe "newPool" $ do     it "never allocates more than allowed" $ do       counterRef <- Concurrent.newIORefN "counterRef" (0 :: Integer)       maxRef <- Concurrent.newIORefN "maxRef" 0@@ -94,7 +93,8 @@         destroy _ =           Concurrent.atomicModifyIORef' counterRef $ \count -> (pred count, ()) -      pool <- Pool.newPool create destroy 10 Pool.defaultSettings+      pool <- Pool.newPool create destroy Pool.defaultSettings+        { Pool.settings_maxLiveLimit = Just 10 }        Async.replicateConcurrently_ 200 $ do         Pool.withResource pool $ const $ Concurrent.threadDelay 1_000