How to write a proper forking bracket function

Some feedback from Yuras Shumovich:

  • forkIO never throws, so the onException part wasn’t necessary.
  • Now using forkIOWithUnmask instead of forkIO, because it seems likely that you always want the action to run with async exceptions unmasked, not for the action in the thread to inherit the masking state from the context in which bracketFork was used.

The revised function:

bracketFork open close action =
    mask_ $
      do
        resource <- open

        forkIOWithUnmask $ \unmask ->
            unmask (action resource >> return ())
            `finally`
            close resource

Or, looking at the definition of finally, I think this would be equivalent and slightly more straightforward:

bracketFork open close action =
    mask_ $
      do
        resource <- open

        let a = action resource >> return ()
            b = close resource >> return ()

        forkIOWithUnmask $ \unmask ->
            (unmask a `onException` b) >> b

Edit: Alternatively, this should be the same thing, written more clearly, I think.

{- | Like 'bracket', except the action and the cleanup
     take place in a newly-forked thread. -}
bracketFork ::
       IO resource        -- ^ The first action, runs in the current thread
    -> (resource -> IO a) -- ^ A final action that runs in the forked thread,
                          --   with async exceptions masked
    -> (resource -> IO b) -- ^ Action that runs in the forked thread,
                          --   with async exceptions unmasked
    -> IO ThreadId

bracketFork open close action =
    mask_ $
      do
        resource <- open
        action resource `forkUnmaskedFinally` close resource

{- | Like 'forkFinally', except the action always runs
     with async exceptions unmasked. -}
forkUnmaskedFinally ::
       IO a -- ^ Action that runs in the forked thread,
            --   with async exceptions unmasked
    -> IO b -- ^ A final action that runs in the forked thread,
            --   with async exceptions masked
    -> IO ThreadId

forkUnmaskedFinally action close =
    mask_ $ forkIOWithUnmask $ \unmask ->
      do
        unmask action `onException` close
        close
        return ()