packages feed

shibuya-kiroku-adapter-0.5.1.3: 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