How do I send a signal to a child process? - go

I'm having trouble sending a signal from a parent process and receiving it in the child process.
This is the code for the child process. It exits when it receives SIGINT.
// child.go
func main() {
stop := make(chan os.Signal, 1)
signal.Notify(stop, os.Interrupt)
fmt.Println("started")
<-stop
fmt.Println("stopped")
}
This is the parent process. It starts child.go, sends SIGINT, then waits for it to exit.
// main.go
func main() {
// Start child process
cmd := exec.Command("go", "run", "child.go")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Start()
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Start: " + err.Error())
return
}
// Wait, then send signal
time.Sleep(time.Millisecond * 500)
err = cmd.Process.Signal(os.Interrupt)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Signal: " + err.Error())
return
}
// Wait for child process to finish
err = cmd.Wait()
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Wait: " + err.Error())
}
return
}
This code should print started\nstopped to show that it worked as expected, but it only prints started and hangs at cmd.Wait(), meaning the child process did not receive the signal.
When I run go run child.go it works fine, so I don't think the problem is with that file. I understand that func (*Process) Signal doesn't work on Windows; I am using Linux.
How can I fix the code so that the child process gets the signal sent by the parent process?

As mentioned by #JimB in the comments section, the go run is your problem.
go run child.go will compile child and execute it as it's own process. If you run a ps after go run child.go, you will see two processes running.
The process you are watching and signalling is the go executable, not the child.
Replace the exec.Command("go", "run", "child.go") with the compiled binary exec.Command("child")and it should work.

Related

Capture stdout from exec.Command line by line and also pipe to os.Stdout

