Go | ssh.Session: Is there a reason to use session.close() after session.wait() - go

Regarding Go's golang.org/x/crypto/ssh.Session package:
IIUC, session.Wait() waits for the exit status to come back and for any other failure,
so I wonder if there's a reason to session.close() after the cmd has returned.

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

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.

SIGCONT not detected by channel

I'm trying to replicate a shell environment. The following code runs within os.StartProcess and p.Wait(). It is able to receive C-z (SIGTSTP) and C-c (SIGINT) but not when I send SIGCONT from another shell with kill -CONT [PID].
sigChild := make(chan os.Signal)
defer close(sigChild)
signal.Notify(sigChild, syscall.SIGTSTP, syscall.SIGINT, syscall.SIGCONT)
defer signal.Stop(sigChild)
sigRcvd := <- sigChild
fmt.Println(sigRcvd)
I'm not sure if I missing something in my code.
It's an known issue in Go. There is an issue for it on GitHub.

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