You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
os/exec: consider changing Wait to stop copying goroutines rather than waiting for them #23019
When the Stdin, Stdout, or Stderr fields of os/exec.Cmd are set to anything other than nil or a *os.File (a common case is *bytes.Buffer), we call os.Pipe to get a pipe and create goroutines to copy data in or out. The (*Cmd).Wait method first waits for the subprocess to exit, then waits for those goroutines to finish copying data.
If the subprocess C1 itself starts a subsubprocess C2, and if C1 passes any of its stdin/stdout/stderr descriptors to C2, and if C1 exits without waiting for C2 to exit, then C2 will hold an open end of the pipes created by the os/exec package. The (*Cmd).Wait method will wait until the goroutines complete, which means waiting until those pipes are closed, which in practice means waiting until C2 exits. This is confusing, because the user sees that C1 is done, and doesn't understand why their program is still waiting for it.
It doesn't have to work this way. Although the current goroutines call io.Copy, we could change them to use a loop. After every Read, the loop could check whether the process has been waited for. Then Wait could wait for the child, tell the goroutines to stop, give them a chance for a final write, and then return. The stdout/stderr goroutines would close their end of the pipe. There wouldn't be any race and there wouldn't be any unexpected waits. But in cases where there is a C2 process, not all of the standard output and standard error output that we currently collect would be available.
To be clear, programmers can already handle these cases however they like by calling os.Pipe themselves and using the pipe in the Cmd struct. That is true today and it would be true if change how it works.
The questions I want to raise are: would making this change be less confusing for people? Can we make this change without breaking the Go 1 guarantee?
Reacted by Ahmed W., Shuanglei Tao, Yiftach Karkason, Serhii Mudryk, runne, Wèi Cōngruì, DeedleFake, Dean Jackson, joe miller, Jesse Nelson and 33 more
This change may break existing code, but it does fix unspecified behavior in (*Cmd).Wait that is also confusing and unexpected and will definitely make the use of os.Cmd less confusing.
FWIW, it took me quite a while to figure out that this issue was root cause for mobile tests to sometimes hang indefinitely. https://go-review.googlesource.com/#/c/42271/ is the workaround for the Android test harness.
I actually prefer the semantics we have today. That way, it's at least apparent that something is wrong instead of silently leaving long-running processes behind.
The underlying issue of held-open pipe FDs is explained in any basic OS course and can be debugged using readily available Unix tools. I think the alternative will result in more complex failure cases.
@slrz It makes many people confusing that Wait do not return after the process exited. Although the document has been improved, but we still don't have a way to cancel the io.Copy in the internal goroutines unless using io.Pipe for the os.Cmd struct which will make code complex.
In order to handle the case where C2 doesn't output anything, we would have to put a deadline on reading from the pipe, so that while waiting for output we periodically check whether C1 has exited.
But in cases where there is a C2 process, not all of the standard output and standard error output that we currently collect would be available.
This sounds like a bug to me, and very much unexpected. I wouldn't ever expect Wait to be guaranteed to return until after I have consumed all the output; in this scenario, the output has not ended yet.
Also, I'm not sure if I understood the "give them a chance for a final write" part, but that sounds like there's a race that could also lose C1 output if C1 exits immediately after filling the pipe buffer; that's not guaranteed to come through in a single Read.
Would it be possible to get back the go1.9 behavior with a flag?
I have a use case where I need to cancel C1 now and where I really don't care about C2 output and where C2 can be left hanging. I have no control over C2. That used to work with go 1.9, but with go 1.10 it insists on waiting for C2 (which I can't control).
@kgadams There was no intentional change in this area in 1.10 This issue is about a proposed change to os/exec; the change has not been implemented. If you are finding an unexpected change that you think is a bug, please open a new issue. Thanks.
My scenario is like this: we are writing an interpreter for a specific content. Other people write the content (I have no control over that). Part of that content is shell script snippets.
Now, during execution (which happens behind a UI), the end user can Cancel us at any time.
If at the point of Cancelation by the end user I am executing the shell (C1), and the shell is executing a long running command (C2), I do not care about C1 output nor about C2 output. All I care about is that the whole enchilada is killed and that the function executing C1 returns without waiting for C2.
I even can live with C2 continuing to run in the background.
Cancelation works by canceling the Context that is passed to exec.Cmd.
I'll provide an example later of a test that succeeded with go-1.9 but fails with go-1.10.
Hi,
this is the example that demonstrates the problem.
We read from the StdoutPipe.
Context is canceled after one second.
With go 1.9 Cmd.Wait() returns immediately.
With go 1.10 it waits till C2 (sleep 5) is done.
If you can tell me a way to get the go 1.9 behavior with go 1.10, my problem would be solved.
Activity
karkason commentedon Feb 24, 2018
This change may break existing code, but it does fix unspecified behavior in
(*Cmd).Waitthat is also confusing and unexpected and will definitely make the use ofos.Cmdless confusing.ianlancetaylor commentedon Feb 28, 2018
Another case: #24050.
eliasnaur commentedon Feb 28, 2018
FWIW, it took me quite a while to figure out that this issue was root cause for mobile tests to sometimes hang indefinitely. https://go-review.googlesource.com/#/c/42271/ is the workaround for the Android test harness.
slrz commentedon Mar 2, 2018
I actually prefer the semantics we have today. That way, it's at least apparent that something is wrong instead of silently leaving long-running processes behind.
The underlying issue of held-open pipe FDs is explained in any basic OS course and can be debugged using readily available Unix tools. I think the alternative will result in more complex failure cases.
tsl0922 commentedon Mar 3, 2018
@slrz It makes many people confusing that
Waitdo not return after the process exited. Although the document has been improved, but we still don't have a way to cancel theio.Copyin the internal goroutines unless usingio.Pipefor theos.Cmdstruct which will make code complex.ianlancetaylor commentedon Apr 25, 2018
In order to handle the case where C2 doesn't output anything, we would have to put a deadline on reading from the pipe, so that while waiting for output we periodically check whether C1 has exited.
tv42 commentedon May 31, 2018
This sounds like a bug to me, and very much unexpected. I wouldn't ever expect Wait to be guaranteed to return until after I have consumed all the output; in this scenario, the output has not ended yet.
Also, I'm not sure if I understood the "give them a chance for a final write" part, but that sounds like there's a race that could also lose C1 output if C1 exits immediately after filling the pipe buffer; that's not guaranteed to come through in a single Read.
upgrade to Go 1.10
kgadams commentedon Jun 4, 2018
Would it be possible to get back the go1.9 behavior with a flag?
I have a use case where I need to cancel C1 now and where I really don't care about C2 output and where C2 can be left hanging. I have no control over C2. That used to work with go 1.9, but with go 1.10 it insists on waiting for C2 (which I can't control).
ianlancetaylor commentedon Jun 5, 2018
@kgadams There was no intentional change in this area in 1.10 This issue is about a proposed change to os/exec; the change has not been implemented. If you are finding an unexpected change that you think is a bug, please open a new issue. Thanks.
tv42 commentedon Jun 6, 2018
@kgadams But you are interested in C1 output, and there is no way to tell which is which.
kgadams commentedon Jun 7, 2018
My scenario is like this: we are writing an interpreter for a specific content. Other people write the content (I have no control over that). Part of that content is shell script snippets.
Now, during execution (which happens behind a UI), the end user can Cancel us at any time.
If at the point of Cancelation by the end user I am executing the shell (C1), and the shell is executing a long running command (C2), I do not care about C1 output nor about C2 output. All I care about is that the whole enchilada is killed and that the function executing C1 returns without waiting for C2.
I even can live with C2 continuing to run in the background.
Cancelation works by canceling the Context that is passed to exec.Cmd.
I'll provide an example later of a test that succeeded with go-1.9 but fails with go-1.10.
kgadams commentedon Jun 7, 2018
Hi,
this is the example that demonstrates the problem.
We read from the StdoutPipe.
Context is canceled after one second.
With go 1.9 Cmd.Wait() returns immediately.
With go 1.10 it waits till C2 (sleep 5) is done.
If you can tell me a way to get the go 1.9 behavior with go 1.10, my problem would be solved.
45 remaining items
gopherbot commentedon Apr 19, 2022
Change https://go.dev/cl/400877 mentions this issue:
cmd/go/internal/test: used lockedStdout instead of os.Stdoutgopherbot commentedon Apr 19, 2022
Change https://go.dev/cl/400877 mentions this issue:
cmd/go/internal/test: wrap os.Stdout alwayscmd/go/internal/test: wrap os.Stdout always
Read logs from Stderr/out Pipe to avoid hanging
Test for runTerraformCmd leaked go-routine (#299)
Test for runTerraformCmd leaked go-routine (hashicorp#299)
lixd commentedon Jul 7, 2022
there are some workarounds to use before this proposal implement.
we can kill all child process when context is done.
or set stdout、stderr use *os.File type to avoid bloking
ianlancetaylor commentedon Oct 10, 2022
I'm retracting this proposal in favor of #50436.
Export TF_PLUGIN_CACHE_MAY_BREAK_DEPENDENCY_LOCK_FILE=true in CI
Use Command.WaitDelay to avoid Command.Wait blocking on stdin/out/err
fix(servstate): use WaitDelay to avoid Command.Wait blocking on stdin…
(*Cmd).Rundoesn't return when TCP connection attached to I/O #73675 as a duplicate of this issue