I have some code that in Go (golang), has a few different threads running a separate executable. I want to ensure that if a user kills my process in Go, that I have a way of killing the executables I've called, is there a way to do that?
-
In same fashion that you start the process you can kill it using the Bash command of your choosing (like killall) and the `exec` package. Just create a clean up function and call it with `defer` in your main. – evanmcdonnal Jan 11 '16 at 21:00
-
1@evanmcdonnal: a defer doesn't run when you receive a signal, since the function isn't returning. You need a signal handler to catch it. – JimB Jan 11 '16 at 21:23
-
Tangentially related but worth linking to: [killing a whole process group on Linux](http://stackoverflow.com/q/392022/720999). – kostix Jan 12 '16 at 16:51
5 Answers
The only ways to ensure that the child process is killed, is to start it in the same process group, and kill the process group as a whole, or set Pdeadthsig
in the syscall.SetProcAttr
.
You can setup a signal handler for common signals like SIG_INT
and SIG_TERM
, and kill your child processes before exiting, but since you can't catch SIG_KILL
that's often not worth the effort.
See: Panic in other goroutine not stopping child process
cmd := exec.Command("./long-process")
cmd.SysProcAttr = &syscall.SysProcAttr{
Pdeathsig: syscall.SIGTERM,
}

- 2,673
- 18
- 37

- 104,193
- 13
- 262
- 255
-
4note that the syscall package is deprecated as of Go 1.4 (see https://golang.org/s/go1.4-syscall) and you're supposed to use https://godoc.org/golang.org/x/sys - I think Prctl is the method that gives you the equivalent behaviour – Nick Jan 11 '16 at 23:51
-
1@Nick, that's incorrect. The syscall package is mostly frozen, but not "deprecated". Much of the stdlib depends on it. – JimB Jan 12 '16 at 00:02
-
The document says "We can freeze and, in effect, deprecate it, however." Maybe the document should be edited to clarify what people are supposed to do – Nick Jan 12 '16 at 00:10
-
@Nick, that was just the proposal document, from when the syscall "freeze" was implemented. It's not official documentation, and is out of date. – JimB Jan 12 '16 at 01:07
-
2
-
1@Rambatino, do you have reference for that? I can't find anything in offcial documentation or source code that says its deprecated. – John Eikenberry Aug 03 '19 at 19:03
-
I've just had a look at the source code, and I too cannot find where I got that from. But I would have come across it having commented on here. – Rambatino Aug 06 '19 at 09:42
If you're on Windows, you might find this snippet helpful:
package main
import (
"os"
"os/exec"
"unsafe"
"golang.org/x/sys/windows"
)
// We use this struct to retreive process handle(which is unexported)
// from os.Process using unsafe operation.
type process struct {
Pid int
Handle uintptr
}
type ProcessExitGroup windows.Handle
func NewProcessExitGroup() (ProcessExitGroup, error) {
handle, err := windows.CreateJobObject(nil, nil)
if err != nil {
return 0, err
}
info := windows.JOBOBJECT_EXTENDED_LIMIT_INFORMATION{
BasicLimitInformation: windows.JOBOBJECT_BASIC_LIMIT_INFORMATION{
LimitFlags: windows.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE,
},
}
if _, err := windows.SetInformationJobObject(
handle,
windows.JobObjectExtendedLimitInformation,
uintptr(unsafe.Pointer(&info)),
uint32(unsafe.Sizeof(info))); err != nil {
return 0, err
}
return ProcessExitGroup(handle), nil
}
func (g ProcessExitGroup) Dispose() error {
return windows.CloseHandle(windows.Handle(g))
}
func (g ProcessExitGroup) AddProcess(p *os.Process) error {
return windows.AssignProcessToJobObject(
windows.Handle(g),
windows.Handle((*process)(unsafe.Pointer(p)).Handle))
}
func main() {
g, err := NewProcessExitGroup()
if err != nil {
panic(err)
}
defer g.Dispose()
cmd := exec.Command("notepad.exe", "noname")
if err := cmd.Start(); err != nil {
panic(err)
}
if err := g.AddProcess(cmd.Process); err != nil {
panic(err)
}
if err := cmd.Wait(); err != nil {
panic(err)
}
}
Original link: https://gist.github.com/hallazzang/76f3970bfc949831808bbebc8ca15209

- 651
- 8
- 18
One possible strategy is to keep a list of processes you're running in a global array var childProcesses = make([]*os.Process, 0)
and append to it every time you start a process.
Have your own Exit
function. Make sure that you never call os.Exit
anywhere in your code, and always call your own Exit
function instead. Your Exit function will kill all the childProcesses
for _, p := range childProcesses {
p.Kill()
}
Handle signals so they go through your own exit function, for example by doing this during initialization (somewhere near the top of your main function)
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL, syscall.SIGQUIT)
goUnsafe(func() {
var signal = <-sigs
log.Println("Got Signal", signal)
Exit(0)
})

- 720
- 4
- 8
I would suggest you to use unix.Prctl.
flag := unix.SIGHUP
if err := unix.Prctl(unix.PR_SET_PDEATHSIG, uintptr(flag), 0, 0, 0); err != nil {
return
}
A detailed example is below, new.go for child process and main.go as parent process.
//new.go
package main
func main() {
flag := unix.SIGHUP
if err := unix.Prctl(unix.PR_SET_PDEATHSIG, uintptr(flag), 0, 0, 0); err != nil {
return
}
f, _ := os.Create("./dat2")
defer f.Close()
i := 0
for {
n3, _ := f.WriteString("writes " + string(i) + "\n")
fmt.Printf("wrote %d bytes\n", n3)
f.Sync()
i += 2
time.Sleep(2 * time.Second)
}
for {
n3, _ := f.WriteString("newwrites\n")
fmt.Printf("wrote %d bytes\n", n3)
f.Sync()
time.Sleep(2 * time.Second)
}
}
//main.go
package main
import "os/exec"
func main() {
commandA := exec.Command("./new")
commandA.Start()
commandB := exec.Command("./new")
commandB.Start()
for {
}
}
-
You are mistaken -- `Pdeathsig` is still in Go's Linux `SysProcAttr`. – likebike Mar 31 '18 at 17:02
-
The `unix` package being used in the example code comes from here: https://godoc.org/golang.org/x/sys/unix – likebike Mar 31 '18 at 17:02
-
Note that this answer only applies if the *children* processes are written in golang, since they have to be changed to call unix.Prctl on themselves. – Arnavion Jul 17 '18 at 22:17
-
Note that the death-signal stuff is not implemented in all Unix variants. – torek Mar 15 '21 at 15:14
I have some code that in Go (golang), has a few different threads running a separate executable. I want to ensure that if a user kills my process in Go, that I have a way of killing the executables I've called, is there a way to do that?
It is not clear to me what you mean by "different threads running." I will assume:
- Those threads belong to "executables I've called"
- Those threads have not necessarily written in golang
- You cannot control their execution (i.e., you unable to change their code)
In this sense, we are talking about to create a process group and ensure that when you kill a process, every other process in that group must stop too. This mechanism is Operating System dependent.
In the current version, there are two possibilities (see package x/sys):
Unix: it is about to
setpgid(2)
Windows: it is about to
CreateProcess
. Please, refer to the MS Windows documentation CreateProcess. You must pass theCREATE_NEW_PROCESS_GROUP
flag (see)

- 2,772
- 2
- 15
- 21
-
`setgid` is for the group ID of the running process, as distinguished from the *process* group to which the process belongs. That is, files have uid-and-gid and this gid is a file-gid, not a process-group-ID. – torek Mar 15 '21 at 15:11
-
1@torek, you are alright. I did a type when wrote this; I mean `setpgid(2)`. Just corrected it in my answer. Thanks. – Lourenco Mar 16 '21 at 17:06
-
Much better - note that you not only need to use `setpgid` but also need to make sure that whatever is sending the signal is doing it with the process group IDs and `killpg`. (But that's up to whatever is *sending* the signal, i.e., there's not too much you can do in your own Go program.) – torek Mar 16 '21 at 17:10