You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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
}

可能的原因与解决方向

  1. 修改systemd服务单元配置
    Ubuntu下systemd默认会向整个控制组发送终止信号(KillMode=control-group),即使子进程脱离了父进程,仍可能被波及。修改服务单元文件,添加:

    KillMode=process
    

    这样systemd只会终止主进程,不会影响已脱离的子进程。

  2. 子进程显式忽略信号
    子进程可能继承了父进程的信号处理逻辑,在子进程启动后显式忽略可能导致终止的信号:

    import "os/signal"
    import "syscall"
    
    // 在子进程入口处添加
    signal.Ignore(syscall.SIGTERM, syscall.SIGHUP, syscall.SIGINT)
    
  3. 彻底脱离父进程环境
    除了设置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
    
  4. 调整停止逻辑的时序
    如果子进程需要完成后续任务,可让子进程通过IPC(如Unix Socket)通知父服务,父服务收到通知后再执行停止操作;或者在父服务的停止回调中添加延迟,给子进程预留执行时间。

内容的提问来源于stack exchange,提问作者MC LinkTimeError

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 19:24:49