I finally was able to track a weird bug I was having down to the (at least to me) surprising interaction between mask
and timeout
:
import System.Timeout
import Control.Exception
ack :: Int -> Int -> Int
ack m n | m == 0, n >= 0 = n + 1
| m > 0, n == 0 = ack (m - 1) 1
| m > 0, n > 0 = ack (m - 1) (ack m (n - 1))
tryack :: Int -> Int -> IO (Maybe Int)
tryack m n = timeout 100000 {- uS -} $ evaluate $ ack m n
main :: IO ()
main = do
a <- tryack 3 11
print a -- Nothing
b <- mask_ $ tryack 3 11
print b -- Just 16381 after a few seconds
This strikes me as a rather "non-compositional" interaction, as it means that if a library internally uses timeout
, an externally applied mask
somewhere up the call-chain may cause the library to malfunction.
So is this a (known) deficiency in the implementation of timeout
or is it intentional?
What does mask_
do?
Executes an IO computation with asynchronous exceptions masked. That is, any thread which attempts to raise an exception in the current thread with Control.Exception.throwTo will be blocked until asynchronous exceptions are unmasked again.
And what does timeout
do?
Wrap an IO computation to time out and return Nothing in case no result is available within n microseconds (1/10^6 seconds).In case a result is available before the timeout expires, Just a is returned. ... A tricky implementation detail is the question of how to abort an IO computation. This combinator relies on asynchronous exceptions internally.
So... mask_
is preventing timeout
from delivering its exceptions. That's just how it is.
You just can't use mask
and have timeout
work.
Perhaps a better approach would be to use a handler to catch anything but the exception that timeout
uses?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With