Linux下Go进程问题:父服务停止时子进程意外终止求解决方案
Ubuntu下Go服务子进程终止问题排查与解决建议
我在Ubuntu系统用Go开发了一个守护进程服务(通过systemctl可查询,但ps无法查看),基于github.com/kardianos/service包管理。服务会启动一个同代码的可执行文件作为子进程,但当子进程执行service.Control("stop")停止父服务时,子进程也会意外终止,无法继续执行后续流程。
已尝试以下方法但均未解决问题:
- 设置会话ID(
Setsid=true) - 设置组ID(
Setpgid=true) - 调用
Start()后调用进程的Release()方法 - 以上方法的组合
初始化进程的函数代码
func ExecuteCommandAsync(command, args string, detach bool) error { logger.Notify("Running cli command:", command, args) cmd := exec.Command(command, args) if detach { logger.Notify("Detaching from parent process") cmd.SysProcAttr = &syscall.SysProcAttr{ Setsid: true, // new session id prevents sigkill to parent kill the child //Setpgid: true, // new group id prevents sigkill to parent kill the child } } err := cmd.Start() if err != nil { logger.Error("Failed running cli command:", command, err) return err } if detach { logger.Notify("Detached from parent process - releasing child process") err = cmd.Process.Release() if err != nil { return err } } else { go func() { logger.Notify("Waiting for cli command to finish") waitErr := cmd.Wait() if waitErr != nil { logger.Error("Error waiting for cli command to finish:", waitErr) } else { logger.Notify("Command finished successfully") } }() } return nil }
停止服务的函数代码
func systemStopService(s service.Service) error { if s == nil { return errors.New("stopService - service is nil") } logger.Notify(s.String() + " Stopping service") err := service.Control(s, "stop") if err == nil { logger.Notify(s.String() + " Service stopped") } else if strings.Contains(err.Error(), "service does not exist") || strings.Contains(err.Error(), "service has not been started") { logger.Notify(s.String() + err.Error()) err = nil // not an error } else { logger.Error(s.String()+" Failed to stop service", err) } return err }
可能的原因与解决方向
修改systemd服务单元配置
Ubuntu下systemd默认会向整个控制组发送终止信号(KillMode=control-group),即使子进程脱离了父进程,仍可能被波及。修改服务单元文件,添加:KillMode=process这样systemd只会终止主进程,不会影响已脱离的子进程。
子进程显式忽略信号
子进程可能继承了父进程的信号处理逻辑,在子进程启动后显式忽略可能导致终止的信号:import "os/signal" import "syscall" // 在子进程入口处添加 signal.Ignore(syscall.SIGTERM, syscall.SIGHUP, syscall.SIGINT)彻底脱离父进程环境
除了设置Setsid和Setpgid,还需切断子进程与父进程的终端、IO关联:cmd.SysProcAttr = &syscall.SysProcAttr{ Setsid: true, Setpgid: true, Noctty: true, // 不控制终端 } // 重定向IO到/dev/null cmd.Stdin = os.Open("/dev/null") cmd.Stdout = os.OpenFile("/dev/null", os.O_WRONLY, 0644) cmd.Stderr = cmd.Stdout调整停止逻辑的时序
如果子进程需要完成后续任务,可让子进程通过IPC(如Unix Socket)通知父服务,父服务收到通知后再执行停止操作;或者在父服务的停止回调中添加延迟,给子进程预留执行时间。
内容的提问来源于stack exchange,提问作者MC LinkTimeError
相关产品推荐
相关产品推荐

