-
Notifications
You must be signed in to change notification settings - Fork 4
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
Nested scope stuck on Data.ByteString.hGetLine #19
Comments
Thanks for the report, I'll look into this |
a deadlock could occur if a child, after beginning to tear down via a `ScopeClosing` exception delivered from its parent, ultimately wants to die with a different exception (e.g. a synchronous exception thrown during a registered cleanup action). this PR makes a child thread that has an exception to propagate to its parent first check to see if its parent's scope is closed, and if it is, elects to communicate the exception via `childExceptionVar` rather than use `throwTo`
It's a real bug :) We have a PR up here with some details: #20 Once we confirm your repro runs without failing, we'll get a patch version release up on Hackage. Versions Thank you for the bug report and working repro! |
Here's the behavior I'm seeing on that branch:
That's certainly a step up from
However, I'm not certain what's going on with the |
It's possible a bug lies somewhere in I modified your repro to use {- cabal:
build-depends: base, bytestring, ki, process
ghc-options: -threaded -rtsopts -with-rtsopts=-N -with-rtsopts=-T
-}
module Main where
import Control.Concurrent (threadDelay)
import Prelude
import Control.Monad (forever)
import qualified Data.ByteString as BS
import qualified Ki as Ki
import qualified System.Process as Process
catAction :: Ki.Scope -> IO ()
catAction scope =
Process.withCreateProcess cmd $ \_ (Just stdout) _ process -> do
Ki.fork_ scope $ forever $ BS.hGetLine stdout
forever $ do
putStrLn $ "Cat is running..."
threadDelay 300000
where
cmd =
(Process.proc "cat" []) {Process.std_out = Process.CreatePipe}
main :: IO ()
main = do
Ki.scoped $ \scope -> do
_ <- scope `Ki.fork` Ki.scoped catAction
threadDelay 500000
putStrLn "Exiting main scope..."
putStrLn "This is the end." And it exits without me having to press Enter (which is also why there's no
|
Thank you for looking into that so quickly, I really appreciate. It was not obvious what was the issue since I built a supervision tree layer on top of ki and I'm glad the simple reproducer is helpful. Though I'm not sure the proposed fix is enough, I got the same result ( It looks like this can be related to the issue described in fpco/typed-process#38 (comment) . But I wonder why the bug does not happen when only a single scope is at play, e.g. this works with or without #20: {- cabal:
build-depends: base, bytestring, ki, typed-process
ghc-options: -threaded -rtsopts -with-rtsopts=-N -with-rtsopts=-T
-}
module Main where
import Control.Concurrent (threadDelay)
import Control.Monad (forever)
import qualified Data.ByteString as BS
import qualified Ki as Ki
import qualified System.Process.Typed as ProcessTyped
catAction :: Ki.Scope -> IO ()
catAction scope =
ProcessTyped.withProcessWait_ cmd $ \p -> do
Ki.fork_ scope $ forever $ BS.hGetLine (ProcessTyped.getStdout p)
forever $ do
putStrLn $ "Cat is running..."
threadDelay 300000
where
cmd =
ProcessTyped.setStdout ProcessTyped.createPipe $
ProcessTyped.proc "cat" []
main :: IO ()
main = do
Ki.scoped $ \scope -> do
_ <- scope `Ki.fork` catAction scope
threadDelay 500000
putStrLn "Exiting main scope..."
putStrLn "This is the end." |
Of course!
I believe this is expected. Despite being syntactically nested within the scope of the That's just a partial answer, though: we will look into that |
a deadlock could occur if a child, after beginning to tear down via a `ScopeClosing` exception delivered from its parent, ultimately wants to die with a different exception (e.g. a synchronous exception thrown during a registered cleanup action). this PR makes a child thread that has an exception to propagate to its parent first check to see if its parent's scope is closed, and if it is, elects to communicate the exception via `childExceptionVar` rather than use `throwTo`
Looking into
Pressing enter allows The " {- cabal:
build-depends: base, process, typed-process
ghc-options: -threaded -rtsopts -with-rtsopts=-N
-}
import Control.Concurrent
import Control.Monad
import System.IO (hClose, hGetLine)
import System.Process qualified as Process
main :: IO ()
main = do
(_, Just stdout, _, _) <- Process.createProcess (Process.proc "cat" []) {Process.std_out = Process.CreatePipe}
forkIO (void (hGetLine stdout))
threadDelay 1000000
hClose stdout
|
Let us know if there's anything else to address @TristanCacqueray! |
If I understand correctly, without the intermediary scope, the thread that calls I guess it's better to use |
It isn't that the kill order is inverted, but rather than without the intermediary scope both threads (the When the intermediary scope is installed there is only one immediate child, the thread calling The bug in |
Hello, it looks like a scope may not terminate properly when reading the output of an external process using
hGetLine
.When running the following example I get:
The program hangs until it is interrupted with Ctrl-C.
When trying to minify the reproducer, I noticed that removing the nested scope, by replacing
Ki.scoped catAction
withcatAction scope
, the issue disappear: the program exit cleanly. Thus it seems like a bug inki
, but I can't tell the root cause, and perhaps it's related to the process library and how the exceptions are masked?The text was updated successfully, but these errors were encountered: