fpco / unliftio

The MonadUnliftIO typeclass for unlifting monads to IO
Other
151 stars 51 forks source link

catchSyncOrAsync does not catch asynchronous exceptions #81

Closed kazu-yamamoto closed 3 years ago

kazu-yamamoto commented 3 years ago

80 introduced catchSyncOrAsync and handleSyncOrAsync. It works well if a handler tries to catch SomeExeption. However, if the type of an asynchronous exception is specified, it does not catch it. This is probably because they do not take care of the async wrapper.

import Control.Concurrent hiding (throwTo)
import UnliftIO.Exception
import UnliftIO.Async

data Kill = Kill deriving (Show)

instance Exception Kill

main :: IO ()
main = do
    mvar <- newEmptyMVar
    concurrently_ (foo mvar) (bar mvar)

foo :: MVar ThreadId -> IO ()
foo mvar = handleSyncOrAsync handler $ do
    tid <- myThreadId
    putMVar mvar tid
    threadDelay 5000000
  where
    handler Kill = putStrLn "foo Killed"
--    handler (SomeException e) = putStrLn $ "foo " ++ show e

bar :: MVar ThreadId -> IO ()
bar mvar = do
    tid <- takeMVar mvar
    throwTo tid Kill
snoyberg commented 3 years ago

The problem here is that Kill is not properly defined as an async exception, and therefore is incurring the wrapper type. The best way to solve this is to provide an Exception instance of Kill that makes Kill a child of SomeAsyncException.

kazu-yamamoto commented 3 years ago

Adding the following works well:

  fromException = asyncExceptionFromException
  toException = asyncExceptionToException

Thanks!

kazu-yamamoto commented 3 years ago

@snoyberg It might be worth re-exporting asyncExceptionFromException and asyncExceptionToException from UnliftIO.Exception.

snoyberg commented 3 years ago

Done!

kazu-yamamoto commented 3 years ago

Lovely! Thanks.