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
.
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)))