How to Prevent Orphan Processes When Running Commands in Go
This article explains why Go programs that spawn child processes can leave orphaned processes after a kill, and demonstrates two robust solutions: creating a new process group with syscall.Kill and using Linux prctl (via CGO or RawSyscall) to ensure child processes terminate when the parent exits.
1 Orphan Process Generation
When executing commands in Go with exec.Command, calling cmd.Process.Kill() terminates the main process, but any child processes it started become orphaned and continue running.
func kill(cmd *exec.Cmd) func() {
return func() {
if cmd != nil {
cmd.Process.Kill()
}
}
}
func main() {
cmd := exec.Command("/bin/bash", "-c", "watch top >top.log")
time.AfterFunc(1*time.Second, kill(cmd))
err := cmd.Run()
fmt.Printf("pid=%d err=%s
", cmd.Process.Pid, err)
}Running the program shows the child process watch top with PPID 1, confirming it became an orphan.
2 Solving with Process Groups
Linux provides process groups; sending a signal to a negative PID targets the entire group. By creating a new process group for the command, we can kill all its children.
#include <signal.h>
int kill(pid_t pid, int sig);Modify the Go code to set SysProcAttr.Setpgid = true and use syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL):
func kill(cmd *exec.Cmd) func() {
return func() {
if cmd != nil {
syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
}
}
}
func main() {
cmd := exec.Command("/bin/bash", "-c", "watch top >top.log")
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
time.AfterFunc(1*time.Second, kill(cmd))
err := cmd.Run()
fmt.Printf("pid=%d err=%s
", cmd.Process.Pid, err)
}After running, all processes in the group are terminated and no orphan remains.
3 Child Process Monitoring Parent Exit (Linux only)
To ensure a child exits when its parent dies, use the Linux prctl system call with the PR_SET_PDEATHSIG option. This can be invoked via CGO or syscall.RawSyscall.
#include <sys/prctl.h>
int prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5);CGO example :
/*
#include <sys/prctl.h>
#include <signal.h>
static void killTest() { prctl(PR_SET_PDEATHSIG, SIGKILL); }
*/
import "C"
func main() {
C.killTest()
for {
time.Sleep(200 * time.Millisecond)
fmt.Println(time.Now())
}
}RawSyscall example :
func main() {
_, _, errno := syscall.RawSyscall(uintptr(syscall.SYS_PRCTL), uintptr(syscall.PR_SET_PDEATHSIG), uintptr(syscall.SIGKILL), 0)
if errno != 0 { os.Exit(int(errno)) }
for {
time.Sleep(200 * time.Millisecond)
fmt.Println(time.Now())
}
}Both approaches cause the child process to receive SIGKILL automatically when the parent exits, preventing orphaned processes.
4 Summary
Create a new process group for the command and use syscall.Kill with a negative PID to terminate the entire group.
If the spawned program is under your control, set PR_SET_PDEATHSIG via CGO or syscall.RawSyscall so the child exits when the parent dies.
These techniques help avoid orphan processes that waste server resources.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Alibaba Cloud Developer
Alibaba's official tech channel, featuring all of its technology innovations.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
