PEP 3143 – 标准守护进程库
- 作者:
- Ben Finney <ben+python at benfinney.id.au>
- 状态:
- 推迟
- 类型:
- 标准跟踪
- 创建日期:
- 2009年1月26日
- Python 版本:
- 3.x
- 发布历史:
摘要
编写一个程序使其成为一个行为良好的Unix守护进程,过程有些复杂且容易出错,但无论程序还需要做些什么,这些步骤对于任何守护进程来说都大同小异。
本PEP向Python标准库引入了一个包,它为成为守护进程提供了简单的接口。
PEP 延期
对本 PEP 中涵盖概念的进一步探索已被推迟,因为目前缺乏一位有兴趣推广 PEP 目标、收集和整合反馈,并有足够可用时间有效完成此任务的倡导者。
规范
使用示例
直接使用DaemonContext的简单示例
import daemon
from spam import do_main_program
with daemon.DaemonContext():
do_main_program()
更复杂的用法示例
import os
import grp
import signal
import daemon
import lockfile
from spam import (
initial_program_setup,
do_main_program,
program_cleanup,
reload_program_config,
)
context = daemon.DaemonContext(
working_directory='/var/lib/foo',
umask=0o002,
pidfile=lockfile.FileLock('/var/run/spam.pid'),
)
context.signal_map = {
signal.SIGTERM: program_cleanup,
signal.SIGHUP: 'terminate',
signal.SIGUSR1: reload_program_config,
}
mail_gid = grp.getgrnam('mail').gr_gid
context.gid = mail_gid
important_file = open('spam.data', 'w')
interesting_file = open('eggs.data', 'w')
context.files_preserve = [important_file, interesting_file]
initial_program_setup()
with context:
do_main_program()
接口
一个新包daemon被添加到标准库中。
定义了一个类DaemonContext,用于表示作为守护进程运行的程序的设置和进程上下文。
DaemonContext 对象
一个DaemonContext实例表示程序成为守护进程时的行为设置和进程上下文。在调用open方法之前,通过设置实例上的选项来自定义行为和环境。
每个选项都可以作为关键字参数传递给DaemonContext构造函数,或者在调用open之前的任何时间通过赋值给实例上的属性来更改。也就是说,对于名为wibble和wubble的选项,以下调用
foo = daemon.DaemonContext(wibble=bar, wubble=baz)
foo.open()
等价于
foo = daemon.DaemonContext()
foo.wibble = bar
foo.wubble = baz
foo.open()
定义了以下选项。
files_preserve- 默认值:
无
启动守护进程时不应关闭的文件列表。如果为
None,则所有打开的文件描述符都将被关闭。列表中的元素是文件描述符(由文件对象的
fileno()方法返回)或Pythonfile对象。每个元素都指定一个在守护进程启动期间不关闭的文件。chroot_directory- 默认值:
无
要设置为进程有效根目录的完整路径。如果为
None,则表示根目录不应更改。working_directory- 默认值:
'/'
守护进程启动时进程应更改到的工作目录的完整路径。
如果进程的当前工作目录位于某个文件系统上,则该文件系统无法卸载,因此应将此项保留为默认值,或设置为守护进程运行时的合理“主目录”的目录。
umask- 默认值:
0
守护进程启动时为进程设置的文件访问创建掩码(“umask”)。
由于进程从其父进程继承其umask,因此启动守护进程会将umask重置为该值,以便守护进程创建的文件具有其期望的访问模式。
pidfile- 默认值:
无
PID锁文件的上下文管理器。当守护进程上下文打开和关闭时,它会进入和退出
pidfile上下文管理器。detach_process- 默认值:
无
如果为
True,则在打开守护进程上下文时分离进程上下文;如果为False,则不分离。如果在实例初始化期间未指定(
None),则默认将其设置为True,并且仅当确定分离进程是冗余时才设置为False;例如,在进程由init、initd或inetd启动的情况下。signal_map- 默认值:
- 系统相关
操作系统信号到回调动作的映射。
映射在守护进程上下文打开时使用,并确定每个信号处理程序的动作
- 值
None将忽略信号(通过将信号动作设置为signal.SIG_IGN)。 - 字符串值将用作
DaemonContext实例上属性的名称。属性的值将用作信号处理程序的动作。 - 任何其他值将用作信号处理程序的动作。
默认值取决于运行系统上定义的信号。以下列表中其信号实际在
signal模块中定义的每个项都将出现在默认映射中signal.SIGTTIN:Nonesignal.SIGTTOU:Nonesignal.SIGTSTP:Nonesignal.SIGTERM:'terminate'
根据程序如何与其子进程交互,它可能需要指定一个包含
signal.SIGCHLD信号(当子进程退出时接收)的信号映射。有关如何确定需要信号处理程序的具体情况的更多详细信息,请参阅特定操作系统的文档。uid- 默认值:
os.getuid()
gid- 默认值:
os.getgid()
守护进程启动时要将进程切换到的用户ID(“UID”)值和组ID(“GID”)值。
默认值,即进程的实际UID和GID,将放弃进程继承的任何有效权限提升。
prevent_core- 默认值:
真
如果为true,则防止生成核心文件,以避免从以
root身份运行的守护进程泄露敏感信息。stdin- 默认值:
无
stdout- 默认值:
无
stderr- 默认值:
无
stdin、stdout和stderr中的每一个都是一个文件类对象,将分别用作标准I/O流sys.stdin、sys.stdout和sys.stderr的新文件。因此,该文件应处于打开状态,对于stdin,最小模式为“r”,对于stdout和stderr,最小模式为“w+”。如果对象具有返回文件描述符的
fileno()方法,则在守护进程启动期间将排除相应文件不被关闭(即,它将被视为已列入files_preserve)。如果为
None,则相应的系统流将重新绑定到由os.devnull命名的文件。
定义了以下方法。
open()- 返回:
无
打开守护进程上下文,将当前程序转换为守护进程。这执行以下步骤
- 如果此实例的
is_open属性为true,则立即返回。这使得在实例上多次调用open是安全的。 - 如果
prevent_core属性为true,则设置进程的资源限制以防止进程产生任何核心转储。 - 如果
chroot_directory属性不为None,则将进程的有效根目录设置为该目录(通过os.chroot)。这允许在“chroot监狱”中运行守护进程,作为限制系统暴露于进程恶意行为的一种手段。请注意,指定的目录需要为此目的而设置好。
- 将进程UID和GID设置为
uid和gid属性值。 - 关闭所有打开的文件描述符。这不包括列在
files_preserve属性中的文件,以及与stdin、stdout或stderr属性相对应的文件。 - 将当前工作目录更改为由
working_directory属性指定的路径。 - 将文件访问创建掩码重置为由
umask属性指定的值。 - 如果
detach_process选项为true,则将当前进程分离到其自己的进程组中,并与任何控制终端解除关联。 - 根据
signal_map属性设置信号处理程序。 - 如果
stdin、stdout、stderr中的任何属性不为None,则将系统流sys.stdin、sys.stdout和/或sys.stderr绑定到由相应属性表示的文件。如果属性具有文件描述符,则复制描述符(而不是重新绑定名称)。 - 如果
pidfile属性不为None,则进入其上下文管理器。 - 将此实例标记为打开(用于将来的
open和close调用)。 - 注册
close方法以便在Python退出处理期间调用。
当函数返回时,正在运行的程序是一个守护进程。
关闭()- 返回:
无
关闭守护进程上下文。这执行以下步骤
- 如果此实例的
is_open属性为false,则立即返回。这使得在实例上多次调用close是安全的。 - 如果
pidfile属性不为None,则退出其上下文管理器。 - 将此实例标记为已关闭(用于将来的
open和close调用)。
is_open- 返回:
- 如果实例已打开,则为
True,否则为False。
此属性公开了指示实例当前是否打开的状态。如果实例的
open方法已被调用,并且close方法尚未随后被调用,则它为True。terminate(signal_number, stack_frame)- 返回:
无
signal.SIGTERM信号的信号处理程序。执行以下步骤- 引发一个解释信号的
SystemExit异常。
该类还通过__enter__和__exit__方法实现了上下文管理器协议。
__enter__()- 返回:
DaemonContext实例
调用实例的
open()方法,然后返回实例。__exit__(exc_type, exc_value, exc_traceback)- 返回:
- 由上下文管理器协议定义的
True或False
调用实例的
close()方法,然后如果异常已处理则返回True,否则返回False。
动机
大多数编写为Unix守护进程的程序要么实现与规范中非常相似的行为,要么按照正确的守护进程行为是行为不佳的守护进程。
由于这些步骤在大多数实现中都应大同小异,但它们非常特殊且容易遗漏或错误实现,因此它们是标准库中经过良好测试的标准实现的理想目标。
基本原理
正确的守护进程行为
根据Stevens在[stevens] §2.6中的说法,程序应执行以下步骤以成为Unix守护进程。
- 关闭所有打开的文件描述符。
- 更改当前工作目录。
- 重置文件访问创建掩码。
- 在后台运行。
- 与进程组解除关联。
- 忽略终端I/O信号。
- 与控制终端解除关联。
- 不要重新获取控制终端。
- 正确处理以下情况
- 由System V
init进程启动。 - 由
SIGTERM信号终止守护进程。 - 子进程生成
SIGCLD信号。
- 由System V
daemon工具[slack-daemon]列出了(在其功能摘要中)在将程序转换为行为良好的Unix守护进程时应执行的行为。它与本PEP的意图不同,因为它调用一个单独的程序作为守护进程。以下功能适用于程序已经运行后启动自身的守护进程
- 为守护进程设置正确的进程上下文。
- 由
initd(8)或inetd(8)启动时行为合理。 - 撤销任何suid或sgid权限,以减少守护进程安装不当具有特殊权限时的安全风险。
- 防止生成核心文件,以防止从以root身份运行的守护进程泄露敏感信息(可选)。
- 通过创建和锁定PID文件来命名守护进程,以保证在任何给定时间只能执行一个具有给定名称的守护进程(可选)。
- 设置运行守护进程的用户和组(可选,仅限root)。
- 创建chroot监狱(可选,仅限root)。
- 捕获守护进程的stdout和stderr并将其定向到syslog(可选)。
守护进程不是服务
本PEP仅涉及Unix风格的守护进程,其上述正确行为是相关的,而不是其他操作系统上的可比行为。
在许多系统中有一个相关的概念,称为“服务”。服务与本PEP中的模型不同,服务不是让当前程序继续作为守护进程运行,而是启动一个额外的进程在后台运行,并且当前进程通过某些定义的通道与该额外进程通信。
本PEP中的Unix风格守护进程模型可以用于(除其他外)实现服务中的后台进程部分;但本PEP不涉及设置和管理服务的其他方面。
参考实现
python-daemon包[python-daemon]。
其他守护进程实现
在本PEP之前,一些现有的第三方Python库或工具已经实现了本PEP的正确守护进程行为中的一部分。
参考实现是以下实现的相当直接的继承者
- 社区为Python cookbook recipes #66012 [cookbook-66012]和#278731 [cookbook-278731]贡献了许多好主意。
bda.daemon库[bda.daemon]是[cookbook-66012]的实现。它是[python-daemon]的前身。
与其他PEP不同的其他Python守护进程实现
zdaemon工具[zdaemon]是为Zope项目编写的。像[slack-daemon]一样,它与本规范不同,因为它用于将另一个程序作为守护进程运行。- Python库
daemon[clapper-daemon](根据其主页)不再维护。截至1.0.1版本,它实现了[stevens]中的基本步骤。 daemonize库[seutter-daemonize]也实现了[stevens]中的基本步骤。- Ray Burr的
daemon.py模块[burr-daemon]提供了[stevens]过程以及PID文件处理和输出重定向到syslog。 - Twisted [twisted]包括(或许不足为奇)一个与Twisted框架其他部分集成的进程守护化API实现;它与本PEP中的API显著不同。
- Python
initd库[dagitses-initd]使用[clapper-daemon],实现了Unixinitd(8)的等效功能,用于控制守护进程。
参考资料
daemon工具http://www.libslack.org/daemon/。python-daemon库http://pypi.python.org/pypi/python-daemon/。bda.daemon库http://pypi.python.org/pypi/bda.daemon/。zdaemon工具http://pypi.python.org/pypi/zdaemon/。daemon库http://pypi.python.org/pypi/daemon/。daemonize库http://daemonize.sourceforge.net/。daemon.py模块http://www.nightmare.com/~ryb/code/daemon.py。Twisted应用程序框架http://pypi.python.org/pypi/Twisted/。initd库http://pypi.python.org/pypi/initd/。版权
本作品特此置于公共领域。在法律上无法将作品置于公共领域的范围内,版权所有者特此授予本作品的所有接收者所有本应受版权限制的权利和自由。
来源:https://github.com/python/peps/blob/main/peps/pep-3143.rst