Detect zombie child process - go

My golang program starts a service program which is supposed to run forever, like this:
cmd := exec.Command("/path/to/service")
cmd.Start()
I do NOT want to wait for the termination of "service" because it is supposed to be running forever. However, if service starts with some error (e.g. it will terminate if another instance is already running), the child process will exit and become zombie.
My question is, after cmd.Start(), can I somehow detect if the child process is still running, rather than becomes a zombie? The preferred way might be:
if cmd.Process.IsZombie() {
... ...
}
or,
procStat := cmd.GetProcessStatus()
if procStat.Zombie == true {
... ...
}
i.e. I hope there are some way to get the status of a (child) process without waiting for its exit code, or, to "peek" its status code without blocking.
Thanks!

Judging from the docs the only way to get the process state is to call os.Process.Wait. So it seems you will have to call wait in a goroutine, and then you can easily check if that goroutine has exited yet:
var cmd exec.Cmd
done := make(chan error, 1)
go func() {
done <- cmd.Wait()
}()
select {
case err := <-done:
// inspect err to check if service exited normally
default:
// not done yet
}

The best solution (for me) is:
add a signal handler listen for SIGCHLD
on receiving SIGCHLD, call cmd.Wait()
This way, the zombie process will disappear.

Related

os.Wait() does not wait for program termination in golang

