Skip to content

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
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information