Re: [reactive] Re: black hole detection and concurrency

Peter Verswyvelen wrote:
I fail to understand this part of the code: case fromException e of Just ThreadKilled -> do myThreadId >>= killThread unblock (race a b)
So the current thread gets killed synchronously, then then the race function is evaluated again? The latter I don't get.
Let's look at what happens when an asynchronous exception arrives. The current thread gets killed. It gets killed asynchronously; as far as the RTS knows, the exceptionit might happen inside a pure computation which may be accessible to other threads. This means that the RTS has to patch things up so that reentering the corresponding thunks continues the computation - because another thread might need the value later. It does that by traversing the stack and turning update frames into ap nodes on the heap, and linking the entered thunks to those using an indirection (again, see RaiseAsync.c for details). Now in fact, IO actions are indistinguishable from pure computations by the RTS, so this mechanism also makes IO actions resumable, in principle, if you can access the corresponding thunk somehow. Normally you can't - there is no reference to that thunk - but unsafePerformIO gives you that reference. So in the above example, the current thread gets killed. However, the IO action (suspended right before the 'unblock (race a b)') is still accessible through the unsafePerformIO thunk. When another thread enters that thunk, execution resumes at that point. It may also be the same thread if it caught the exception further down the stack and later enters the unsafePerformIO thunk again. I don't understand all the interactions here - I don't know why the code is racy in the parallel RTS. Bertram

Bertram Felgenhauer wrote:
Now in fact, IO actions are indistinguishable from pure computations by the RTS, so this mechanism also makes IO actions resumable, in principle, if you can access the corresponding thunk somehow. Normally you can't - there is no reference to that thunk - but unsafePerformIO gives you that reference.
I wonder if other things break in the presence of resumable IO computations... the first thing that comes to mind is, inside a "block" or "unblock" (which have to initiate, take down and deal with all that infrastructure -- luckily you only use them inside a separate thread, a forkIO within the unsafePerformIO... Which admittedly makes things horribly confusing in its own way. Also does forkIO copy any state? it copies blocked status now, which the unamb-calling thread might have...). The state of the thread when entering the computation again, could be different than it was when the computation was first suspended, I'm guessing. (At least you need to be careful about what invariants you assume; I'm not yet sure if it's possible to be careful enough.) -Isaac

Isaac Dupree wrote:
Bertram Felgenhauer wrote:
Now in fact, IO actions are indistinguishable from pure computations by the RTS, so this mechanism also makes IO actions resumable, in principle, if you can access the corresponding thunk somehow. Normally you can't - there is no reference to that thunk - but unsafePerformIO gives you that reference.
I wonder if other things break in the presence of resumable IO computations... the first thing that comes to mind is, inside a "block" or "unblock" (which have to initiate, take down and deal with all that infrastructure -- luckily you only use them inside a separate thread, a forkIO within the unsafePerformIO... Which admittedly makes things horribly confusing in its own way. Also does forkIO copy any state? it copies blocked status now, which the unamb-calling thread might have...). The state of the thread when entering the computation again, could be different than it was when the computation was first suspended, I'm guessing. (At least you need to be careful about what invariants you assume; I'm not yet sure if it's possible to be careful enough.)
There's one other bug in this area that we've known about for a while. In the comments in Exception.cmm: NB. there's a bug in here. If a thread is inside an unsafePerformIO, and inside blockAsyncExceptions# (there is an unblockAsyncExceptions_ret on the stack), and it is blocked in an interruptible operation, and it receives an exception, then the unsafePerformIO thunk will be updated with a stack object containing the unblockAsyncExceptions_ret frame. Later, when someone else evaluates this thunk, the blocked exception state is not restored. this probably isn't affecting reactive, but I thought I'd mention it. Cheers, Simon

therefore mapException is equally buggy!
mapException :: (Exception e1, Exception e2) => (e1 -> e2) -> a -> a mapException f v = unsafePerformIO (catch (evaluate v) (\x -> throw (f x)))
If it maps an asynchronous exception.. and it's re-thrown as synchronous... the same non-resumability happens! mapException is a particular culprit because of the unsafePerformIO (so you had a good reason to expect resumability, since it's for a pure computation, not in IO) - does anyone use mapException? - is there some reason that we don't have all "throw"s (synch. or asynch.) "patch up" the thunks? (memory leaks or serious inefficiency or something?) if "yes", I don't think mapException can currently be implemented; we'd need some way in its "catch" to detect whether the thrown exception was asynchronous, and *iff* so, throw the new exception asynchronously (if synchronous, rethrow synchronously). Or some equivalent. Or maybe some add some magic to unsafePerformIO (probably a bad idea). -Isaac

On Sun, Jan 04, 2009 at 07:40:38PM -0500, Isaac Dupree wrote:
- does anyone use mapException?
I was the one who originally requested it, since it was mentioned in the paper and looked reasonable, but was missing from GHC. My motivation was that it could be used by a pure function to hide the exception behavior of an auxiliary function that it used internally. I can't really claim to have used it very much, though. Lauri

On Sun, Jan 04, 2009 at 07:40:38PM -0500, Isaac Dupree wrote:
- does anyone use mapException?
I was the one who originally requested it, since it was mentioned in the paper and looked reasonable, but was missing from GHC. My motivation was that it could be used by a pure function to hide the exception behavior of an auxiliary function that it used internally. I can't really claim to have used it very much, though.
I use it sometimes to provide a pseudo "call-stack" for errors. Ganesh ============================================================================== Please access the attached hyperlink for an important electronic communications disclaimer: http://www.credit-suisse.com/legal/en/disclaimer_email_ib.html ==============================================================================

Isaac Dupree wrote:
therefore mapException is equally buggy!
mapException :: (Exception e1, Exception e2) => (e1 -> e2) -> a -> a mapException f v = unsafePerformIO (catch (evaluate v) (\x -> throw (f x)))
If it maps an asynchronous exception.. and it's re-thrown as synchronous... the same non-resumability happens! mapException is a particular culprit because of the unsafePerformIO (so you had a good reason to expect resumability, since it's for a pure computation, not in IO)
- does anyone use mapException?
- is there some reason that we don't have all "throw"s (synch. or asynch.) "patch up" the thunks? (memory leaks or serious inefficiency or something?)
In theory you could get a nasty space leak, but it's quite unlikely. When an exception is thrown, instead of just updating each thunk with "raise# DivByZero" for example, we would save the current computation in the heap to be restarted if the thunk was ever evaluated again. If the current computation refers to a large amount of heap data, technically that's a space leak. So one way to work around this would be to do all rethrows using throwTo. In most cases this will have no effect, because the rethrows happen in IO code which normally has no enclosing thunks, but in the case of mapException and unsafePerformIO it will fix (or work around) the problems we have with re-throwing async exceptions. Cheers, Simon
participants (6)
-
Bertram Felgenhauer
-
Isaac Dupree
-
Isaac Dupree
-
Lauri Alanko
-
Simon Marlow
-
Sittampalam, Ganesh