<div dir="ltr">Hi Edward,<br><div class="gmail_extra"><br><div class="gmail_quote">On 2 July 2016 at 05:49, Edward Z. Yang <span dir="ltr"><<a href="mailto:ezyang@mit.edu" target="_blank">ezyang@mit.edu</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">In 2010, in the thread "Asynchronous exception wormholes kill modularity" [1],<br>
Bas van Dijk observed that 'unblock :: IO a -> IO a' broke modularity,<br>
as the sequence of calls 'block . block . unblock $ io' would result in<br>
'io' being run with asynchronous exceptions unblocked, despite the outer<br>
'block' "expecting" that asynchronous exceptions cannot be thrown.<br>
<br>
I would like to make two claims:<br>
<br>
    1. The new mask/restore interface is insufficient to "solve"<br>
    this modularity problem, as *interruptible* operations can<br>
    still be used to catch asynchronous exceptions.<br>
<br>
    2. Thus, we should provide an unblock combinator which<br>
    can be used to catch asynchronous exceptions from a 'mask'<br>
    (though not an 'uninterruptibleMask')--though it is<br>
    doubtful if anyone should ever use 'mask' in the first<br>
    place.<br>
<br>
Claim 1: Here is some code which reimplements 'unblock':<br>
<br>
    import Control.Exception<br>
    import Control.Concurrent<br>
    import Control.Concurrent.MVar<br>
<br>
    unblock :: IO a -> IO a<br>
    unblock io = do<br>
        m <- newEmptyMVar<br>
        _ <- forkIO (io >>= putMVar m)<br>
        takeMVar m<br>
<br></blockquote><div><br></div><div>This isn't really an implementation of unblock, because it doesn't enable fully-asynchronous exceptions inside io.  If a stack overflow occurs, it won't be thrown, for example.  Also, io will not be interrupted by an asynchronous exception thrown to the current thread.<br><br></div><div>We already have a way to allow asynchronous exceptions to be thrown within a mask, it's called allowInterrupt: <a href="http://hackage.haskell.org/package/base-4.9.0.0/docs/Control-Exception.html#v:allowInterrupt">http://hackage.haskell.org/package/base-4.9.0.0/docs/Control-Exception.html#v:allowInterrupt</a><br><br></div><div>I don't buy the claim that this breaks "modularity".  The way to think about mask is that it disables fully-asynchronous exceptions, only allowing them to be thrown at certain well-defined points.  This makes them tractable, it means you can write code without worrying that an async exception will pop up at any point.  Inside a mask, the only way to get back to the state of fully asynchronous exceptions is to use the unblock action that mask gives you (provided you weren't already inside a mask).<br></div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
The main idea is that 'takeMVar' is an interruptible operation:<br>
when it blocks, the thread can now receive asynchronous exceptions.<br>
In general, a thread can unmask exceptions by blocking.  Here<br>
is a simple test-case:<br>
<br>
    main = do<br>
        let x = 10000000 -- Just do a bit of work<br>
        tid <- myThreadId<br>
        forkIO $ (threadDelay 10000 >> killThread tid)<br>
        r <- mask $ \restore -> do<br>
            -- restore $ do<br>
            -- unblock $ do<br>
                -- do something non-blocking<br>
                evaluate (f x [])<br>
        -- If the exception is delivered in a timely manner,<br>
        -- shouldn't get here.<br>
        print r<br>
<br>
    f 0 r = r<br>
    f n r = f (n-1) (n:r)<br>
<br>
With both restore and unblock commented, the ThreadKilled<br>
exception is delayed; uncommenting either restore or unblock<br>
causes the exception to be delivered.<br>
<br>
This admonition does not apply to uninterruptibleMask, for<br>
which there are no interruptible exceptions.<br>
<br>
Claim 2:  Thus, I come to the conclusion that we were wrong<br>
to remove 'unblock', and that it is no worse than the<br>
ability for interruptible actions to catch asynchronous<br>
exceptions.<br>
<br></blockquote><br></div><div class="gmail_quote">I don't think your argument undermines mask.<br></div><div class="gmail_quote"><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
You could very well argue that interruptible actions are a design flaw.<br></blockquote><div><br></div><div>I disagree - it's impossible to define withMVar without interruptible mask.<br></div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
Then you should use 'uninterruptibleMask' instead, which effectively<br>
removes the concept of interruptibility--and is thus modular.  Indeed,<br>
Eyal Lotem proposed [2] that 'bracket' should instead use<br>
'uninterruptibleMask', for precisely the reason that it is too easy to<br>
reenable asynchronous exceptions in 'mask'.  </blockquote><div><br></div><div>The problem he was talking about was to do with the interruptibility of the cleanup action in bracket, not the acquire, which really needs interruptible mask.  The interruptibility of the cleanup is a complex issue with arguments on both sides.  Michael Snoyman recently brought it up again in the context of his safe-exceptions library.  We might yet change that - perhaps at the very least we should implement a catchUninterruptible# that behaves like catch# but applies uninterruptibleMask to the handler, and appropriate user-level wrappers.<br></div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">But assuming that<br>
interruptible masks are a good idea (Simon Marlow has defended them<br>
as "a way avoid reasoning about asynchronous exceptions except<br>
at specific points, i.e., where you might block"), there should<br>
be an 'unblock' for this type of mask.<br>
<br>
It should be said that the absence of 'unblock' for<br>
'uninterruptibleMask' only implies that a passed in IO action (e.g., the<br>
cleanup action in bracket) does not have access to the exceptions thrown<br>
to the current thread; it doesn't actually guarantee uninterruptibility,<br>
since the passed in IO action could always raise a normal exception.<br>
Haskell's type system is not up to the task of enforcing such<br>
invariants.<br>
<br>
Cheers,<br>
Edward<br>
<br>
[1]<br>
<a href="https://mail.haskell.org/pipermail/libraries/2010-March/013310.html" rel="noreferrer" target="_blank">https://mail.haskell.org/pipermail/libraries/2010-March/013310.html</a><br>
<a href="https://mail.haskell.org/pipermail/libraries/2010-April/013420.html" rel="noreferrer" target="_blank">https://mail.haskell.org/pipermail/libraries/2010-April/013420.html</a><br>
<br>
[2]<br>
<a href="https://mail.haskell.org/pipermail/libraries/2014-September/023675.html" rel="noreferrer" target="_blank">https://mail.haskell.org/pipermail/libraries/2014-September/0<br></a><div>Cheers,<br></div><div>Simon<br></div><div><br></div><a href="https://mail.haskell.org/pipermail/libraries/2014-September/023675.html" rel="noreferrer" target="_blank">23675.html</a><br>
<br>
P.S. You were CC'ed to this mail because you participated in the original<br>
"Asynchronous exception wormholes kill modularity" discussion.<br>
<br>
P.P.S. I have some speculations about using uninterruptibleMask more<br>
frequently: it seems to me that there ought to be a variant of<br>
uninterruptibleMask that immediately raises an exception if<br>
the "uninterruptible" action blocks.  This would probably of<br>
great assistance of noticing and eliminating blocking in<br>
uninterruptible code.<br>
</blockquote></div><br></div><div class="gmail_extra">Now that's an interesting idea!<br></div><div class="gmail_extra"><br><div>Cheers,<br></div><div>Simon<br></div><div><br></div><br></div></div>