Asynchronous exception wormholes kill modularity
Functions like finally
create, what I call, an asynchronous exception wormhole because they unblock asynchronous exceptions even if you call them in a blocked scope:
finally :: IO a -> IO b -> IO a
a `finally` sequel = block $ do
r <- unblock a `onException` sequel
_ <- sequel
return r
This hurts modularity.
I proposed solving this as follows:
finally :: IO a -> IO b -> IO a
a `finally` sequel = do
b <- blocked
block $ do
r <- (if b then unblock a else a) `onException` sequel
_ <- sequel
return r
Besides finally
the following functions also have a wormhole:
- Control.Exception.finally/bracket/bracketOnError
- Control.Concurrent.MVar.withMVar/modifyMVar_/modifyMVar
- Foreign.Marshal.Pool.withPool
In the interesting discussion that followed several other solutions were proposed (for example a block and unblock that count nesting levels).
Later, Simon Marlow proposed an even nicer solution:
mask :: ((IO a -> IO a) -> IO b) -> IO b
mask io = do
b <- blocked
if b
then io id
else block $ io unblock
to be used like this:
a `finally` sequel =
mask $ \restore -> do
r <- restore a `onException` sequel
sequel
return r
I created this ticket so we won't forget about this problem.
(These are related bugs: #3944 (closed) and #3945 (closed))
Trac metadata
Trac field | Value |
---|---|
Version | 6.12.2 |
Type | Bug |
TypeOfFailure | OtherFailure |
Priority | normal |
Resolution | Unresolved |
Component | libraries/base |
Test case | |
Differential revisions | |
BlockedBy | |
Related | |
Blocking | |
CC | |
Operating system | |
Architecture |