2
votes

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?

1

1 Answers

1
votes

Notice that Control.Exception.Safe.throwTo wraps synchronous exceptions into AsyncExceptionWrapper, and IOError is synchronous. (I have no idea why this wrapping is necessary, you should never throw synchronous exceptions asynchronously anyway.)

To make your code work, you should either catch AsyncExceptionWrapper or use Control.Exception.throwTo. But actually I don't compitely understand what you are trying to do, most likely you are overcomplicating things.