How to verify that parent process has exited in golang on Windows? - 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.

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.

Terminate shell pipe from interactive go cli

I have a Go program that consumes "live" input from a shell pipe, eg:
tail -f some/file | my-program
my-program is an interactive program built with rivo/tview. I want to be able to close my program with Ctrl-C and have it also terminate the tail -f that supplies input to it.
Currently I have to hit Ctrl-C twice to get back to my shell prompt. Any way I can get back to my prompt by hitting Ctrl-C once?
Adjusted my program per #torek's explanation of progress groups and observation that I can get the progress group ID using unix.Getpgid(pid):
import (
"os"
"golang.org/x/sys/unix"
)
func main() {
// do stuff with piped input
pid := os.Getpid()
pgid, err := unix.Getpgid(pid)
if err != nil {
log.Fatalf("could not get process group id for pid: %v\n", pid)
}
processGroup, err := os.FindProcess(pgid)
if err != nil {
log.Fatalf("could not find process for pid: %v\n", pgid)
}
processGroup.Signal(os.Interrupt)
}
This delivers my desired behavior from my original question.
I opted to not use syscall because of the warning I found:
Deprecated: this package is locked down. Callers should use the corresponding package in the golang.org/x/sys repository instead. That is also where updates required by new systems or versions should be applied. See https://golang.org/s/go1.4-syscall for more information.
I plan to update my program to detect whether or not it was given a pipe using the strategy outlined in this article, so when a pipe is detected, I'll do the above process group signaling on interrupt.
Any issues with that?
We'll assume a Unix-like system, using a shell that understands and engages in job control (and they all do now). When you run a command, the shell creates something called a process group or "pgroup" to hold each of the processes that make up the command. If the command is a pipeline (as this one is), each process in the pipeline gets the same pgroup-ID (see setpgid).
If the command is run in the forgeground (without &), the controlling terminal has this particular pgid assigned to it. Pressing one of the signal-generating keys, such as CTRL-C or CTRL-\, sends the corresponding signal (SIGINT and SIGQUIT in these cases) to the pgroup, using an internal killpg or equivalent. This sends the signal to every member of the pgroup.
(Backgrounding a process is simply *cough* a matter of taking back the pgid on the controlling tty, then restarting the processes in the pipeline. To make that happen is not so simple, though, as indicated by the "restarting" here.)
The likely source of the problem here is that an interactive program will place the controlling terminal into cbreak or raw mode and disable some or all signalling from keyboard keys, so that, for instance, CTRL-C no longer causes the kernel's tty module to send a signal at all. Instead, if you see a key that should cause suspension (CTRL-Z) or termination, the program has to do its own suspending or terminating. Programmers sometimes assume that this consists of simply suspending or terminating—but since the entire pipeline never got the signal in question, that's not the case, unless the entire shell pipeline consisted solely of the interactive program.
The fix is to have the program send the signal to its own pgroup, after doing any necessary cleanup (temporarily or permanently) of the controlling terminal.

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)
}
}()

How to execute multiple commands in interactive shell

My application works with all kind of shell commands, provided from the console (curl, date, ping, whatever). Now I'd like to cover the case with interactive shell commands (like mongo shell), using os/exec.
e.g. as a first step, connect to mongodb:
mongo --quiet --host=localhost blog
then perform arbitrary number of commands, getting the result on every step
db.getCollection('posts').find({status:'INACTIVE'})
and then
exit
I tried the following, but it allows me to perform only one command per mongo connection:
func main() {
cmd := exec.Command("sh", "-c", "mongo --quiet --host=localhost blog")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
stdin, _ := cmd.StdinPipe()
go func() {
defer stdin.Close()
io.WriteString(stdin, "db.getCollection('posts').find({status:'INACTIVE'}).itcount()")
// fails, if I'll do one more here
}()
cmd.Run()
cmd.Wait()
}
Is there a way to run multiple commands, getting stdout result per executed command?
As Flimzy noted, you should absolutely be using a mongo driver to work with mongo, not trying to interact with it via shell exec.
However, to answer the root question, of course you can execute multiple commands - there's no reason you can't. Every time you write to the process' stdin, it's like you're at a terminal typing into it. There's no secret limitation on that, other than processes which specifically detect if they're connected to a TTY.
Your code has several issues, though - you should definitely review the os/exec package documentation. You're calling cmd.Run, which:
starts the specified command and waits for it to complete.
And then calling cmd.Wait, which... also waits for the command to complete. You're writing to the stdin pipe in a goroutine, even though this is a very serialized process: you want to write to the pipe to execute a command, get the result, write another command, get another result... concurrency only muddles matters and should not be used here. And you're not sending newlines to tell Mongo you're done writing a command (just like you'd do in the shell - Mongo won't just start executing as soon as you enter the closing paren, you have to hit enter).
What you would want to do to interact with a process via stdin/stdout (again, noting that this is absolutely not the way to interact with a database, but could be valid for other external commands):
cmd := exec.Command("sh", "-c", "mongo --quiet --host=localhost blog")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
stdin, _ := cmd.StdinPipe()
// Start command but don't wait for it to exit (yet) so we can interact with it
cmd.Start()
// Newlines, like hitting enter in a terminal, tell Mongo you're done writing a command
io.WriteString(stdin, "db.getCollection('posts').find({status:'INACTIVE'}).itcount()\n")
io.WriteString(stdin, "db.getCollection('posts').find({status:'ACTIVE'}).itcount()\n")
// Quit tells it you're done interacting with it, otherwise it won't exit
io.WriteString(stdin, "quit()\n")
stdin.Close()
// Lastly, wait for the process to exit
cmd.Wait()

Detect zombie child process

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.

Resources