Search code examples
exceptionhaskellasynchronousparallel-processinghandler

Haskell Asychronous exceptions


I am using forkIO to spawn worker-threads. When a certain event happens I wish to 'reset' the workers. This should be able to happen an arbitrary number of times. I have my own type of exceptions, which we can call data MyException = MyException. We can imagine that the work is denoted by a IO a value.

My naive idea of doing this, before I read the documentation, was to define something like

    spawnWorkers :: Int -> MVar St -> IO ()
    spawnWorkers num jobs =
      sequence_ $ replicate num $ forkIO $ defHandler $ worker jobs
      where
        defHandler :: IO a -> IO a
        defHandler ioa = ioa `catch` \MyException -> defHandler ioa

However, this does not work. Inside the exception handler asynchronous exceptions are masked. The tail-call to defHandler does not have an associated exception handler.

What is the best way to achieve my desired functionality? I wish to rely only on Control.Concurrent, Control.Concurrent.MVar, and Control.Exception.


Solution

  • Return a value from the exception-handled action that tells whether the exception got thrown or not, then dispatch outside the exception handler. This pattern is wrapped up for you in the try function.

    defHandler :: IO a -> IO a
    defHandler ioa = try ioa >>= \case
        Left MyException -> defHandler ioa
        Right a -> pure a
    

    But try isn't magic; it's just implemented in terms of catch.

    try act = catch (Right <$> act) (pure . Left)
    

    If you want, you can deforest, but I doubt it's worth the cost to the reader's sanity. At the very least leave yourself a note.

    defHandler :: IO a -> IO a
    defHandler ioa = join (catch (pure <$> ioa) (\MyException -> pure (defHandler ioa)))