Asynchronous exceptions in threadWait

The definition of threadWait is: threadWait :: Event http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Internal... -> Fd http://hackage.haskell.org/package/base-4.12.0.0/docs/src/System.Posix.Types... -> IO ()threadWait http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Thread.h... evt http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Thread.h... fd http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Thread.h... = mask_ http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.IO.html#mask_ $ http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Base.html#%24 do m http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Thread.h... <- newEmptyMVar http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.MVar.html#newE... mgr http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Thread.h... <- getSystemEventManager_ http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Thread.h... reg http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Thread.h... <- registerFd http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Manager.... mgr http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Thread.h... (\_ e http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Thread.h... -> putMVar http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.MVar.html#putM... m http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Thread.h... e http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Thread.h...) fd http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Thread.h... evt http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Thread.h... M.OneShot http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Internal... evt' http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Thread.h... <- takeMVar http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.MVar.html#take... m http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Thread.h... `onException http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.IO.html#onExce...` unregisterFd_ http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Manager.... mgr http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Thread.h... reg http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Thread.h... if evt' http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Thread.h... `eventIs http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Internal...` evtClose http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Internal... then ioError http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.IO.Exception.h... $ http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Base.html#%24 errnoToIOError http://hackage.haskell.org/package/base-4.12.0.0/docs/src/Foreign.C.Error.ht... "threadWait" eBADF http://hackage.haskell.org/package/base-4.12.0.0/docs/src/Foreign.C.Error.ht... Nothing http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Maybe.html#Not... Nothing http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Maybe.html#Not... else return http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Base.html#retu... () Although the entire function has asynchronous exceptions masked, the call to takeMVar uses onException to deal with the possibility of an exception. According to the docs in Control.Concurrent, takeMVar can throw exceptions. But my understand (which may be wrong) is that the only exception this could throw would be something like BlockedIndefinitelyOnMVar, which I don't believe can happen here. -- -Andrew Thaddeus Martin

Although the entire function has asynchronous exceptions masked, the call to takeMVar uses onException to deal with the possibility of an exception. According to the docs in Control.Concurrent, takeMVar can throw exceptions. But my understand (which may be wrong) is that the only exception this could throw would be something like BlockedIndefinitelyOnMVar, which I don't believe can happen here.
That's not quite right. The mask_ function blocks asynchronous exceptions from being delivered to the thread while it's not "interruptible". Most blocking functions, such as takeMVar, make the thread interruptible while they're blocked. So any asynchronous exception (such as ThreadKilled) could be delivered to the thread if it blocks in the takeMVar. -- Michael Walker (http://www.barrucadu.co.uk)

