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 |