On MAC OS I want process create/exit (more so exit) callbacks in Golang. Once the process exits I have to do some activity.
Any pointers or help is appreciated.
From run command documentation:
Run starts the specified command and waits for it to complete.
You can run a command with:
cmd := exec.Command("firefox")
err := cmd.Run()
if err != nil {
log.Fatal(err)
}
// DO SOMETHING AFTER PROCESS EXIT
Related
I have an application that runs on a schedule. I want the application to check if an instance of it is already running. If there is an instance running then the application exits. If no instance of the application is running then it continues on performing it's job.
I thought about using a lock or PID file and checking for the existence of the file. Is there something I could do with os.Getpid() or os.Executable() and accomplish this?
I went the route of just creating a pid file and checking for that.
pidfile, err := os.OpenFile(watcherconfig.TeleportFileWatcher.PIDFile, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
if err != nil {
if os.IsExist(err) {
watcherlog.InfoLogger.Println("An instance of the file watcher application is already running.")
os.Exit(0)
}
}
defer pidfile.Close()
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.
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)
}
}()
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()
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.