Can anyone help ?
I have an application I am running via exec.CommandContext (so I can cancel it via ctx). it would normally not stop unless it errors out.
I currently have it relaying its output to os.stdOut which is working great. But I also want to get each line via a channel - the idea behind this is I will look for a regular expression on the line and if its true then I will set an internal state of "ERROR" for example.
Although I can't get it to work, I tried NewSscanner. Here is my code.
As I say, it does output to os.StdOut which is great but I would like to receive each line as it happens in my channel I setup.
Any ideas ?
Thanks in advance.
func (d *Daemon) Start() {
ctx, cancel := context.WithCancel(context.Background())
d.cancel = cancel
go func() {
args := "-x f -a 1"
cmd := exec.CommandContext(ctx, "mydaemon", strings.Split(args, " ")...)
var stdoutBuf, stderrBuf bytes.Buffer
cmd.Stdout = io.MultiWriter(os.Stdout, &stdoutBuf)
cmd.Stderr = io.MultiWriter(os.Stderr, &stderrBuf)
lines := make(chan string)
go func() {
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
fmt.Println("I am reading a line!")
lines <- scanner.Text()
}
}()
err := cmd.Start()
if err != nil {
log.Fatal(err)
}
select {
case outputx := <-lines:
// I will do somethign with this!
fmt.Println("Hello!!", outputx)
case <-ctx.Done():
log.Println("I am done!, probably cancelled!")
}
}()
}
Also tried using this
go func() {
scanner := bufio.NewScanner(&stdoutBuf)
for scanner.Scan() {
fmt.Println("I am reading a line!")
lines <- scanner.Text()
}
}()
Even with that, the "I am reading a line" never gets out, I also debugged it and it neve enters the "for scanner.."
Also tried scanning on &stderrBuf, same, nothing enters.
cmd.Start() does not wait for the command to finish. Also, cmd.Wait() needs to be called to be informed about the end of the process.
reader, writer := io.Pipe()
cmdCtx, cmdDone := context.WithCancel(context.Background())
scannerStopped := make(chan struct{})
go func() {
defer close(scannerStopped)
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
}()
cmd := exec.Command("ls")
cmd.Stdout = writer
_ = cmd.Start()
go func() {
_ = cmd.Wait()
cmdDone()
writer.Close()
}()
<-cmdCtx.Done()
<-scannerStopped
scannerStopped is added to demonstrate that the scanner goroutine stops now.
reader, writer := io.Pipe()
scannerStopped := make(chan struct{})
go func() {
defer close(scannerStopped)
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
}()
cmd := exec.Command("ls")
cmd.Stdout = writer
_ = cmd.Run()
go func() {
_ = cmd.Wait()
writer.Close()
}()
<-scannerStopped
And handle the lines as it helps.
Note: wrote this in a bit of hurry. Let me know if anything is unclear or not correct.
For a correct program using concurrency and goroutines, we should try to show there are no data races, the program can't deadlock, and goroutines don't leak. Let's try to achieve this.
Full code
Playground: https://play.golang.org/p/Xv1hJXYQoZq. I recommend copying and running locally, because the playground doesn't stream output afaik and it has timeouts.
Note that I've changed the test command to % find /usr/local, a typically long-running command (>3 seconds) with plenty of output lines, since it is better suited for the scenarios we should test.
Walkthrough
Let's look at the Daemon.Start method. At the start, it is mostly the same. Most noticeably, though, the new code doesn't have a goroutine around a large part of the method. Even without this, the Daemon.Start method remains non-blocking and will return "immediately".
The first noteworthy fix is these updated lines.
outR, outW := io.Pipe()
cmd.Stdout = io.MultiWriter(outW, os.Stdout)
Instead of constructing a bytes.Buffer variable, we call io.Pipe. If we didn't make this change and stuck with a bytes.Buffer, then scanner.Scan() will return false as soon as there is no more data to read. This can happen if the command writes to stdout only occasionally (even a millisecond apart, for this matter). After scanner.Scan() returns false, the goroutine exits and we miss processing future output.
Instead, by using the read end of io.Pipe, scanner.Scan() will wait for input from the pipe's read end until the pipe's write end is closed.
This fixes the race issue between the scanner and the command output.
Next, we construct two closely-related goroutines: the first to consume from <-lines, and the second to produce into lines<-.
go func() {
for line := range lines {
fmt.Println("output line from channel:", line)
...
}
}()
go func() {
defer close(lines)
scanner := bufio.NewScanner(outR)
for scanner.Scan() {
lines <- scanner.Text()
}
...
}()
The consumer goroutine will exit when the lines channel is closed, as the closing of the channel would naturally cause the range loop to terminate; the producer goroutine closes lines upon exit.
The producer goroutine will exit when scanner.Scan() returns false, which happens when the write end of the io.Pipe is closed. This closing happens in upcoming code.
Note from the two paragraphs above that the two goroutines are guaranteed to exit (i.e. will not leak).
Next, we start the command. Standard stuff, it's a non-blocking call, and it returns immediately.
// Start the command.
if err := cmd.Start(); err != nil {
log.Fatal(err)
}
Moving on to the final piece of code in Daemon.Start. This goroutine waits for the command to exit via cmd.Wait(). Handling this is important because the command may for reasons other than Context cancellation.
Particularly, we want to close the write end of the io.Pipe (which, in turn, closes the output lines producer goroutine as mentioned earlier).
go func() {
err := cmd.Wait()
fmt.Println("command exited; error is:", err)
outW.Close()
...
}()
As a side note, by waiting on cmd.Wait(), we don't have to separately wait on ctx.Done(). Waiting on cmd.Wait() handles both exits caused by natural reasons (command successfully finished, command ran into internal error etc.) and exits caused by Context-cancelation.
This goroutine, too, is guaranteed to exit. It will exit when cmd.Wait() returns. This can happen either because the command exited normally with success; exited with failure due to a command error; or exited with failure due to Context cancelation.
That's it! We should have no data races, no deadlocks, and no leaked goroutines.
The lines elided ("...") in the snippets above are geared towards the Done(), CmdErr(), and Cancel() methods of the Daemon type. These methods are fairly well-documented in the code, so these elided lines are hopefully self-explanatory.
Besides that, look for the TODO comments for error handling you may want to do based on your needs!
Test it!
Use this driver program to test the code.
func main() {
var d Daemon
d.Start()
// Enable this code to test Context cancellation:
// time.AfterFunc(100*time.Millisecond, d.Cancel)
<-d.Done()
fmt.Println("d.CmdErr():", d.CmdErr())
}
You have to scan stdoutBuf instead of os.Stdin:
scanner := bufio.NewScanner(&stdoutBuf)
The command is terminated when the context canceled. If it's OK to read all output from the command until the command is terminated, then use this code:
func (d *Daemon) Start() {
ctx, cancel := context.WithCancel(context.Background())
d.cancel = cancel
args := "-x f -a 1"
cmd := exec.CommandContext(ctx, "mydaemon", strings.Split(args, " ")...)
stdout, err := cmd.StdoutPipe()
if err != nil {
log.Fatal(err)
}
err = cmd.Start()
if err != nil {
log.Fatal(err)
}
go func() {
defer cmd.Wait()
scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
s := scanner.Text()
fmt.Println(s) // echo to stdout
// Do something with s
}
}()
}
The command is terminated when the context is canceled.
Read on stdout returns io.EOF when the command is terminated. The goroutine breaks out of the scan loop when stdout returns an error.

