I'm trying to dive into building concurrent and robust code with Haskell, and it was recommended that I use the safe-exceptions and async libraries. However, I'm having a hard time understanding how to handle non-fatal errors thrown within an async
action.
For instance, if there is a simple loop that is checking a network resource every n seconds, it would make sense to stop this using the cancel
function which would cause an AsyncCancelled
exception to be thrown within the seperate thread. Of course, there would also be the possibility that an IOError
would be thrown from within the thread due to the network going down or some other problem. Depending on the type of exception and the data it contains, I would want to control whether the seperate thread ignores the exception, performs some action, stops, or raises the exception in the main thread.
With the safe-exceptions library, the only functions that are able to do this are catchAsync
and others like it, which are labelled as dangerous in the documentation. Other than that, there is waitCatch
in the async library, but the fromException
function always returns Nothing
when I try to extract an IOError
:
{-# LANGUAGE ScopedTypeVariables #-}
import Control.Concurrent.Async
import Control.Concurrent hiding (throwTo)
import Control.Exception.Safe
import Control.Monad
import System.IO
import System.IO.Error hiding (catchIOError)
main = do
hSetBuffering stdin NoBuffering
putStrLn "Press any key to continue..."
a <- async runLoop
async $ hWaitForInput stdin (-1) *>
throwTo (asyncThreadId a) (userError "error")
waitCatch a >>= either handler nothing
where
printThenWait i = putStr (show i ++ " ") *> threadDelay 1000000
runLoop = sequence_ $ printThenWait <$> [1..]
nothing _ = pure ()
handler e
| Just (e' :: IOError) <- fromException e =
putStrLn "It's an IOError!"
| (Nothing :: Maybe IOError) <- fromException e =
putStrLn "We got Nothing!"
I'm a bit confused about the danger of recovering from async exceptions, especially when standard functions such as cancel
cause them to be thrown, and I don't know what the recommended way to handle them would be when using these two libraries. Is this an instance where catchAsync
would be recommended, or is there another way to handle these types of situations that I haven't discovered?