[Haskell-cafe] Re: Asynchronous exception wormholes kill modularity

Isaac Dupree ml at isaac.cedarswampstudios.org
Tue Apr 20 13:16:00 EDT 2010

On 04/20/10 06:56, Simon Marlow wrote:
> On 09/04/2010 12:14, Bertram Felgenhauer wrote:
>> Simon Marlow wrote:
>>> On 09/04/2010 09:40, Bertram Felgenhauer wrote:
>>>> timeout t io = mask $ \restore -> do
>>>> result<- newEmptyMVar
>>>> tid<- forkIO $ restore (io>>= putMVar result)
>>>> threadDelay t `onException` killThread tid
>>>> killThread tid
>>>> tryTakeMVar result
>> I'm worried about the case when this function is called with exceptions
>> already blocked. Then 'restore' will be the identity, and exceptions
>> will continue to be blocked inside the forked thread.
>> You could argue that this is the responsibility of the whole chain of
>> callers (who'd have to supply their own 'restore' functions that will
>> have to be incorporated into the 'io' action), but that goes against
>> modularity. In my opinion there's a valid demand for an escape hatch
>> out of the blocked exception state for newly forked threads.
>> It could be baked into a variant of the forkIO primitive, say
>> forkIOwithUnblock :: ((IO a -> IO a) -> IO b) -> IO ThreadId
> I agree with the argument here. However, forkIOWithUnblock reintroduces
> the "wormhole", which is bad.
> The existing System.Timeout.timeout does it the other way around: the
> forked thread sleeps and then sends an exception to the main thread.
> This version work if exceptions are masked, regardless of whether we
> have forkIOWithUnblock.
> Arguably the fact that System.Timeout.timeout uses an exception is a
> visible part of its implementation: the caller must be prepared for
> this, so it is not unreasonable for the caller to also ensure that
> exceptions are unmasked. But it does mean that a library cannot use
> System.Timeout.timeout invisibly as part of its implementation. If we
> had forkIOWithUnblock that would solve this case too, as the library
> code can use a private thread in which exceptions are unmasked. This is
> quite a nice solution too, since a private ThreadId is not visible to
> anyone else and hence cannot be the target of any unexpected exceptions.
> So I think I'm convinced that forkIOWithUnblock is necessary. It's a
> shame that it can be misused, but I don't see a way to avoid that.

[forkIOWithUnblock in the implementation of 'timeout'?] I thought that 
System.Timeout.timeout runs the IO in the original thread for a good 
reason.  Was it just so that it could receive asynchronous exceptions 
correctly? Or also so that myThreadID returned the correct value?  If 
just the former is what we're concerned about, we *could* make it behave 
differently when exceptions are unblocked vs. when it's uninterruptible, 
except that they can be restored to an unblocked state somewhere within 
the io.

[Oh wait, Simon was suggesting that the library should run 
forkIOWithUnblock as a wrapper to its use of 'timeout'.]  Yes, that 
sounds relatively safe.  None of the exceptions thrown to the original 
thread will be discharged unexpectedly as a result of this unblocking, 
because of the forkIO.

More information about the Haskell-Cafe mailing list