packages feed

sockets-and-pipes-0.1: src/SocketsAndPipes/Serve/ForkBracket.hs

{- |

    The 'forkBracket' function ensures that if the setup
    action completes, then no matter what else happens,
    exactly one of either 'onForkFail' or 'onThreadEnd' will run.

-}
module SocketsAndPipes.Serve.ForkBracket
    ( forkBracket, Cleanup (..), ThreadId ) where

import Control.Concurrent     ( forkFinally, ThreadId )
import Control.Exception.Safe ( bracketOnError )

forkBracket ::
    IO resource           -- ^ Setup:
                          --      How to open some resource.
    -> Cleanup resource   -- ^ Cleanup:
                          --      How to close the resource.
    -> (resource -> IO x) -- ^ Action that uses the resource.
                          --      This will run in a new thread.
    -> IO ThreadId
forkBracket setup Cleanup{onForkFail, onThreadEnd} action =
    bracketOnError setup onForkFail $ \resource ->
        action resource `forkFinally` \_result ->
            onThreadEnd resource >> return ()

{- |
    A strategy for closing a resource that is opened
    by a main event loop and used in a forked thread.

    It consists of handlers for two possibilities:

      * 'onThreadEnd' runs after the thread has completed.
      * 'onForkFail' runs if the thread failed to start.
-}
data Cleanup resource
  where
    Cleanup ::
        { onForkFail :: resource -> IO x
            {- ^ Cleanup if forking fails.

                This action runs in the original thread.

                It only runs if there was an exception that
                prevented a new thread from starting.
            -}

        , onThreadEnd :: resource -> IO y
            {- ^ Cleanup after the thread terminates.

                This action runs in the forked thread.

                If the thread actually started, this cleanup
                action runs regardless of whether the thread
                terminates normally or with an exception.
            -}
        }
        -> Cleanup resource