Check if exec.Cmd is done running in Go

I'm trying to check if an exec.Cmd is running in these scenarios:
Before I actually started the command
After the command has started, but before it finished
After the command has finished
This will allow me to kill this command if it is running so that I can start it again with different parameters.
A simple use case below:
c := exec.Command("omxplayer", "video.mp4")
s, _ := c.StdinPipe() // use the pipe to send the "q" string to quit omxplayer
log.Printf("Running (false): %v\n", checkRunning(c)) // prints false: command has not started yet
c.Start()
log.Printf("Running (true): %v\n", checkRunning(c)) // print true: command has started
time.AfterFunc(3*time.Second, func() {
log.Println("about to quit process...")
log.Printf("Running (true): %v\n", checkRunning(c)) // prints true: command still running at this point
s.Write([]byte("q"))
})
log.Println("waiting for command to end")
log.Printf("Running (true): %v\n", checkRunning(c)) // prints true: command still running at this point
c.Wait()
log.Println("command should have ended by now")
log.Printf("Running (false): %v\n", checkRunning(c)) // prints false: command ended at this point
Here's the best that I could come up with:
func checkRunning(cmd *exec.Cmd) bool {
if cmd == nil || cmd.ProcessState != nil && cmd.ProcessState.Exited() || cmd.Process == nil {
return false
}
return true
}
It works for the use case above, but it seems overly complicated and I'm not sure how reliable it is.
Is there a better way?
Maybe run synchronously in a goroutine and put the result on a channel you can select on?
c := exec.Command("omxplayer", "video.mp4")
// setup pipes and such
ch := make(chan error)
go func(){
ch <- c.Run()
}()
select{
case err := <- ch:
// done! check error
case .... //timeouts, ticks or anything else
}
A slightly different approach to captncraig's answer that worked for me:
c := exec.Command("omxplayer", "video.mp4")
err := c.Start() // starts the specified command but does not wait for it to complete
// wait for the program to end in a goroutine
go func() {
err := c.Wait()
// logic to run once process finished. Send err in channel if necessary
}()

Go - close an external application

