unsafeUnmask unmasks even inside uninterruptibleMask
Control.Exception exports
```hs
allowInterrupt :: IO ()
allowInterrupt = unsafeUnmask $ return ()
```
with documentation:
*When invoked inside `mask`, this function allows a blocked asynchronous exception to be raised, if one exists. It is equivalent to performing an interruptible operation, but does not involve any actual blocking. When called outside `mask`, or inside `uninterruptibleMask`, this function has no effect.*
However, this is not actually true: `unsafeUnmask` unmasks exceptions even inside `uninterruptibleUnmask`, as the attached test demonstrates (the test uses a foreign call just to have something non-interruptible but still observable; in particular, doing a `print` *is* interruptible because it uses an `MVar` under the hood).
I think it is possible to define a better `unsafeUnmask` in user-land:
```hs
interruptible :: IO a -> IO a
interruptible act = do
st <- getMaskingState
case st of
Unmasked -> act
MaskedInterruptible -> unsafeUnmask act
MaskedUninterruptible -> act
```
but it still seems to be that we should either (i) change the behaviour of unsafeUnmask, or (ii) provide a version of `unsafeUnmask` with the behaviour as described and then change `allowInterrupt` to use that new version of `unsafeUnmask`, or at the very least (iii) change the documentation.
(One question with the above definition of `interruptible` is what happens when we *nest* `mask` and `uninterruptibleMask`?)
<details><summary>Trac metadata</summary>
| Trac field | Value |
| ---------------------- | -------------- |
| Version | 7.8.2 |
| Type | Bug |
| TypeOfFailure | OtherFailure |
| Priority | normal |
| Resolution | Unresolved |
| Component | Runtime System |
| Test case | |
| Differential revisions | |
| BlockedBy | |
| Related | |
| Blocking | |
| CC | simonmar |
| Operating system | |
| Architecture | |
</details>
<!-- {"blocked_by":[],"summary":"unsafeUnmask unmasks even inside uninterruptibleMask","status":"New","operating_system":"","component":"Runtime System","related":[],"milestone":"","resolution":"Unresolved","owner":{"tag":"OwnedBy","contents":"simonmar"},"version":"7.8.2","keywords":[],"differentials":[],"test_case":"","architecture":"","cc":["simonmar"],"type":"Bug","description":"Control.Exception exports\r\n\r\n{{{#!hs\r\nallowInterrupt :: IO ()\r\nallowInterrupt = unsafeUnmask $ return ()\r\n}}}\r\n\r\nwith documentation:\r\n\r\n''When invoked inside `mask`, this function allows a blocked asynchronous exception to be raised, if one exists. It is equivalent to performing an interruptible operation, but does not involve any actual blocking. When called outside `mask`, or inside `uninterruptibleMask`, this function has no effect.''\r\n\r\nHowever, this is not actually true: `unsafeUnmask` unmasks exceptions even inside `uninterruptibleUnmask`, as the attached test demonstrates (the test uses a foreign call just to have something non-interruptible but still observable; in particular, doing a `print` ''is'' interruptible because it uses an `MVar` under the hood).\r\n\r\nI think it is possible to define a better `unsafeUnmask` in user-land:\r\n\r\n{{{#!hs\r\ninterruptible :: IO a -> IO a\r\ninterruptible act = do\r\n st <- getMaskingState\r\n case st of\r\n Unmasked -> act\r\n MaskedInterruptible -> unsafeUnmask act\r\n MaskedUninterruptible -> act\r\n}}}\r\n\r\nbut it still seems to be that we should either (i) change the behaviour of unsafeUnmask, or (ii) provide a version of `unsafeUnmask` with the behaviour as described and then change `allowInterrupt` to use that new version of `unsafeUnmask`, or at the very least (iii) change the documentation. \r\n\r\n(One question with the above definition of `interruptible` is what happens when we ''nest'' `mask` and `uninterruptibleMask`?)","type_of_failure":"OtherFailure","blocking":[]} -->
issue