It’s not.
It’s easier to see why when you assign the scoped function a more accurate name:
\f -> mask_ $ forkIOWithUnmask $ \unmask -> f unmask
vs
\f -> { *** } mask $ \restore -> forkIO $ f restore
In the first one, when unmask
is called, it will unconditinally unmask asynchronous exceptions.
In the second one, when restore
is called, it will restore the masking state from the point marked by ***
. This means that if exceptions were already masked at this point by the outer mask that can’t be seen here, they will stay masked even after you use restore
.
Here’s demonstration:
import Control.Concurrent
import Control.Exception
import System.Timeout
test1 :: IO ()
test1 = do
uninterruptibleMask_ $ do
sem <- newEmptyMVar
_ <- mask_ $ forkIOWithUnmask $ \unmask -> do
_ <- timeout 1_000_000 . unmask $ threadDelay 5_000_000
putMVar sem ()
readMVar sem
test2 :: IO ()
test2 = do
uninterruptibleMask_ $ do
sem <- newEmptyMVar
mask $ \restore -> forkIO $ do
_ <- timeout 1_000_000 . restore $ threadDelay 5_000_000
putMVar sem ()
readMVar sem
test1
takes 1 second, test2
takes 5.