packages feed

resource-pool 0.3.1.0 → 0.4.0.0

raw patch · 5 files changed

+94/−31 lines, 5 filesdep +hashable

Dependencies added: hashable

Files

CHANGELOG.md view
@@ -1,3 +1,10 @@+# resource-pool-0.4.0.0 (2023-01-16)+* Require `poolMaxResources` to be not smaller than the number of stripes.+* Add support for setting the number of stripes.+* Hide the constructor of `PoolConfig` from the public API and provide+  `defaultPoolConfig` so that future additions to `PoolConfig` don't require+  major version bumps.+ # resource-pool-0.3.1.0 (2022-06-15) * Add `tryWithResource` and `tryTakeResource`. 
resource-pool.cabal view
@@ -1,7 +1,7 @@ cabal-version:       2.4 build-type:          Simple name:                resource-pool-version:             0.3.1.0+version:             0.4.0.0 license:             BSD-3-Clause license-file:        LICENSE category:            Data, Database, Network@@ -14,7 +14,8 @@              flexibly-sized collections of resources such as database              connections. -tested-with: GHC ==8.4.4 || ==8.6.5 || ==8.8.4 || ==8.10.7 || ==9.0.2 || ==9.2.3+tested-with: GHC ==8.4.4 || ==8.6.5 || ==8.8.4 || ==8.10.7 || ==9.0.2 || ==9.2.5+              || ==9.4.3  extra-doc-files:   CHANGELOG.md@@ -33,6 +34,7 @@                    Data.Pool.Introspection    build-depends: base >= 4.11 && < 5+               , hashable >= 1.1.0.0                , primitive >= 0.7                , time 
src/Data/Pool.hs view
@@ -2,11 +2,15 @@ -- collections of resources such as database connections. module Data.Pool   ( -- * Pool-    PoolConfig(..)-  , Pool+    Pool   , LocalPool   , newPool +    -- ** Configuration+  , PoolConfig+  , defaultPoolConfig+  , setNumStripes+     -- * Resource management   , withResource   , takeResource@@ -72,7 +76,7 @@     else takeAvailableResource pool lp stripe  -- | A variant of 'withResource' that doesn't execute the action and returns--- 'Nothing' instead of blocking if the capability-local pool is exhausted.+-- 'Nothing' instead of blocking if the local pool is exhausted. tryWithResource :: Pool a -> (a -> IO r) -> IO (Maybe r) tryWithResource pool act = mask $ \unmask -> tryTakeResource pool >>= \case   Just (res, localPool) -> do@@ -82,7 +86,7 @@   Nothing -> pure Nothing  -- | A variant of 'takeResource' that returns 'Nothing' instead of blocking if--- the capability-local pool is exhausted.+-- the local pool is exhausted. tryTakeResource :: Pool a -> IO (Maybe (a, LocalPool a)) tryTakeResource pool = mask_ $ do   lp <- getLocalPool (localPools pool)@@ -103,6 +107,7 @@   , freeResource     = free   , poolCacheTTL     = realToFrac idleTime   , poolMaxResources = numStripes * maxResources+  , poolNumStripes   = Just numStripes   }  ----------------------------------------
src/Data/Pool/Internal.hs view
@@ -8,23 +8,20 @@ import Control.Concurrent import Control.Exception import Control.Monad+import Data.Hashable (hash) import Data.IORef import Data.Primitive.SmallArray import GHC.Clock import qualified Data.List as L  -- | Striped resource pool based on "Control.Concurrent.QSem".------ The number of stripes is arranged to be equal to the number of capabilities--- so that they never compete over access to the same stripe. This results in a--- very good performance in a multi-threaded environment. data Pool a = Pool   { poolConfig   :: !(PoolConfig a)   , localPools   :: !(SmallArray (LocalPool a))   , reaperRef    :: !(IORef ())   } --- | A single, capability-local pool.+-- | A single, local pool. data LocalPool a = LocalPool   { stripeId   :: !Int   , stripeVar  :: !(MVar (Stripe a))@@ -53,30 +50,60 @@  -- | Configuration of a 'Pool'. data PoolConfig a = PoolConfig-  { createResource :: !(IO a)-    -- ^ The action that creates a new resource.-  , freeResource :: !(a -> IO ())-    -- ^ The action that destroys an existing resource.-  , poolCacheTTL :: !Double+  { createResource   :: !(IO a)+  , freeResource     :: !(a -> IO ())+  , poolCacheTTL     :: !Double+  , poolMaxResources :: !Int+  , poolNumStripes   :: !(Maybe Int)+  }++-- | Create a 'PoolConfig' with optional parameters having default values.+--+-- For setting optional parameters have a look at:+--+-- - 'setNumStripes'+--+-- @since 0.4.0.0+defaultPoolConfig+  :: IO a+  -- ^ The action that creates a new resource.+  -> (a -> IO ())+  -- ^ The action that destroys an existing resource.+  -> Double   -- ^ The amount of seconds for which an unused resource is kept around. The   -- smallest acceptable value is @0.5@.   --   -- /Note:/ the elapsed time before destroying a resource may be a little   -- longer than requested, as the collector thread wakes at 1-second intervals.-  , poolMaxResources :: !Int-  -- ^ The maximum number of resources to keep open across all stripes. The+  -> Int+  -- ^ The maximum number of resources to keep open __across all stripes__. The   -- smallest acceptable value is @1@.   --   -- /Note:/ for each stripe the number of resources is divided by the number of-  -- capabilities and rounded up. Therefore the pool might end up creating up to-  -- @N - 1@ resources more in total than specified, where @N@ is the number of-  -- capabilities.+  -- stripes and rounded up, hence the pool might end up creating up to @N - 1@+  -- resources more in total than specified, where @N@ is the number of stripes.+  -> PoolConfig a+defaultPoolConfig create free cacheTTL maxResources = PoolConfig+  { createResource   = create+  , freeResource     = free+  , poolCacheTTL     = cacheTTL+  , poolMaxResources = maxResources+  , poolNumStripes   = Nothing   } --- | Create a new striped resource pool.+-- | Set the number of stripes in the pool. ----- The number of stripes is equal to the number of capabilities.+-- If set to 'Nothing' (the default value), the pool will create the amount of+-- stripes equal to the number of capabilities. This ensures that threads never+-- compete over access to the same stripe and results in a very good performance+-- in a multi-threaded environment. --+-- @since 0.4.0.0+setNumStripes :: Maybe Int -> PoolConfig a -> PoolConfig a+setNumStripes numStripes pc = pc { poolNumStripes = numStripes }++-- | Create a new striped resource pool.+-- -- /Note:/ although the runtime system will destroy all idle resources when the -- pool is garbage collected, it's recommended to manually call -- 'destroyAllResources' when you're done with the pool so that the resources@@ -87,9 +114,11 @@     error "poolCacheTTL must be at least 0.5"   when (poolMaxResources pc < 1) $ do     error "poolMaxResources must be at least 1"-  numStripes <- getNumCapabilities+  numStripes <- maybe getNumCapabilities pure (poolNumStripes pc)   when (numStripes < 1) $ do     error "numStripes must be at least 1"+  when (poolMaxResources pc < numStripes) $ do+    error "poolMaxResources must not be smaller than numStripes"   pools <- fmap (smallArrayFromListN numStripes) . forM [1..numStripes] $ \n -> do     ref <- newIORef ()     stripe <- newMVar Stripe@@ -168,11 +197,27 @@ ---------------------------------------- -- Helpers --- | Get a capability-local pool.+-- | Get a local pool. getLocalPool :: SmallArray (LocalPool a) -> IO (LocalPool a) getLocalPool pools = do-  (cid, _) <- threadCapability =<< myThreadId-  pure $ pools `indexSmallArray` (cid `rem` sizeofSmallArray pools)+  sid <- if stripes == 1+    -- If there is just one stripe, there is no choice.+    then pure 0+    else do+      capabilities <- getNumCapabilities+      -- If the number of stripes is smaller than the number of capabilities and+      -- doesn't divide it, selecting a stripe by a capability the current+      -- thread runs on wouldn't give equal load distribution across all stripes+      -- (e.g. if there are 2 stripes and 3 capabilities, stripe 0 would be used+      -- by capability 0 and 2, while stripe 1 would only be used by capability+      -- 1, a 100% load difference). In such case we select based on the id of a+      -- thread.+      if stripes < capabilities && capabilities `rem` stripes /= 0+        then hash <$> myThreadId+        else fmap fst . threadCapability =<< myThreadId+  pure $ pools `indexSmallArray` (sid `rem` stripes)+  where+    stripes = sizeofSmallArray pools  -- | Wait for the resource to be put into a given 'MVar'. waitForResource :: MVar (Stripe a) -> MVar (Maybe a) -> IO (Maybe a)
src/Data/Pool/Introspection.hs view
@@ -1,11 +1,15 @@ -- | A variant of "Data.Pool" with introspection capabilities. module Data.Pool.Introspection   ( -- * Pool-    PoolConfig(..)-  , Pool+    Pool   , LocalPool   , newPool +  -- ** Configuration+  , PoolConfig+  , defaultPoolConfig+  , setNumStripes+     -- * Resource management   , Resource(..)   , Acquisition(..)@@ -89,7 +93,7 @@     else takeAvailableResource pool t1 lp stripe  -- | A variant of 'withResource' that doesn't execute the action and returns--- 'Nothing' instead of blocking if the capability-local pool is exhausted.+-- 'Nothing' instead of blocking if the local pool is exhausted. tryWithResource :: Pool a -> (Resource a -> IO r) -> IO (Maybe r) tryWithResource pool act = mask $ \unmask -> tryTakeResource pool >>= \case   Just (res, localPool) -> do@@ -99,7 +103,7 @@   Nothing -> pure Nothing  -- | A variant of 'takeResource' that returns 'Nothing' instead of blocking if--- the capability-local pool is exhausted.+-- the local pool is exhausted. tryTakeResource :: Pool a -> IO (Maybe (Resource a, LocalPool a)) tryTakeResource pool = mask_ $ do   t1 <- getMonotonicTime