
I can agree to this phrasing, but I don't think it's perfect yet.
Specifically, it doesn't explicitly explain how unsafePerformIO code
is paused; it only mentions pausing in respect to pure code, while it
is arguably reasonable to expect unsafePerformIO to behave
differently.
Fix that, and I'd sign off on the patch if I were in charge. :)
On Wed, Jun 3, 2009 at 1:34 AM, Bertram Felgenhauer
Conal Elliott wrote:
I've pushed Svein's patch, plus some small tweaks of my own, to the darcs repo at http://code.haskell.org/unamb . Comments, please. I have a pretty tenuous grip on this block/unblock/retry stuff.
I think the code is fine.
| -- Exception handling in unsafePerformIO does not happen like you're | -- used to in normal code. Specifically:
This is not really true. Exception handling is the same whether unsafePerformIO is involved or not. The real point, I think, is that normal IO code will never be executed more than once with the same RealWorld# token, and never be called from pure code, so that you do not have to think about the difference between asynchronous and synchronous exceptions at all.
I've tried to rephrase the comment below. (I can submit a patch if we agree that this is an improvement.)
restartingUnsafePerformIO :: IO a -> a restartingUnsafePerformIO = unsafePerformIO . retry where -- We rely heavily on implementation details of GHC's exception handling -- in the code below. Specificially: -- -- * If a thread catches an asynchronous exception, the stack is unwound -- until the first exception handler is found. All pending updates -- are turned into thunks that recreate the stack and continue the -- computation where it is aborted. This is done so that pure -- computations can be interrupted and later resumed when the -- corresponding value is demanded another time. In other words, -- the currently running computation is paused. This also works -- for unsafePerformIO, and that's what we exploit below. -- * If a thread throws a normal (synchronous) exception, e.g. throw, -- throwIO, error (including pattern match failures), etc., then -- all pending updates will instead be turned into thunks that -- re-throw the same exception again. This is done for performance -- reasons, but it would make our unsafePerformIO unrestartable. -- So we have to avoid using throw or throwIO below. -- -- To make a whole IO action restartable, we catch any exceptions -- thrown by it (which typically indicate that the thread was killed -- in the case of 'unamb'), and use an asynchronous exception to set -- up a paused computation that runs the IO action again. -- -- Incidentally, all exception handlers run inside an implicit block, and -- blocking operations contain an implicit unblock. This ensures that any -- further pending exceptions won't mess this scheme up, as they can't be -- delivered until after throwTo has been called. -- retry :: IO a -> IO a retry act = act `catch` \ (SomeException e) -> do myThreadId >>= flip throwTo e unblock $ retry act <<<
regards,
Bertram
-- Svein Ove Aas
participants (1)
-
Svein Ove Aas