shibuya-kiroku-adapter-0.7.0.0: internal/Shibuya/Adapter/Kiroku/Internal.hs
module Shibuya.Adapter.Kiroku.Internal
( acquireAllAndTransfer,
)
where
import Control.Exception (SomeException)
import Data.Int (Int32)
import Effectful (Eff, IOE, (:>))
import Effectful.Exception qualified as Exception
-- | Acquire a fixed set of resources and atomically transfer their ownership.
--
-- The acquisition itself is restored to the caller's masking state because it may
-- perform interruptible startup work. As soon as an acquisition returns, the new
-- resource is added to the masked ownership ledger before the next interruptible
-- operation can run. If acquisition or transfer fails, every owned resource is
-- given one release attempt in reverse acquisition order and the original
-- exception is rethrown even when a release also fails.
--
-- The post-acquire hook runs while the new resource is already in the ledger. It
-- exists so tests can stop at the exact cancellation boundary without adding a
-- hook to the public adapter API; production callers pass a no-op.
acquireAllAndTransfer ::
(IOE :> es) =>
Int32 ->
(Int32 -> Eff es resource) ->
(resource -> Eff es ()) ->
(Int32 -> resource -> Eff es ()) ->
([resource] -> Eff es result) ->
Eff es result
acquireAllAndTransfer count acquire release afterAcquire transfer =
Exception.mask $ \restore -> go restore [] 0
where
go restore owned member
| member >= count = do
outcome <- tryAny (transfer (reverse owned))
either (`cleanupAndRethrow` owned) pure outcome
| otherwise = do
acquisition <- tryAny (restore (acquire member))
case acquisition of
Left primary -> cleanupAndRethrow primary owned
Right resource -> do
let owned' = resource : owned
handoff <- tryAny (afterAcquire member resource)
case handoff of
Left primary -> cleanupAndRethrow primary owned'
Right () -> go restore owned' (member + 1)
cleanupAndRethrow primary owned = do
mapM_ releaseIgnoringFailure owned
Exception.throwIO primary
releaseIgnoringFailure resource = do
_ <- tryAny (release resource)
pure ()
tryAny :: Eff es a -> Eff es (Either SomeException a)
tryAny = Exception.try