Thanks. That makes sense. So, in the code I’m looking at, since takeMVar is the last effectful thing that happens, there is no semantic difference between having mask_ extend all the way down to the bottom of the block (as it currently does) and cutting it off right before the line involving takeMVar?
Sent from my iPhone
On Jan 17, 2019, at 5:23 PM, Michael Walker
Although the entire function has asynchronous exceptions masked, the call to takeMVar uses onException to deal with the possibility of an exception. According to the docs in Control.Concurrent, takeMVar can throw exceptions. But my understand (which may be wrong) is that the only exception this could throw would be something like BlockedIndefinitelyOnMVar, which I don't believe can happen here.
That's not quite right. The mask_ function blocks asynchronous exceptions from being delivered to the thread while it's not "interruptible". Most blocking functions, such as takeMVar, make the thread interruptible while they're blocked. So any asynchronous exception (such as ThreadKilled) could be delivered to the thread if it blocks in the takeMVar.
-- Michael Walker (http://www.barrucadu.co.uk)

So, something like this?
threadWait :: Event -> Fd -> IO ()
threadWait evt fd = do
(m, mgr, reg) <- mask_ $ do
m <- newEmptyMVar
mgr <- getSystemEventManager_
reg <- registerFd mgr (\_ e -> putMVar m e) fd evt M.OneShot
pure (m, mgr, reg)
evt' <- takeMVar m `onException` unregisterFd_ mgr reg
if evt' `eventIs` evtClose
then ioError $ errnoToIOError "threadWait" eBADF Nothing Nothing
else return ()
I'm not sure this is semantically equivalent.
GHC can pre-empt a thread whenever it allocates memory, and I assume
it can deliver exceptions at that granularity too. So if
`onException` allocates memory before the exception handler is set up,
then an exception could possibly be delivered in that brief window,
which would result in `unregisterFd_ mgr reg` never being called, and
an exception being raised in the caller of `threadWait`.
On Thu, 17 Jan 2019 at 22:43, Andrew Martin
Thanks. That makes sense. So, in the code I’m looking at, since takeMVar is the last effectful thing that happens, there is no semantic difference between having mask_ extend all the way down to the bottom of the block (as it currently does) and cutting it off right before the line involving takeMVar?
Sent from my iPhone
On Jan 17, 2019, at 5:23 PM, Michael Walker
wrote: Although the entire function has asynchronous exceptions masked, the call to takeMVar uses onException to deal with the possibility of an exception. According to the docs in Control.Concurrent, takeMVar can throw exceptions. But my understand (which may be wrong) is that the only exception this could throw would be something like BlockedIndefinitelyOnMVar, which I don't believe can happen here.
That's not quite right. The mask_ function blocks asynchronous exceptions from being delivered to the thread while it's not "interruptible". Most blocking functions, such as takeMVar, make the thread interruptible while they're blocked. So any asynchronous exception (such as ThreadKilled) could be delivered to the thread if it blocks in the takeMVar.
-- Michael Walker (http://www.barrucadu.co.uk)
-- Michael Walker (http://www.barrucadu.co.uk)

No, something like
threadWait :: Event -> Fd -> IO ()
threadWait evt fd = do
m <- newEmptyMVar
mgr <- getSystemEventManager_
evt' <- mask_ $ do
reg <- registerFd mgr (\_ e -> putMVar m e) fd evt M.OneShot
takeMVar m `onException` unregisterFd_ mgr reg
if evt' `eventIs` evtClose
then ioError $ errnoToIOError "threadWait" eBADF Nothing Nothing
else return ()
On Thu, Jan 17, 2019 at 5:58 PM Michael Walker
So, something like this?
threadWait :: Event -> Fd -> IO () threadWait evt fd = do (m, mgr, reg) <- mask_ $ do m <- newEmptyMVar mgr <- getSystemEventManager_ reg <- registerFd mgr (\_ e -> putMVar m e) fd evt M.OneShot pure (m, mgr, reg) evt' <- takeMVar m `onException` unregisterFd_ mgr reg if evt' `eventIs` evtClose then ioError $ errnoToIOError "threadWait" eBADF Nothing Nothing else return ()
I'm not sure this is semantically equivalent.
GHC can pre-empt a thread whenever it allocates memory, and I assume it can deliver exceptions at that granularity too. So if `onException` allocates memory before the exception handler is set up, then an exception could possibly be delivered in that brief window, which would result in `unregisterFd_ mgr reg` never being called, and an exception being raised in the caller of `threadWait`.
On Thu, 17 Jan 2019 at 22:43, Andrew Martin
wrote: Thanks. That makes sense. So, in the code I’m looking at, since takeMVar
is the last effectful thing that happens, there is no semantic difference between having mask_ extend all the way down to the bottom of the block (as it currently does) and cutting it off right before the line involving takeMVar?
Sent from my iPhone
On Jan 17, 2019, at 5:23 PM, Michael Walker
wrote:
Although the entire function has asynchronous exceptions masked, the
call to takeMVar uses onException to deal with the possibility of an exception. According to the docs in Control.Concurrent, takeMVar can throw exceptions. But my understand (which may be wrong) is that the only exception this could throw would be something like BlockedIndefinitelyOnMVar, which I don't believe can happen here.
That's not quite right. The mask_ function blocks asynchronous exceptions from being delivered to the thread while it's not "interruptible". Most blocking functions, such as takeMVar, make the thread interruptible while they're blocked. So any asynchronous exception (such as ThreadKilled) could be delivered to the thread if it blocks in the takeMVar.
-- Michael Walker (http://www.barrucadu.co.uk)
-- Michael Walker (http://www.barrucadu.co.uk) _______________________________________________ Libraries mailing list Libraries@haskell.org http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries

Ah, that makes sense. They certainly differ in the guarantees they provide around when unregisterFd_ is called. Although I think the exception would still be raised in the caller of threadWait regardless of which way it was written. Sent from my iPhone
On Jan 17, 2019, at 5:58 PM, Michael Walker
wrote: So, something like this?
threadWait :: Event -> Fd -> IO () threadWait evt fd = do (m, mgr, reg) <- mask_ $ do m <- newEmptyMVar mgr <- getSystemEventManager_ reg <- registerFd mgr (\_ e -> putMVar m e) fd evt M.OneShot pure (m, mgr, reg) evt' <- takeMVar m `onException` unregisterFd_ mgr reg if evt' `eventIs` evtClose then ioError $ errnoToIOError "threadWait" eBADF Nothing Nothing else return ()
I'm not sure this is semantically equivalent.
GHC can pre-empt a thread whenever it allocates memory, and I assume it can deliver exceptions at that granularity too. So if `onException` allocates memory before the exception handler is set up, then an exception could possibly be delivered in that brief window, which would result in `unregisterFd_ mgr reg` never being called, and an exception being raised in the caller of `threadWait`.
On Thu, 17 Jan 2019 at 22:43, Andrew Martin
wrote: Thanks. That makes sense. So, in the code I’m looking at, since takeMVar is the last effectful thing that happens, there is no semantic difference between having mask_ extend all the way down to the bottom of the block (as it currently does) and cutting it off right before the line involving takeMVar?
Sent from my iPhone
On Jan 17, 2019, at 5:23 PM, Michael Walker
wrote: Although the entire function has asynchronous exceptions masked, the call to takeMVar uses onException to deal with the possibility of an exception. According to the docs in Control.Concurrent, takeMVar can throw exceptions. But my understand (which may be wrong) is that the only exception this could throw would be something like BlockedIndefinitelyOnMVar, which I don't believe can happen here.
That's not quite right. The mask_ function blocks asynchronous exceptions from being delivered to the thread while it's not "interruptible". Most blocking functions, such as takeMVar, make the thread interruptible while they're blocked. So any asynchronous exception (such as ThreadKilled) could be delivered to the thread if it blocks in the takeMVar.
-- Michael Walker (http://www.barrucadu.co.uk)
-- Michael Walker (http://www.barrucadu.co.uk)

Michael Walker explained quite well why that's reasonable. What seems much
*less* reasonable is that the function masks exceptions right from the
start and all the way to the end. I don't see any reason to mask until
after calling getSystemEventManager_, or to remain masked after taking the
MVar.
On Thu, Jan 17, 2019 at 5:17 PM Andrew Martin
The definition of threadWait is:
threadWait :: Event http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Internal... -> Fd http://hackage.haskell.org/package/base-4.12.0.0/docs/src/System.Posix.Types... -> IO ()threadWait http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Thread.h... evt http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Thread.h... fd http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Thread.h... = mask_ http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.IO.html#mask_ $ http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Base.html#%24 do m http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Thread.h... <- newEmptyMVar http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.MVar.html#newE... mgr http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Thread.h... <- getSystemEventManager_ http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Thread.h... reg http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Thread.h... <- registerFd http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Manager.... mgr http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Thread.h... (\_ e http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Thread.h... -> putMVar http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.MVar.html#putM... m http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Thread.h... e http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Thread.h...) fd http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Thread.h... evt http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Thread.h... M.OneShot http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Internal... evt' http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Thread.h... <- takeMVar http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.MVar.html#take... m http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Thread.h... `onException http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.IO.html#onExce...` unregisterFd_ http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Manager.... mgr http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Thread.h... reg http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Thread.h... if evt' http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Thread.h... `eventIs http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Internal...` evtClose http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Event.Internal... then ioError http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.IO.Exception.h... $ http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Base.html#%24 errnoToIOError http://hackage.haskell.org/package/base-4.12.0.0/docs/src/Foreign.C.Error.ht... "threadWait" eBADF http://hackage.haskell.org/package/base-4.12.0.0/docs/src/Foreign.C.Error.ht... Nothing http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Maybe.html#Not... Nothing http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Maybe.html#Not... else return http://hackage.haskell.org/package/base-4.12.0.0/docs/src/GHC.Base.html#retu... ()
Although the entire function has asynchronous exceptions masked, the call to takeMVar uses onException to deal with the possibility of an exception. According to the docs in Control.Concurrent, takeMVar can throw exceptions. But my understand (which may be wrong) is that the only exception this could throw would be something like BlockedIndefinitelyOnMVar, which I don't believe can happen here.
-- -Andrew Thaddeus Martin _______________________________________________ Libraries mailing list Libraries@haskell.org http://mail.haskell.org/cgi-bin/mailman/listinfo/libraries
participants (3)
-
Andrew Martin
-
David Feuer
-
Michael Walker