-
Notifications
You must be signed in to change notification settings - Fork 25
Fix waitForProcess logic #2
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
Conversation
Interesting, there are a few things intertwined here. Thanks for raising this, let's hash it out. Firstly: you're right about I think both of our approaches in theory are open to a race condition. The race condition you're describing in the current code, IIUC, looks like:
However, I'm not convinced this is possible, since (as you pointed out) we have async exceptions masked. In fact, if we have them masked with On the other hand, I see a potential issue with your approach: what if the |
Just to clarify, you mean we have the behavior we want if
Yeah, this is the race condition I attempted to handle with the Thanks for looking! |
No, I mean if it's masked, but not uninterruptibly masked.
The idea is that, with the current code, we never kill the process when there's a chance that |
Then a
But the mechanism to determine whether or not Cheers! |
Are you familiar with interruptible actions? http://hackage.haskell.org/package/base-4.10.0.0/docs/Control-Exception.html#g:13 I'm having trouble following your argument at this point, I don't think it adds up at all with how masking works. |
Yes I am! It's my understanding that an |
The docs and my experience both imply the opposite:
Do you have any evidence to demonstrate that interruptible FFI calls are not, in fact, interruptible? |
And
|
Huh, you are totally right! I just played around with some Therefore, |
Ah, that doesn't explain anything, as once |
module Main where
import System.Process
import Control.Concurrent
import Control.Exception
main :: IO ()
main = do
(_, _, _, p) <- createProcess (shell "sleep 2")
tid <-
forkIO $ mask_ $ do
putStrLn "thread: waitForProcess"
code <- waitForProcess p
putStrLn ("thread: " ++ show code)
threadDelay 1000000
putStrLn "main: killThread"
killThread tid
putStrLn "main: bye" This prints:
|
I think what's going on is:
|
It sounds like the solution would be in the process package, to explicitly poll for masked exceptions before retrying the system call. Does that make sense? |
I agree, |
I think this patch is no longer necessary, though there is another small issue. The withProcess_ config (\p -> {- something short -}) |
Want to send an updated PR for that issue?
…On Wed, Jul 26, 2017 at 6:11 PM, Mitchell Rosen ***@***.***> wrote:
I think this patch is no longer necessary, though there is another small
issue. The Left ... branch doesn't fill the exit code TMVar, so
withProcess_ (for example) would throw BlockedIndefinitelyOnSTM if the
process didn't complete before the callback finished, as in:
withProcess_ config (\p -> {- something short -})
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#2 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AADBB7G313zwI2Pm8maw-fqI9CB1hW3Aks5sR1cagaJpZM4OgOxd>
.
|
Sure, #3. Does that sound like the right fix to |
I'm not sure about the |
I agree the docs are vague! But without a fix to withProcess config (\_ -> pure ()) -- this won't return until the process completes!
Here are some demonstrations of how safe/interruptible interacts with unmasked/masked/uninterruptible (file is called interrupt.hs): #!/usr/bin/env stack
-- stack script --resolver lts-9.0 interrupt.hs -- +RTS -V0 -RTS
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE InterruptibleFFI #-}
import Control.Concurrent
import Control.Exception
import System.Environment
import System.Timeout
foreign import ccall safe
"sleep" sleep_safe :: Int -> IO Int
foreign import ccall interruptible
"sleep" sleep_interruptible :: Int -> IO Int
main :: IO ()
main = do
[_, safety, masking] <- getArgs
let sleep =
case safety of
"safe" -> sleep_safe
"interruptible" -> sleep_interruptible
maskf =
case masking of
"unmasked" -> id
"interruptible" -> mask_
"uninterruptible" -> uninterruptibleMask_
tid <- forkIO $ maskf $ do
putStrLn "Worker: sleeping"
_ <- sleep 1
putStrLn "Worker: woke up"
threadDelay 100000
putStrLn "Main: killing worker"
timeout 100000 (killThread tid) >>= \case
Nothing -> putStrLn "Main: failed to kill worker"
Just _ -> putStrLn "Main: killed worker"
Notice that in the With my proposed #!/usr/bin/env stack
-- stack script --resolver lts-9.0 interrupt2.hs -- +RTS -V0 -RTS
{-# LANGUAGE InterruptibleFFI #-}
import Control.Concurrent
import Control.Exception
import System.Environment
import System.Timeout
foreign import ccall interruptible
"sleep" sleep :: Int -> IO Int
main :: IO ()
main = do
tid <-
forkIO $ mask_ $ do
putStrLn "Worker: interruptible sleep"
_ <- interruptible (sleep 1)
putStrLn "Worker: woke up"
threadDelay 100000
putStrLn "Main: killing worker"
killThread tid
The async exception is delivered as intended (no |
To clarify my previous comment: I agree that something needs to be fixed in process, I just couldn't give a sign-off on your idea above due to the ambiguity I mentioned in the docs. Proving (as you have here) how the interrupts work is perfect. Even better would be including a test case on the process repo. If you open up a PR there, I'll be able to review/merge it in. Referencing this PR is a good idea. |
This issue was resolved by #3 and haskell/process#101, so closing. Thanks. |
Hello! I think I identified a couple problems with the current process cleanup logic.
waitingThread
is spawned with exceptions masked (when usingwithProcess
), socancel waitingThread
does not actually do anything. I believe therefore we would always enter theRight
branch ofcase eec of ...
Also, I don't think the general idea of killing the waiting thread and possibly re-waiting is correct, Since an async
ThreadKilled
might bring the thread down afterwaitpid
returns, and thus the exit code is lost forever.So, my fix is to leave the responsibility of filling
pExitCode
to the waiting thread. And because it does not expect to be sent aThreadKilled
during cleanup, I think a simpleforkIO
suffices to spawn it.Finally, there is a small race condition fix in there involving calling
terminateProcess
on a process that just ended.