-
Notifications
You must be signed in to change notification settings - Fork 70
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Prevent a deadlock if stepping the network crashes #262
Conversation
Currently, the following problem causes a deadlock: ```haskell {-# language TypeApplications #-} import Control.Exception import Data.Functor import Reactive.Banana import Reactive.Banana.Frameworks main :: IO () main = do (addHandler, fire) <- newAddHandler network <- compile $ do e <- fromAddHandler addHandler e' <- execute $ e $> do error "BANG" return () reactimate $ print <$> e reactimate $ print <$> e' actuate network _ <- try @SomeException (fire ()) fire () ``` The problem occurs when first call `fire`. The high-level step function first takes the network `MVar`, and then tries to step the network. This crashes, but the overall program is safe because it was wrapped in `try`. However, we're now in a state where the network `MVar` is empty, so the second time we try and call `fire` we deadlock. The fix in this commit is to bracket the whole network evaluation with `bracketOnError`, and to restore the starting state if an error occurs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM; it doesn't look totally async exception safe, but I don't think it has to be :)
Oopsies, I misread the PR a bit - won't this still deadlock if a |
@mitchellwrosen I'd love to know more! What are you thinking of? |
@mitchellwrosen Re |
@mitchellwrosen Ah sorry, I see what you mean now! |
Alright, pushed a slightly different version which I think does what we want wrt |
Okay, looks great. The async exception unsafety I was referring to is still present, I think, but probably not very important. bracketOnError
(takeMVar s)
(putMVar s)
\s1 -> do
(output, s2) <- f s1
putMVar s s2
{- async exception here -}
return output If an async exception strikes there, I think we'll deadlock trying to put to a full MVar. There might be a higher-level way than just using mask \restore -> do
s1 <- takeMVar s
(output, s2) <- restore (f s1) `onException` putMVar s s1
putMVar s s2
return output |
Ah, I see! That's a great catch. I really want to get this right, so I think using |
(sorry, forgot the |
93b0853
to
4e25441
Compare
Ok, using |
So long as the network itself isn't created with async exceptions uninterruptibly masked, a user could still timeout the takeMVar here, because takeMVar on an empty MVar is considered a "blocking" operation, and therefore can still be sniped by an async exception, if one's enqueued. |
Ah! So even though it's |
Yeah, that's right, it can be interrupted if it blocks (because we are using I think the |
🙏 Makes sense, thanks guys! |
Currently, the following problem causes a deadlock:
The problem occurs when first call
fire
. The high-level step functionfirst takes the network
MVar
, and then tries to step the network. Thiscrashes, but the overall program is safe because it was wrapped in
try
. However, we're now in a state where the networkMVar
is empty,so the second time we try and call
fire
we deadlock.The fix in this commit is to bracket the whole network evaluation with
bracketOnError
, and to restore the starting state if an error occurs.