async exception safety for coprocesses

Tested the forcerestart code path and it works.

The hairy part is, what if an async exception is caught when it's in
restart?

If it's in the part that stops the old process, the old process
is left in the handle. The next attempt to use the CoProcessHandle
will then throw an IO exception, which will result in restart getting
run again. So I think this will work, but have not actually tested it.

The use of withMVarMasked lets it start the new process and fill the
mvar with it, even if there's an async exception at that point.

Note that exceptions are masked while running forcerestart, so
do not need to worry about an async exception being thrown while it's
recovering from an async exception.
This commit is contained in:
Joey Hess 2020-06-09 13:30:35 -04:00
parent a49d300545
commit 7013798df5
No known key found for this signature in database
GPG key ID: DB12DB0FF05F8F38
2 changed files with 47 additions and 20 deletions

View file

@ -1,6 +1,6 @@
{- Simple IO exception handling (and some more)
-
- Copyright 2011-2016 Joey Hess <id@joeyh.name>
- Copyright 2011-2020 Joey Hess <id@joeyh.name>
-
- License: BSD-2-clause
-}
@ -20,6 +20,7 @@ module Utility.Exception (
bracketIO,
catchNonAsync,
tryNonAsync,
catchAsync,
tryWhenExists,
catchIOErrorType,
IOErrorType(..),
@ -87,6 +88,14 @@ catchNonAsync a onerr = a `catches`
, M.Handler (\ (e :: SomeException) -> onerr e)
]
{- Catches only async exceptions. -}
catchAsync :: MonadCatch m => m a -> (Either AsyncException SomeAsyncException -> m a) -> m a
catchAsync a onerr = a `catches`
[ M.Handler (\ (e :: AsyncException) -> onerr (Left e))
, M.Handler (\ (e :: SomeAsyncException) -> onerr (Right e))
, M.Handler (\ (e :: SomeException) -> throwM e)
]
tryNonAsync :: MonadCatch m => m a -> m (Either SomeException a)
tryNonAsync a = go `catchNonAsync` (return . Left)
where