Missing primitive for unmasking uninterruptibility of async exceptions?
Motivation
I'm looking at a bug where async
is called in an uninterruptibleMask
, causing a deadlock as the withAsync
cannot complete because the Async
cannot be canceled due to spawning in the uninterruptibleMask
state.
The local solution is to write:
asyncWithUnmask $ \unmask -> unmask $ do
mask $ \restore -> ...
However, this leaves us in a slightly vulnerable state - that unmask
lambda parameter is provided as unsafeUnmask
, which removes all masking and then throws the async exception.
Looking into the code, it doesn't appear that there's a primitive that goes from MaskedUninterruptible
to MaskedInterruptible
. We have unmaskAsyncException#
, which seems to remove all masking. Indeed, it seems like the first thing it does is throw any async exceptions on the thread - so the unmask $ mask
pattern is immediately vulnerable.
The issue is located here: https://github.com/iand675/hs-opentelemetry/pull/42
Proposal
-- | Removes the uninterruptibility of the masked action.
--
-- Has no effect if we are in an unmasked or interruptibly masked state.
-- Insert appropriate unsafety caveats here.
unsafeUnUninterruptibleMask :: IO a -> IO a
unsafeUnUninterruptibleMask (IO io) = IO (unsafeUnUninterruptibleMask# io)