I'm using Go on an OSX machine and trying to make a program to open an external application and then after few seconds, close it - the application, not exit the Go script.
I'm using the library available on https://github.com/skratchdot/open-golang to start the app and it works fine. I also already have the timeout running. But the problem comes when I have to close the application.
Would someone give a hint of how I would be able to exit the app?
Thanks in advance.
It looks like that library is hiding details that you'd use to close the program, specifically the process ID (PID).
If you launch instead with the os/exec package or get a handle on that PID then you can use the Process object to kill or send signals to the app to try and close it gracefully.
https://golang.org/pkg/os/#Process
Thank you guys for the help. I would able to do what I was trying with the following code.
cmd := exec.Command(path string)
err := cmd.Start()
if err != nil {
log.Printf("Command finished with error: %v", err)
}
done := make(chan error, 1)
go func() {
done <- cmd.Wait()
}()
select {
case <-time.After(30 * time.Second): // Kills the process after 30 seconds
if err := cmd.Process.Kill(); err != nil {
log.Fatal("failed to kill: ", err)
}
<-done // allow goroutine to exit
log.Println("process killed")
indexInit()
case err := <-done:
if err!=nil{
log.Printf("process done with error = %v", err)
}
}
if err != nil {
log.Fatal(err)
}
log.Printf("Waiting for command to finish...")
//timer() // The time goes by...
err = cmd.Wait()
}
I placed that right after start the app with the os/exec package as #JimB recommended.

Golang cant kill parent process from child

Last days i am kinda struggling with forking a process and kill the parent from the forked one(child)
I don't know why but it seems not to kill the parent at all. first i tough it where the open connections that keeps the process running because of the graceful shutdown process, But its not that issue.
if i send SIGTERM to the parent from the terminal it worked perfect, but when the child send SIGTERM it does not stop, force quitting is no option cause of graceful shutdown.
edit * the processes are still in the process list. Maybe that is for parent to track its children?
Some code that does the fork, maybe i do something wrong here
func (s *Server) Upgrade() error {
tl := s.listener.(*listener)
addr := s.listener.Addr()
name := fmt.Sprintf("%s:%s->", addr.Network(), addr.String())
os.Setenv("PROX_NAME", name)
fl, err := tl.File()
if err != nil {
return fmt.Errorf("Failed to extract file desciptor, %v", err)
}
fd := fl.Fd()
argv0, err := exec.LookPath(os.Args[0])
if err != nil {
return fmt.Errorf("Failed to execute lookpath, %v", err)
}
noCloseOnExec(fd)
files := make([]*os.File, fd+1)
files[syscall.Stdin] = os.Stdin
files[syscall.Stdout] = os.Stdout
files[syscall.Stderr] = os.Stderr
files[fd] = os.NewFile(fd, name)
wd, err := os.Getwd()
if err != nil {
return err
}
os.Setenv("GPROXY_FD", fmt.Sprintf("%d", fd))
os.Setenv("GPROXY_PID", fmt.Sprintf("%d", syscall.Getpid()))
args := []string{"gproxy", "-debug", "start"}
_, err = os.StartProcess(argv0, args, &os.ProcAttr{
Dir: wd,
Env: os.Environ(),
Files: files,
})
return err
}
the termination of the parent
func termParentProcess() error {
pid := syscall.Getppid()
return syscall.Kill(pid, syscall.SIGTERM)
}
Long story short, you can't fork, there's a very old and ongoing issue for it here.
You could use something like https://github.com/icattlecoder/godaemon but the parent will have to terminate itself not the child.
var isChild = flag.Bool("child", false, "parent pid")
func main() {
flag.Parse()
var s string
if !*isChild { //parent
fmt.Println("forking")
files := make([]*os.File, 3)
files[syscall.Stdin] = os.Stdin
files[syscall.Stdout] = os.Stdout
files[syscall.Stderr] = os.Stderr
fmt.Println(os.StartProcess(os.Args[0], append(os.Args, "-child"), &os.ProcAttr{
Dir: "/tmp",
Env: os.Environ(),
Files: files,
}))
fmt.Scan(&s) // block
} else {
ppid := os.Getppid()
fmt.Println("ppid", ppid, "kill:", syscall.Kill(ppid, syscall.SIGTERM))
time.Sleep(5 * time.Second)
fmt.Println("child dying")
}
}
The problem my parent process wont terminate on a TERM signal is because of a inner for loop that wont break. I fixed the for loop and let the main function return. If in Go the main returns the program exits. sorry for bothering you guys with a big mistake of myself

