I have a simple Go web server:
package main
import (
"net/http"
"strings"
)
func sayHello(w http.ResponseWriter, r *http.Request) {
message := r.URL.Path
message = strings.TrimPrefix(message, "/")
message = "Hello " + message
w.Write([]byte(message))
}
func main() {
http.HandleFunc("/", sayHello)
if err := http.ListenAndServe(":8080", nil); err != nil {
panic(err)
}
}
And a Makefile that runs it and caches the PID to a PID file:
GOSRC=$(wildcard *.go)
.PHONY: kill
kill:
echo ""
.PHONY: neaten
neaten:
go clean
go mod tidy
go mod download
goimports -w $(GOSRC)
go vet $(GOSRC)
go test -cover $(GOSRC)
.PHONY: build
build: neaten
go build -o server $(GOSRC)
.PHONY: run
run: kill build
nohup ./server &
echo $! > server.pid
However, echo $! does not work for some reason, what I get is an empty server.pid file.
The process is definitely running since I can access the web server. But echo $! doesn't do anything.
I have also tried echo $$ but that also doesn't do much.
I'm also running the Makefile from the fish terminal.
When you run command from make, each command is executed in a separate shell:
run: kill build
nohup ./server &
echo $! > server.pid
The 'nohup' will execute in one shell, and echo will be executed in a different shell. As a result, the PID of the back grounded process ($!) is available in the first line, but NOT in the second.
The simple solution is to place the two command on the same line.
run: kill build
nohup ./server & echo $! > server.pid
Related
i noticed that subprocesses created using Start() will be terminated after program exit, for example:
package main
import "os/exec"
func main() {
cmd := exec.Command("sh", "test.sh")
cmd.Start()
}
when main() exits, test.sh will stop running
The subprocess should continue to run after your process ends, as long as it ends cleanly, which won't happen if you hit ^C.
What you can do is intercept the signals sent to your process so you can end cleanly.
sigchan := make(chan os.Signal, 1)
signal.Notify(sigchan,
syscall.SIGINT,
syscall.SIGKILL,
syscall.SIGTERM,
syscall.SIGQUIT)
go func() {
s := <-sigchan
// do anything you need to end program cleanly
}()
A subprocess (if not waited on within the go program) will continue to run once the go program has finished (unless the subprocess naturally finishes before the parent go program).
The problem the original poster is likely encountering is that they are probably terminating their go program early (e.g. using <Ctrl-c>), and because the go program is not exiting cleanly the subprocess it spawned is also terminated.
Below is a reduced test case that helps validate this behaviour...
First I create a bash shell script I want to run (e.g. test.sh, don't forget to chmod +x ./test.sh so the script is considered 'executable'). The script is very simple. It sleeps for 10 seconds and then either creates a new file called testfile (if it doesn't exist) or if the file already exists it will update the 'last modified' timestamp. This is important because this is how I confirm the bash script is still running once my go program finishes (which I expect to finish long before the bash script finishes due to the 10 second sleep).
#!/usr/local/bin/bash
sleep 10
touch testfile
Next, I have a simple go program, which spawns a subprocess that runs the bash script above but importantly doesn't wait for it to complete. You'll see I've also added a 2 second sleep to my go program which gives me some time to press <Ctrl-c>. Now, even though I have a 2 second sleep, this program (if left to run without me pressing <Ctrl-c>) will finish before the subprocess bash script does (which is sleeping for 10 seconds):
package main
import (
"fmt"
"log"
"os/exec"
"time"
)
func main() {
cmd := exec.Command("./test.sh")
err := cmd.Start()
if err != nil {
log.Fatal(err)
}
time.Sleep(2 * time.Second)
fmt.Println("program finished, but what about the subprocess?")
}
If I run the go program and just let it finish naturally, I can ls -l testfile and check the timestamp on it. I'll then wait 10 seconds and run the ls -l testfile again and I will see the timestamp update (which shows the subprocess finished successfully).
Now if I was to re-run the go program and this time press <Ctrl-c> before the program finishes (this is why I add the 2 second sleep), then not only will the go program exit early, but the subprocess will be terminated also. So I can wait 10 seconds or 10 hours or longer, doesn't matter. The timestamp on the testfile will not update, proving the subprocess was terminated.
Try modding you program a to use Run instead of start. In that way the Go program will wait for the sh script to finish before exiting.
package main
import (
"log"
"os/exec"
)
func main() {
cmd := exec.Command("sh", "test.sh")
err := cmd.Run()
if err != nil {
log.Fatalln(err)
}
}
Likewise, you could always use a wait group but I think that's overkill here.
You could also just a go routine with or without a wait group. Depends on if you want go to wait for the program the sh program to complete
package main
import (
"os/exec"
)
func runOffMainProgram() {
cmd := exec.Command("sh", "test.sh")
cmd.Start()
}
func main() {
// This will start a go routine, but without a waitgroup this program will exit as soon as it runs
// regardless the sh program will be running in the background. Until the sh program completes
go runOffMainProgram()
}
The accepted answer is vague about where the signal should be handled. I think some more sophisticated techniques must be used to prevent sending interrupts to children, if at all possible.
TLDR;
So the only way to deal with ctrl-c is to anticipate the SIGINT and process that signal in the children.
I did some experimentation of my own.
go build -o ctrl-c ctrl-c.go
If the program is sent to the background, The only way to kill the main process is with kill -9 (SIGKILL).
SIGTERM (15) will not do.
$ ./ctrl-c & cmd=$! ; sleep 1 && echo kill $cmd && kill $cmd
[1] 1165918
1165918
bashed 1165926
bashed 1165927
bashed 1165928
main()
go SIGN 23 urgent I/O condition
go SIGN 23 urgent I/O condition
main()
kill 1165918
go SIGN 15 terminated
main()
$ main()
main()
main()
main()
main()
main() done.
Bash _ 1165926 EXITs
Bash q 1165927 EXITs
Bash c 1165928 EXITs
[1]+ Done ./ctrl-c
SIGINT (2) will not do.
$ ./ctrl-c & cmd=$! ; sleep 1 && echo kill $cmd && kill -INT $cmd
[1] 1167675
1167675
bashed 1167683
bashed 1167684
bashed 1167685
main()
main()
kill 1167675
go SIGN 2 interrupt
main()
balmora: ~/src/my/go/doodles/sub-process [master]
$ main()
main()
main()
main()
main()
main() done.
Bash _ 1167683 EXITs
Bash q 1167684 EXITs
Bash c 1167685 EXITs
SIGKILL kills the main process but not the bash sub-commands.
$ ./ctrl-c & cmd=$! ; sleep 1 && echo kill $cmd && kill -KILL $cmd
[1] 1170721
1170721
bashed 1170729
bashed 1170730
bashed 1170731
main()
main()
kill 1170721
[1]+ Killed ./ctrl-c
Bash _ 1170729 EXITs
Bash q 1170730 EXITs
Bash c 1170731 EXITs
However, if the go binary is running in the foreground then only children who do deal with SIGINT will be kept running. This feels like almost the opposite of the above findings
$ ./ctrl-c
1186531
bashed 1186538
bashed 1186539
bashed 1186540
main()
main()
main()
main()
main()
main()
^C
Bash c 1186540 INTs quit
Bash q 1186539 INTs ignored
Bash c 1186540 EXITs
Bash _ 1186538 INTs ignored
go SIGN 2 interrupt
go SIGN 17 child exited
6q ELAPSED 2
Bash q 1186539 EXITs
6_ ELAPSED 2
Bash _ 1186538 EXITs
go SIGN 17 child exited
main()
main()
main() done.
Anyway, the takeaway for me is that ctrl+c is forwarded to children when Cmd.Start() is used. The behavior is the same if Cmd.Run() is used, but Cmd.Run() will wait before each sub-command exits. Running the Cmd in a go routine (go func(){}()) does not change anything. If the sub-commands are started "in parallel" as a go-routine or with Cmd.Start(), the the interrupt signal will reach all of them at the same time.
To keep the sub-commands running on an interactive terminal after an interrupt, I think the sub-commands have to handle the signal and ignore it.
The code I experimented with:
package main
import (
"fmt"
"log"
"os"
"os/exec"
"os/signal"
"syscall"
"time"
)
func signs(s ...os.Signal) chan os.Signal {
signals := make(chan os.Signal, 1)
signal.Notify(signals, s...)
signal.Notify(signals,
os.Interrupt, syscall.SIGINT, syscall.SIGQUIT, // keyboard
syscall.SIGKILL, syscall.SIGHUP, syscall.SIGTERM, // os termination
syscall.SIGUSR1, syscall.SIGUSR2, // user
syscall.SIGPIPE, syscall.SIGCHLD, syscall.SIGSEGV, // os other
)
return signals
}
func interpret(signals chan os.Signal) chan os.Signal {
go func() {
for ;; {
select {
case sign := <-signals:
elog("go SIGN %#v %s", sign, sign)
}
}
}()
return signals
}
func bash(script string) {
cmd := exec.Command("/bin/bash", "-c", script )
cmd.Stdout = os.Stderr
err := cmd.Start()
//err := cmd.Run()
if err != nil {
log.Fatal(err)
}
elog("bashed %d", cmd.Process.Pid)
}
func main() {
fmt.Println(os.Getpid())
signals := interpret(signs())
signals = signals
//go bash(`
bash(`
trap ' echo Bash _ $$ INTs ignored; ' SIGINT
trap ' echo Bash _ $$ QUITs ignored; ' SIGQUIT
trap ' echo Bash _ $$ EXITs' EXIT
sleep 6;
echo 6_ $( ps -o etimes -p $$ )
#for i in {1..60}; do echo -n _; sleep 0.1; done; echo
`)
// go bash(`
bash(`
trap ' echo Bash q $$ INTs ignored; ' SIGINT
trap ' echo Bash q $$ QUITs; exit ' SIGQUIT
trap ' echo Bash q $$ EXITs; ' EXIT
sleep 6;
echo 6q $( ps -o etimes -p $$ )
#for i in {1..60}; do echo -n q; sleep 0.1; done; echo
`)
//go bash(`
bash(`
trap ' echo Bash c $$ INTs quit; exit ' SIGINT
trap ' echo Bash c $$ QUITs ignored; ' SIGQUIT
trap ' echo Bash c $$ EXITs' EXIT
sleep 6;
echo 6c $( ps -o etimes -p $$ )
#for i in {1..60}; do echo -n c; sleep 0.1; done; echo
`)
go func() {
for ;; {
time.Sleep(time.Millisecond * 333)
elog("main()")
}
}()
time.Sleep(3 * time.Second)
elog("main() done.")
}
func echo(a ...interface{}) {
_, err := fmt.Println(a...)
if err != nil {
fmt.Println("ERR ", err.Error())
}
}
func elog(form string, arg ...interface{}) {
println(fmt.Sprintf(form, arg...))
}
I need to know in my shell script the output of some docker exec commands, for example I have an nginx container and in my script I run:
docker exec -it containerName /etc/init.d/nginx configtest
I want to continue script execution only if nginx config test is success, not when fail.
I've tried out to use $?, but it is 0 even then configtest output is fail (because docker exec is successfully executed, as I understand).
I found this to be working quite well:
docker exec -t -i my-container sh -c 'my-command; exit $?'
Translating my comment into an answer. This should work:
docker exec -it mynginx /etc/init.d/nginx configtest && echo "pass" || echo "fail"
It works for me.
The api/client/exec.go#L97-L100 does get the exit code:
var status int
if _, status, err = getExecExitCode(cli, execID); err != nil {
return err
}
That comes from api/client/utils.go#L84-L97
// getExecExitCode perform an inspect on the exec command. It returns
// the running state and the exit code.
func getExecExitCode(cli *DockerCli, execID string) (bool, int, error) {
resp, err := cli.client.ContainerExecInspect(execID)
if err != nil {
// If we can't connect, then the daemon probably died.
if err != lib.ErrConnectionFailed {
return false, -1, err
}
return false, -1, nil
}
return resp.Running, resp.ExitCode, nil
}
So if your command fail, you will get the exit code.
Although, as mentioned here, you could use nginx -t instead of configtest.
If you don't want to immediately respond to exit code returned from a process triggered by docker exec command, you could do something like this.
docker exec my-app-container /app/scripts/test.sh
UNIT_TEST_EXIT_CODE=$? # Get exit code from last command (/app/scripts/test.sh)
... # do some stuff after unit testing
exit $UNIT_TEST_EXIT_CODE
I used this approach in the context of CI/CD pipelines.
A new version ready to test to run a bash command:
docker exec -t -i oracle bash -c "echo hola && exit"
hola
For fun and to better learn Go, I'm trying to re-implement antigen in Go.
Problem is: source is a shell built-in function, so I can't call it with os/exec Command function, because it expects an executable in PATH.
How can I do this? And, is it possible to make a source from inside a go program affect the user shell?
You can write the command directly in the terminal device. But, to do that, first you need to know which device is using the user. A script that executes your program can be a solution.
#!/bin/bash
echo Running from foo script, pid = $$
go run foo.go `tty`
Then, the program has to write the commands to the terminal device.
package main
import (
"C"
"fmt"
"os"
"syscall"
"unsafe"
)
func main() {
// Get tty path
if len(os.Args) < 2 {
fmt.Printf("no tty path\n")
os.Exit(1)
}
ttyPath := os.Args[1]
// Open tty
tty, err := os.Open(ttyPath)
if err != nil {
fmt.Printf("error opening tty: %s\n", err.Error())
os.Exit(2)
}
defer tty.Close()
// Write a command
cmd := "echo Hello from go, pid = $$\n"
cmdstr := C.CString(cmd)
cmdaddr := uintptr(unsafe.Pointer(cmdstr))
for i := range []byte(cmd) {
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, tty.Fd(), syscall.TIOCSTI, cmdaddr+uintptr(i))
if uintptr(err) != 0 {
fmt.Printf("syscall error: %s\n", err.Error())
os.Exit(3)
}
}
}
Here is an example output:
$ echo $$
70318
$ ./foo
Running from foo script, pid = 83035
echo Hello from go, pid = $$
$ echo Hello from go, pid = $$
Hello from go, pid = 70318
Note that I am executing the script with ./ not source, so the PID of the script differs. But later, the command executed by the go program has the same PID.
I've run into an issue with the os/exec library. I want to run a shell and pass it multiple commands to run, but it's failing when I do. Here's my test code:
package main
import (
"fmt"
"os/exec"
)
func main() {
fmt.Printf("-- Test 1 --\n`")
command1 := fmt.Sprintf("\"%s\"", "pwd") // this one succeeds
fmt.Printf("Running: %s\n", command1)
cmd1 := exec.Command("/bin/sh", "-c", command1)
output1,err1 := cmd1.CombinedOutput()
if err1 != nil {
fmt.Printf("error: %v\n", err1)
return
}
fmt.Printf(string(output1))
fmt.Printf("-- Test 2 --\n")
command2 := fmt.Sprintf("\"%s\"", "pwd && pwd") // this one fails
fmt.Printf("Running: %s\n", command2)
cmd2 := exec.Command("/bin/sh", "-c", command2)
output2,err2 := cmd2.CombinedOutput()
if err2 != nil {
fmt.Printf("error: %v\n", err2)
return
}
fmt.Printf(string(output2))
}
When running this I get an error 127 on the second example. It seems like it's looking for a literal "pwd && pwd" command instead of evaluating it as a script.
If I do the same thing from the command line it works just fine.
$ /bin/sh -c "pwd && pwd"
I'm using Go 1.4 on OS X 10.10.2.
the quotes are for your shell where you typed the command line, they should not be included when programatically launching an app
just make this change and it will work:
command2 := "pwd && pwd" // you don't want the extra quotes
I am using cmd.go (see below) to execute a docker command but it fails. I do the following steps to execute and get the following error.
go build
sudo ./cmd
Output:
docker run -v ~/exp/a.out:/a.out ubuntu:14.04 /a.out -m 10m
2014/10/16 14:32:12 exit status 1
On the other hand running directly as
sudo docker run -v ~/exp/a.out:/a.out ubuntu:14.04 /a.out -m 10m
results in the correct output of a.out.
Hello World
This is the code of cmd.go. How can I get it to work? Thanks!
package main
import (
"fmt"
"log"
"os/exec"
"strings"
)
func ExampleCmd_Output() {
//out, err := exec.Command("date", "--version").Output() // This works
//out, err := exec.Command("docker", "--version").Output() // This works
//out, err := exec.Command(cmd, "images").Output() // Even docker images command works!
cmd := "docker"
cmdArgs := []string{"run", "-v", "~/exp/a.out:/a.out", "ubuntu:14.04", "/a.out", "-m", "10m"}
fmt.Println(cmd + " " + strings.Join(cmdArgs, " "))
out, err := exec.Command(cmd, cmdArgs...).Output()
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s", out)
}
func main() {
ExampleCmd_Output()
}
EDIT: After a comment, I tried executing the command "docker images". It works if I run the executable with sudo. That is, I am using the following line in the code now.
out, err := exec.Command(cmd, "images").Output()
After doing go build and running "sudo ./cmd", I get the output of docker images command. However, without sudo, I still get exit status 1. But with docker run command above even with sudo, I don't get an output.
Thanks to Os Exec Sudo Command in Go, I am now able to do what I want.
func main() {
cmdStr := "sudo docker run -v ~/exp/a.out:/a.out ubuntu:14.04 /a.out -m 10m"
out, _ := exec.Command("/bin/sh", "-c", cmdStr).Output()
fmt.Printf("%s", out)
}
Output:
Hello World