无法在从systemd启动的主进程中分离子进程。

huangapple go评论75阅读模式
英文:

Can't detach child process when main process is started from systemd

问题

我想要生成长时间运行的子进程,即使主进程重新启动或终止也能够继续存在。在终端中运行时,这个方法是有效的:

$ 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。

英文:

I want to spawn long-running child processes that survive when the main process restarts/dies. This works fine when running from the terminal:

$ 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

Note that the child process is still alive after parent process was killed. However, if I start the main process from systemd like this...

[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

... then the child also dies when I kill the main process:

$ 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
$

How can I make the child survive?

Running go version go1.4.2 linux/amd64 under CentOS Linux release 7.1.1503 (Core).

答案1

得分: 37

解决方法是在服务块中添加KillMode=process。默认值是control-group,这意味着systemd会清理任何子进程。

根据man systemd.kill的说明:

KillMode=指定如何终止该单元的进程。可选值为control-group、process、mixed、none。

如果设置为control-group,在单元停止时(对于服务来说,是在执行停止命令后,根据ExecStop=配置),将终止该单元控制组中的所有剩余进程。如果设置为process,只有主进程本身会被终止。如果设置为mixed,将向主进程发送SIGTERM信号(参见下文),然后向该单元控制组的所有剩余进程发送SIGKILL信号(参见下文)。如果设置为none,则不会终止任何进程。在这种情况下,只有在单元停止时才会执行停止命令,否则不会终止任何进程。停止后仍然存活的进程将保留在其控制组中,并且控制组将继续存在,除非它为空。

英文:

Solution is to add

KillMode=process

to the service block. Default value is control-group which means systemd cleans up any child processes.

From man systemd.kill
> KillMode= Specifies how processes of this unit shall be killed. One of
> control-group, process, mixed, none.
>
> If set to control-group, all remaining processes in the control group
> of this unit will be killed on unit stop (for services: after the stop
> command is executed, as configured with ExecStop=). If set to process,
> only the main process itself is killed. If set to mixed, the SIGTERM
> signal (see below) is sent to the main process while the subsequent
> SIGKILL signal (see below) is sent to all remaining processes of the
> unit's control group. If set to none, no process is killed. In this
> case, only the stop command will be executed on unit stop, but no
> process be killed otherwise. Processes remaining alive after stop are
> left in their control group and the control group continues to exist
> after stop unless it is empty.

答案2

得分: 5

我知道解决这个问题的唯一方法是使用--scope参数启动子进程。

systemd-run --user --scope firefox

也提到了KillMode,但是更改KillMode意味着如果主进程崩溃,systemd不会重新启动它,即使有任何子进程仍在运行。

英文:

The only way I know to solve this is to launch the child process with the --scope argument.

systemd-run --user --scope firefox

KillMode has been mentioned here also, but changing the KillMode also means that if your main process crashes, systemd won't restart it if any child process is still running.

答案3

得分: 0

如果由于某种原因你无法更改服务的KillMode,你可以尝试使用at命令(参见man)。

你可以安排你的命令在1分钟后运行。以下是一个示例:

# 这个命令将在1分钟后从"/path/"目录中删除所有.tmp文件(此任务只运行一次)
echo rm /path/*.tmp | at now + 1 minute
英文:

If you cannot (like me) to change the KillMode of the service for some reason, you could try the at command (see man).

You can schedule your command to run 1 minute ahead. See an example:

# 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

huangapple
  • 本文由 发表于 2015年8月26日 00:02:08
  • 转载请务必保留本文链接:https://go.coder-hub.com/32208782.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定