-
Notifications
You must be signed in to change notification settings - Fork 17.9k
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
os/exec: Current requirement for all reads to be completed before calling cmd.Wait can block indefinitely #60309
Comments
See #23019, perhaps. cc @ianlancetaylor @bcmills |
Here is a Playground variation on that program that exhibits the same behavior: |
Actually, the problem on the Playground seems to come from the Playground-specific |
@mitar, I think the problem you observe comes from sending the signal to the Adjusting the program to create a process group and send the signal to the whole process group also gives the expected behavior. package main
import (
"io"
"log"
"os"
"os/exec"
"sync"
"syscall"
"time"
)
func execute() error {
cmd := exec.Command("bash", "-c", "(sleep infinity)")
cmd.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true,
}
stdout, _ := cmd.StdoutPipe()
stderr, _ := cmd.StderrPipe()
if err := cmd.Start(); err != nil {
log.Printf("Error executing command: %s......\n", err.Error())
return err
}
go func() {
<-time.After(1 * time.Second)
log.Printf("sending SIGTERM")
syscall.Kill(-cmd.Process.Pid, syscall.SIGTERM)
}()
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
io.Copy(os.Stdout, stdout)
}()
wg.Add(1)
go func() {
defer wg.Done()
io.Copy(os.Stderr, stderr)
}()
wg.Wait()
if err := cmd.Wait(); err != nil {
log.Printf("Error waiting for command execution: %s......\n", err.Error())
return err
}
log.Printf("end")
return nil
}
func main() {
execute()
} |
As far as I can tell, this is more of a |
Thanks. A process group approach is nice and is a workaround when you know that your child process is misbehaving like bash. But the issue is that in my particular case I cannot know that in advance. I am writing an init process in Go and some services might behave well and expect only the top-level process to get the signal and some might need the process group approach. For example, this could be a case where process does double fork to get to daemonize. In that case I want to reap the direct child and if it happens that stdin/stdout got inherited by the second daemonized process should not prevent me from doing this. To me the surprising thing is Go API requirement here. Without this API requirement, I could wait for the process, see that it terminated, decide to end processing of stdout and stderr (if that has not yet finished), and leave to the stray process to get reparented to PID 1. Even more, I could even decide that I want to continue processing stdout and stderr, but that I also want to reap the now dead children. |
In that case, don't use It's kind of an unfortunate workaround, but it avoids the close-on- |
I don't think there is anything to be done here, so closing. Please comment if you disagree. |
I am confused by looking at https://go.dev/cl/484741, because from what I understand from it, it creates a pipe, calls run, calls wait, and when the overall function returns, it closes the pipe without waiting for everything to be read from it. Isn't this exactly the same as what would happen if I just used So my question is: could documentation for
To something like (paraphrasing):
Am I missing something here? Maybe I do not see how is your "unfortunate workaround" doing anything different with the close-on- |
It seems to me that the current documentation is reasonable for almost all users: it says what happens and makes a recommendation. You clearly understand in detail what is happening. If |
The difference is that with your own pipe, you can continue to read from the pipe (and wait for that read to complete) even after In contrast, with |
Could |
No. That is exactly the opposite of the current documented behavior, and could introduce deadlocks in existing programs that (accidentally or intentionally) rely on that behavior. |
I am just thinking that this is a common use case. One wanting to wait for all data and for program to finish. Maybe then another Wait-like method should be introduced? Like |
Again, you can fairly easily use (Most of the extra complexity in the linked CL is because it handles arbitrary writers for |
What version of Go are you using (
go version
)?Does this issue reproduce with the latest release?
Yes.
What operating system and processor architecture are you using (
go env
)?go env
OutputWhat did you do?
I tried to run the following program:
This is a variation of an example from #38268. I constructed this example but in my real app I do not just copy subprocess stdout to main stdout, but I do a transformation on it. Anyway, the issue is that it seems when bash subprocess gets terminated, reader does not get EOF and so
io.Copy
does not end, sowg.Wait
never finishes, so we never get tocmd.Wait
. If I comment outwg.Wait
, thencmd.Wait
correctly reaps the subprocess (but of course it could mean not everything gets copied).Currently, documentation of
StdoutPipe
andStderrPipe
requires:But it seems it is not possible to always know when all reads from the pipe have completed?
What did you expect to see?
And programs terminates.
What did you see instead?
And program never terminates.
The text was updated successfully, but these errors were encountered: