packages feed

resource-pool-0.5.0.1: src/Data/Pool/Introspection.hs

-- | A variant of "Data.Pool" with introspection capabilities.
module Data.Pool.Introspection
  ( -- * Pool
    Pool
  , LocalPool
  , newPool

    -- ** Configuration
  , PoolConfig
  , defaultPoolConfig
  , setNumStripes
  , setPoolLabel

    -- * Resource management
  , Resource (..)
  , Acquisition (..)
  , withResource
  , takeResource
  , tryWithResource
  , tryTakeResource
  , putResource
  , destroyResource
  , destroyAllResources
  ) where

import Control.Concurrent.STM
import Control.Exception
import Control.Monad
import Data.Text qualified as T
import GHC.Clock (getMonotonicTime)
import GHC.Generics (Generic)

import Data.Pool.Internal

-- | A resource taken from the pool along with additional information.
data Resource a = Resource
  { resource :: a
  , poolLabel :: !T.Text
  , stripeNumber :: !Int
  , availableResources :: !Int
  , acquisition :: !Acquisition
  , acquisitionTime :: !Double
  , creationTime :: !(Maybe Double)
  }
  deriving stock (Eq, Generic, Show)

-- | Describes how a resource was acquired from the pool.
data Acquisition
  = -- | A resource was taken from the pool immediately.
    Immediate
  | -- | The thread had to wait until a resource was released.
    Delayed
  deriving stock (Eq, Generic, Show)

-- | 'Data.Pool.withResource' with introspection capabilities.
withResource :: Pool a -> (Resource a -> IO r) -> IO r
withResource pool act = mask $ \unmask -> do
  (res, localPool) <- takeResource pool
  r <- unmask (act res) `onException` destroyResource pool localPool (resource res)
  putResource localPool (resource res)
  pure r

-- | 'Data.Pool.takeResource' with introspection capabilities.
takeResource :: Pool a -> IO (Resource a, LocalPool a)
takeResource pool = mask_ $ do
  t1 <- getMonotonicTime
  lp <- getLocalPool (localPools pool)
  join . atomically $ do
    stripe <- readTVar (stripeVar lp)
    if available stripe == 0
      then do
        q <- newEmptyTMVar
        writeTVar (stripeVar lp) $! stripe {queueR = Queue q (queueR stripe)}
        pure
          $ waitForResource (stripeVar lp) q >>= \case
            Just a -> do
              t2 <- getMonotonicTime
              let res =
                    Resource
                      { resource = a
                      , poolLabel = pcLabel $ poolConfig pool
                      , stripeNumber = stripeId lp
                      , availableResources = 0
                      , acquisition = Delayed
                      , acquisitionTime = t2 - t1
                      , creationTime = Nothing
                      }
              pure (res, lp)
            Nothing -> do
              t2 <- getMonotonicTime
              a <- createResource (poolConfig pool) `onException` restoreSize (stripeVar lp)
              t3 <- getMonotonicTime
              let res =
                    Resource
                      { resource = a
                      , poolLabel = pcLabel $ poolConfig pool
                      , stripeNumber = stripeId lp
                      , availableResources = 0
                      , acquisition = Delayed
                      , acquisitionTime = t2 - t1
                      , creationTime = Just $! t3 - t2
                      }
              pure (res, lp)
      else takeAvailableResource pool t1 lp stripe

-- | A variant of 'withResource' that doesn't execute the action and returns
-- '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
      r <- unmask (act res) `onException` destroyResource pool localPool (resource res)
      putResource localPool (resource res)
      pure (Just r)
    Nothing -> pure Nothing

-- | A variant of 'takeResource' that returns 'Nothing' instead of blocking if
-- the local pool is exhausted.
tryTakeResource :: Pool a -> IO (Maybe (Resource a, LocalPool a))
tryTakeResource pool = mask_ $ do
  t1 <- getMonotonicTime
  lp <- getLocalPool (localPools pool)
  join . atomically $ do
    stripe <- readTVar (stripeVar lp)
    if available stripe == 0
      then pure $ pure Nothing
      else fmap Just <$> takeAvailableResource pool t1 lp stripe

----------------------------------------
-- Helpers

takeAvailableResource
  :: Pool a
  -> Double
  -> LocalPool a
  -> Stripe a
  -> STM (IO (Resource a, LocalPool a))
takeAvailableResource pool t1 lp stripe = case cache stripe of
  [] -> do
    let newAvailable = available stripe - 1
    writeTVar (stripeVar lp) $! stripe {available = newAvailable}
    pure $ do
      t2 <- getMonotonicTime
      a <- createResource (poolConfig pool) `onException` restoreSize (stripeVar lp)
      t3 <- getMonotonicTime
      let res =
            Resource
              { resource = a
              , poolLabel = pcLabel $ poolConfig pool
              , stripeNumber = stripeId lp
              , availableResources = newAvailable
              , acquisition = Immediate
              , acquisitionTime = t2 - t1
              , creationTime = Just $! t3 - t2
              }
      pure (res, lp)
  Entry a _ : as -> do
    let newAvailable = available stripe - 1
    writeTVar (stripeVar lp) $! stripe {available = newAvailable, cache = as}
    pure $ do
      t2 <- getMonotonicTime
      let res =
            Resource
              { resource = a
              , poolLabel = pcLabel $ poolConfig pool
              , stripeNumber = stripeId lp
              , availableResources = newAvailable
              , acquisition = Immediate
              , acquisitionTime = t2 - t1
              , creationTime = Nothing
              }
      pure (res, lp)