【问题标题】:Can't detach child process when main process is started from systemd从systemd启动主进程时无法分离子进程
【发布时间】:2015-11-19 10:33:16
【问题描述】:

我想生成长时间运行的子进程,这些子进程在主进程重新启动/死亡时仍然存在。从终端运行时效果很好:

$ cat exectest.go
package main

import (
        "log"
        "os"
        "os/exec"
        "syscall"
        "time"
)

func main() {
        if len(os.Args) == 2 && os.Args[1] == "child" {
                for {   
                        time.Sleep(time.Second)
                }
        } else {
                cmd := exec.Command(os.Args[0], "child")
                cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
                log.Printf("child exited: %v", cmd.Run())
        }
}
$ go build
$ ./exectest
^Z
[1]+  Stopped                 ./exectest
$ bg
[1]+ ./exectest &
$ ps -ef | grep exectest | grep -v grep | grep -v vim
snowm     7914  5650  0 23:44 pts/7    00:00:00 ./exectest
snowm     7916  7914  0 23:44 ?        00:00:00 ./exectest child
$ kill -INT 7914 # kill parent process
[1]+  Exit 2                  ./exectest
$ ps -ef | grep exectest | grep -v grep | grep -v vim
snowm     7916     1  0 23:44 ?        00:00:00 ./exectest child

请注意,在父进程被杀死后,子进程仍然处于活动状态。但是,如果我像这样从 systemd 启动主进程...

[snowm@localhost exectest]$ cat /etc/systemd/system/exectest.service 
[Unit]
Description=ExecTest

[Service]                        
Type=simple
ExecStart=/home/snowm/src/exectest/exectest
User=snowm

[Install]
WantedBy=multi-user.target
$ sudo systemctl enable exectest
ln -s '/etc/systemd/system/exectest.service' '/etc/systemd/system/multi-user.target.wants/exectest.service'
$ sudo systemctl start exectest

...那么当我杀死主进程时,孩子也死了:

$ ps -ef | grep exectest | grep -v grep | grep -v vim
snowm     8132     1  0 23:55 ?        00:00:00 /home/snowm/src/exectest/exectest
snowm     8134  8132  0 23:55 ?        00:00:00 /home/snowm/src/exectest/exectest child
$ kill -INT 8132
$ ps -ef | grep exectest | grep -v grep | grep -v vim
$

我怎样才能让孩子活下来?

在 CentOS Linux release 7.1.1503 (Core) 下运行 go version go1.4.2 linux/amd64。

【问题讨论】:

  • SystemD 可能会杀死进程组(或 cgroup,我有一段时间没有查看 systemd 设置)中的所有内容。您实际上是在尝试模拟守护程序的行为,还是仅作为示例?
  • @JimB:我的实际代码产生了运行 ffmpeg 的子进程。我基本上是在使用上面的模式来模拟守护进程的行为,是的。
  • 为什么不让父进程运行,让systemd正常管理呢?我认为可能有一种方法可以生成子进程并通知系统新进程,但前者会容易得多。
  • @JimB:子进程重启成本很高,所以当主进程重启或崩溃时,它们不能死掉。但是,感谢您的 cgroup 提示,我发现解决方案是说 KillMode=process(默认为 control-group)。

标签: linux go process systemd


【解决方案1】:

解决办法是添加

KillMode=process

到服务块。默认值为control-group,这意味着systemd会清理所有子进程。

来自man systemd.kill

KillMode= 指定如何杀死该单元的进程。之一 控制组、过程、混合、无。

如果设置为控制组,控制组中的所有剩余进程 这个单位的将在单位停止时被杀死(对于服务:停止后 命令被执行,配置为 ExecStop=)。如果设置为处理, 只有主进程本身被杀死。如果设置为混合,则 SIGTERM 信号(见下文)被发送到主进程,而随后的 SIGKILL 信号(见下文)被发送到所有剩余进程 单位控制组。如果设置为 none,则不会杀死任何进程。在这个 在这种情况下,只有停止命令将在单元停止时执行,但不会 否则进程被杀死。停止后仍然活着的进程是 留在他们的控制组和控制组继续存在 之后停止,除非它是空的。

【讨论】:

  • 是否有办法将进程移出组?
  • 这救了我。谢谢。
  • 今天,手册告诉我们“如果设置为'进程',只有主进程本身被杀死(不推荐!)”。知道为什么不鼓励这样做吗?
【解决方案2】:

如果你因为某种原因不能(像我一样)改变服务的KillMode,你可以试试at命令(见man)。

您可以安排命令提前 1 分钟运行。看一个例子:

# this will remove all .tmp files from "/path/" in 1 minute ahead (this task will run once)
echo rm /path/*.tmp | at now + 1 minute

【讨论】:

    【解决方案3】:

    据我所知,解决此问题的唯一可行方法是使用不同的 cgroup 启动子进程。您可以使用 systemd-run 命令和 --slice 参数来做到这一点。

    systemd-run --user --scope --slice=app-firefox firefox
    

    更改 KillMode 还意味着如果您的主进程崩溃,如果任何子进程仍在运行,systemd 将不会重新启动它。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-04-15
      • 2018-11-10
      • 2020-04-26
      • 2018-03-19
      • 2017-11-02
      • 1970-01-01
      • 2017-03-19
      相关资源
      最近更新 更多