
On Dec 27, 2008, at 9:02 AM, Bertram Felgenhauer wrote:
The key part here is 'myThreadId >>= killThread' which throws an asynchronous exception to the thread itself, causing the update frames to be saved on the heap.
Note that 'myThreadId >>= killThread' is not equivalent to 'throw ThreadKilled'; it is a synchronous exception and replaces thunks pointed to by the update frames by another call to the raise primitive - the result being that the exception gets rethrown whenever such a thunk is evaluated. This happens with 'finally' and 'bracket': they use 'throw' for re-throwing the exception.
See rts/RaiseAsync.c (raiseAsync() in particular) for the gory details for the first case, and rts/Schedule.c, raiseExceptionHelper() for the second case.
In the above code, there is a small window between catching the ThreadKilled exception and throwing it again though, where other exceptions may creep in. The only way I see of fixing that is to use 'block' and 'unblock' directly.
That certainly seems to do the trick for the simple example at least. One way to reason about it better would be, instead of folding everything into the race function, to simply modify ghc's bracket function to give us the behavior we'd prefer (speaking of which, I recall there's something in the works for 6.12 or so to improve rethrowing of asynchronous exceptions?) brackAsync before after thing = block (do a <- before r <- catch (unblock (thing a)) (\_ -> after a >> myThreadId >>= killThread >> brackAsync before after thing ) after a return r ) where threadKilled ThreadKilled = Just () threadKilled _ = Nothing This brackAsync just drops in to the previous code where bracket was and appears to perform correctly. Further, if we place a trace after the killThread, we se it gets executed once when the example is read (i.e. a resumption) but it does not get executed if the (`seq` v) is removed from the example So this gives me some hope that this is actually doing what we'd like. I don't doubt it may have further kinks however. Cheers, Sterl.