Exec external program/script and detect if it requests user input

given the following example:
// test.go
package main
import (
"fmt"
"os/exec"
)
func main() {
cmd := exec.Command("login")
in, _ := cmd.StdinPipe()
in.Write([]byte("user"))
out, err := cmd.CombinedOutput()
if err != nil {
fmt.Println("error:", err)
}
fmt.Printf("%s", out)
}
How can I detect that the process is not going to finish, because it is waiting for user input?
I'm trying to be able to run any script, but abort it if for some reason it tries to read from stdin.
Thanks!
Detecting that the process is not going to finish is a difficult problem. In fact, it is one of the classic "unsolvable" problems in Computer Science: the Halting Problem.
In general, when you are calling exec.Command and will not be passing it any input, it will cause the program to read from your OS's null device (see documentation in the exec.Cmd fields). In your code (and mine below), you explicitly create a pipe (though you should check the error return of StdinPipe in case it is not created correctly), so you should subsequently call in.Close(). In either case, the subprocess will get an EOF and should clean up after itself and exit.
To help with processes that don't handle input correctly or otherwise get themselves stuck, the general solution is to use a timeout. In Go, you can use goroutines for this:
// Set your timeout
const CommandTimeout = 5 * time.Second
func main() {
cmd := exec.Command("login")
// Set up the input
in, err := cmd.StdinPipe()
if err != nil {
log.Fatalf("failed to create pipe for STDIN: %s", err)
}
// Write the input and close
go func() {
defer in.Close()
fmt.Fprintln(in, "user")
}()
// Capture the output
var b bytes.Buffer
cmd.Stdout, cmd.Stderr = &b, &b
// Start the process
if err := cmd.Start(); err != nil {
log.Fatalf("failed to start command: %s", err)
}
// Kill the process if it doesn't exit in time
defer time.AfterFunc(CommandTimeout, func() {
log.Printf("command timed out")
cmd.Process.Kill()
}).Stop()
// Wait for the process to finish
if err := cmd.Wait(); err != nil {
log.Fatalf("command failed: %s", err)
}
// Print out the output
fmt.Printf("Output:\n%s", b.String())
}
In the code above, there are actually three main goroutines of interest: the main goroutine spawns the subprocess and waits for it to exit; a timer goroutine is sent off in the background to kill the process if it's not Stopped in time; and a goroutine that writes the output to the program when it's ready to read it.
Although this would not allow you to "detect" the program trying to read from stdin, I would just close stdin. This way, the child process will just receive an EOF when it tried to read. Most programs know how to handle a closed stdin.
// All error handling excluded
cmd := exec.Command("login")
in, _ := cmd.StdinPipe()
cmd.Start()
in.Close()
cmd.Wait()
Unfortunately, this means you can't use combined output, the following code should allow you to do the same thing. It requires you to import the bytes package.
var buf = new(bytes.Buffer)
cmd.Stdout = buf
cmd.Stderr = buf
After cmd.Wait(), you can then do:
out := buf.Bytes()
I think the solution is to run the child process with closed stdin - by adjusting the Cmd.Stdin appropriately and then Runinng it afterwards instead of using CombinedOutput().
Finally, I'm going to implement a combination of Kyle Lemons answer and forcing the new process have it's own session without a terminal attached to it, so that the executed comand will be aware that there is no terminal to read from.
// test.go
package main
import (
"log"
"os/exec"
"syscall"
)
func main() {
cmd := exec.Command("./test.sh")
cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
out, err := cmd.CombinedOutput()
if err != nil {
log.Fatal("error:", err)
}
log.Printf("%s", out)
}

Resources