
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.