At some point throughout my code execution, I want my program to start an editor (doesn't matter which one) for the user to perform some live editing.
I need my program to halt at this point and until the user decides to close the editor (more or less how git rebase works)
Here is how I go about this
func main() {
fpath := os.TempDir() + "/afile.txt"
f, err := os.Create(fpath)
if err != nil {
log.Fatal(err)
}
defer f.Close()
cmd := exec.Command("/usr/local/bin/code", fpath)
err = cmd.Start()
if err != nil {
log.Fatal(err)
}
err = cmd.Wait()
if err != nil {
fmt.Println(err)
}
}
No error is ever printed, however the above code, although it does of course open vscode, it terminates (returns) before the user closes the editor.
Shouldn't cmd.Wait() be handling this?
The program is executed on MacOS Catalina fwiw.
Shouldn't cmd.Wait() be handling this?
Yes, and it does. Go waits as intended, it's your invocation of /usr/local/bin/code which is incorrect, and does not wait. The default behavior of code is to exit immediately after spawning the VSCode window. It does not wait for the window to close, and so Go cannot wait for the window to close.
Try simply typing code in your terminal. You'll find that it exits immediately, even thought your VSCode window is still open.
To make code block until the editor window is closed (thereby allowing Go to wait), you need to pass the -w or --wait flag to it. Again, try code -w in your terminal. You'll find the terminal command blocks until the VSCode window is closed.
Practically, you only need to change this...
cmd := exec.Command("/usr/local/bin/code", fpath)
to this:
cmd := exec.Command("/usr/local/bin/code", "-w", fpath)
// or
// cmd := exec.Command("/usr/local/bin/code", "--wait", fpath)
As per https://golang.org/pkg/os/exec/#Cmd.Start
Start starts the specified command but does not wait for it to
complete.
If Start returns successfully, the c.Process field will be set.
The Wait method will return the exit code and release associated
resources once the command exits.
If you could strace code you'll find +++ exited with 0 +++ at the bottom, in linux.
Basically, the command that starts vscode exits, clones (a type of fork) and thus doesn't wait to return.
strace code -w actually waits for vscode to exit.

error handling when running cmd.Wait() with goroutine

I tried to call some external commands non-blocking in Golang, so I used
cmd.Start()
and
go cmd.Wait()
Although I don't need to wait for the command to run successfully, the reason I run cmd.wait() is because the document mentions Wait releases any resources associated with the Cmd." So I don't want to cause a resource leak.
However, this usage will cause linter to report an error, reminding me that I have not handled the error.
Error return value of `cmd.Wait` is not checked (errcheck)
go cmd.Wait()
How do I handle error for go cmd.Wait()? Or, if I don't run go cmd.Wait(), will it cause a resource leak?
add:
One reason I use go cmd.Wait() is that if I don't use it, the external process I started will become a zombie process when it is exited. I haven't figured out why this is happening.
Why do you want to run cmd.Wait() as a goroutine? And if you really need to do this in a goroutine then you can try something like this. Basically wrapping up the cmd.Wait() command inside an inline go func
go func(){
_ := cmd.Wait()
}()
You can do an error check also inside the go function.
If "cmd" goes out of scope the garbage collector will free its reference.
func execCommand() {
exec.Command("something").Start()
}
func main() {
execCommand()
time.Sleep(time.Minute * 1)
}
So something like this will have its resource freed when the command is executed
exec.Wait command is used to get the exit code and waits for copying from stdout or something like that.
If you still wanna use cmd.Wait you have to start it as a seperate Function or you could just use exec.Run
go func(){
err := cmd.Run() //or cmd.Wait()
if err != nil {
fmt.println("error ", err)
}
}()

Sending SIGTSTP suspends entire program

I'm trying to send a SIGTSTP signal to a child process. The problem I'm facing is that sending SIGTSTP to the child process halts my entire program and the caller is unable to proceed with execution of the rest of the program. Here's my code
cmd := exec.Command("ping", "google.com")
stdout, _ := cmd.StdoutPipe()
cmd.Start()
io.Copy(os.Stdout, stdout)
cmd.Wait()
Running this code, I get output from ping google.com printed on the terminal. When I hit ctrl-z, the output is stopped, but the program is not longer able to accept signals or do anything else unless SIGCONT is sent to the child process. Am I missing something? How do I suspend the child process but resume execution of the caller? Thanks.
Wait waits for the command to exit. Your child process isn't exiting, it's just paused, so Wait doesn't return.

How to verify that parent process has exited in golang on Windows?

So I am writing a small utility which should be able to update itself (replace it's own binary).
The best way to do this on Windows seems to be:
Download the new version of the binary as my.exe.new
my.exes runs my.exe.new and exits
my.exe.new waits for my.exe to exit
my.exe.new copies itself as my.exe
my.exe.new starts another copy of itself as my.exe and exits
my.exe waits for my.exe.new to exit
my.exe removes my.exe.new
Now for all of this to work I have to be able to synchronize the state between the processes (being able to know when the parent has exited), but it seems that os.Getppid (nor syscall.Getppid) in golang Windows is not implemented as it always returns -1.
I've seen that patches are underway, but I am not willing to patch my standard libraries.
Is there an easy way to make Getppid working on even older versions of Go (perhaps reimplementing it?), or perhaps anyone can suggest a better method of synchronizing between the process state?
The thing which comes to mind is binding on a socket, but thats a big hacky.
Perhaps passing a pipe to the child process, and the child waiting for the pipe to close?
Thanks
You could use os.Stdin with exec.Cmd, now I don't have access to windows to test this, however the same concept should apply there just fine:
var child = flag.Bool("child", false, "damn children and their music")
func init() {
flag.Parse()
}
func main() {
if *child {
fmt.Println("child start", time.Now())
// wait until the parent dies and bufio closes the stdin
ioutil.ReadAll(os.Stdin)
fmt.Println("the parent is dead", time.Now())
time.Sleep(5 * time.Second)
fmt.Println("tada\n")
} else {
fmt.Fprintln(os.Stdout, time.Now())
cmd := exec.Command(os.Args[0], "-child")
cmd.Stdout = os.Stdout //not needed in a real program.
//this is important, bufio will close after the parent exits,
// unlike os.Stdin which screws up, at least on linux
cmd.Stdin = bufio.NewReader(os.Stdin)
fmt.Println("giving painful birth:", cmd.Start())
time.Sleep(2 * time.Second)
}
}
The parent process can pass its PID to the child.
You could use a command-line parameter or an environment variable to do this.

golang handling kill in a process started by cmd.Start

I have two go programs. ProgA starts ProgB using cmd.Start(). From ProgA I try to kill ProgB, but ProgB shouldn't get killed immediately, it has to do some cleanup before dying. So I'm using signal.Notify in ProgB to handle sigcall.SIGKILL but whenever ProgA calls progb.Process.Kill() it doesn't seem to notify ProgB(write contents to sigc channel)
in ProgB I have the notify like this:
signal.Notify(sigc, syscall.SIGKILL)
go func() {
fmt.Println("started listening")
<-sigc
fmt.Println("sig term")
cleanUp()
os.Exit(1)
}()
someLongRunningCode()
is there something I'm missing out? I'm sure that ProgA sends a SIGKILL because cmd.Process.Kill() internally does a process.Signal(SIGKILL)
SIGKILL cannot be trapped by recieving process - kernel will force process termination. You may send SIGTERM to process and handle it on other side - it is a conventional method to stop an application.

Resources