packages feed

wireform-core-0.2.0.0: src/Wireform/Ring/Pool.hs

{- | A pool of pre-allocated 'MagicRing' buffers for reuse.

Allocating a magic ring costs @memfd_create@ + @ftruncate@ + 3×
@mmap@ + @memset@ per ring. This pool keeps a bounded free list of
rings so connection churn doesn't repeat that syscall work.

Rings are stored as raw @(Ptr Word8, Int)@ pairs — no IORefs, no
per-ring mutable state. The transport-layer cursor state (head,
tail, open\/closed) is always fresh per connection; only the
underlying mmap'd memory is recycled.
-}
module Wireform.Ring.Pool (
  RingPool,
  RingPoolConfig (..),
  defaultRingPoolConfig,
  newRingPool,
  closeRingPool,
  acquireRing,
  releaseRing,
) where

import Control.Concurrent.MVar
import Control.Exception (SomeException, mask_, try)
import Control.Monad (forM_)
import GHC.Exts (RealWorld)
import Wireform.Ring.Internal (MagicRing (..), destroyMagicRing, newMagicRing)


data RingPoolConfig = RingPoolConfig
  { ringPoolMaxIdle :: !Int
  }


defaultRingPoolConfig :: RingPoolConfig
defaultRingPoolConfig =
  RingPoolConfig
    { ringPoolMaxIdle = 64
    }


{- | The pool is just a bounded free list protected by an MVar.
All rings in the list have the same allocated size (the first
allocation's rounded-up size sets the class).
-}
data RingPool = RingPool
  { rpMaxIdle :: !Int
  , rpFree :: !(MVar [MagicRing RealWorld])
  }


newRingPool :: RingPoolConfig -> IO RingPool
newRingPool cfg = do
  free <- newMVar []
  pure
    RingPool
      { rpMaxIdle = ringPoolMaxIdle cfg
      , rpFree = free
      }


{- | Destroy all idle rings. The pool remains usable (acquire
allocates fresh, release destroys immediately).
-}
closeRingPool :: RingPool -> IO ()
closeRingPool pool = mask_ $ do
  rings <- swapMVar (rpFree pool) []
  forM_ rings $ \r ->
    () <$ try @SomeException (destroyMagicRing r)


-- | Take a ring from the pool or allocate a fresh one.
acquireRing :: RingPool -> Int -> IO (MagicRing s)
acquireRing pool requested = mask_ $ do
  rings <- takeMVar (rpFree pool)
  case rings of
    (r : rs) | mrSize r >= requested -> do
      putMVar (rpFree pool) rs
      pure (coerceRing r)
    _ -> do
      putMVar (rpFree pool) rings
      newMagicRing requested


{- | Return a ring to the pool. Destroyed immediately if the pool
is full.
-}
releaseRing :: RingPool -> MagicRing s -> IO ()
releaseRing pool ring = mask_ $ do
  rings <- takeMVar (rpFree pool)
  if length rings >= rpMaxIdle pool
    then do
      putMVar (rpFree pool) rings
      destroyMagicRing ring
    else
      putMVar (rpFree pool) (coerceRing ring : rings)


-- MagicRing's phantom s is nominal, but the pool stores them as
-- RealWorld and hands them out polymorphically. The underlying
-- data is just (Ptr Word8, Int) — the coercion is safe.
coerceRing :: MagicRing s -> MagicRing s'
coerceRing (MagicRing p n) = MagicRing p n