probably we shouldn't get a crash, but a blackhole instead.
Even a blackhole is wrong. There's no cycle so it ought to evaluate successfully.
I didn't mean to suggest that a blackhole is correct, sorry for the confusion. No, obviously the code should just work.
Fixing it so that the evaluation of x is actually continued is what we want, but I can't see an easy way to do that.
I think GHC does the right thing (semantically) here. From memory, GHC works like this:
on entering thunk: turn it from a thunk into a blocking queue (optimization: delay the transformation until context switch time)
on entering a blocking queue: if cyclic structure (i.e., already under evaluation by same thread), report blackhole. otherwise, put this thread to sleep on the blocking queue.
Delete the sentence "if cyclic structure..." and that's correct. If a thread goes to sleep on its own blocking queue, then it just deadlocks, and at some point later it will get a NonTermination exception when the RTS realises what it has done.
Based on this, what does GHC do here when a finalizer tries to evaluate a thunk already being evaluated? The finalizer is put to sleep until the main thread finishes evaluation of the thunk.
Yup.
And what does GHC do if a call to unsafePerformIO hits a thunk already under evaluation by the same thread? This is a blackhole (i.e., deadlock). The thread throws the blackhole exception.
Yup.
I think Hugs should report a blackhole in the unsafePerformIO case (I believe it does already).
Unless you try to exit and re-enter the RTS inside unsafePerformIO... (admittedly not very likely, but I bet you could make it fall over this way).
Hugs (and NHC, I believe) have no way to block finalizers once they start executing.
I think our only choice is to turn off blackholing, allow the shared term to be evaluated twice over and hope that the two updates to the thunk (one by finalizer and then one by the main thread) don't do any harm. I don't know if this would work. Even if it does, it's not too palatable because of the loss of sharing and the space leaks (that blackholing normally fixes).
Yes, I don't think this is a good solution.
I suppose you could suspend either the finalizer or the main thread using the trick of saving its stack on the heap - is this implemented in Hugs?
No - it is an STG-specific trick. Hugs is based on the G-machine.
In Hugs, the state of a pure evaluation is stored on the C stack and in the heap. We could try longjmp-ing out of the finalizer and hope that the heap has a full and consistent record of the state but I'm not overly confident that this would work. (Hmmm, longjmp-ing out is what exception handlers do and we believe that those work....)
The only other solution I can think of is to delay finalizers until we get back up to the IO monad.
and not inside an unsafePerformIO call.
right. Cheers, Simon
participants (1)
-
Simon Marlow