如何实现简单的跨平台Python守护进程?

Nic*_*ton 22 python daemon cross-platform

我希望我的Python程序在后台作为守护进程在Windows或Unix上运行.我看到python-daemon包仅适用于Unix; 是否有跨平台的替代方案?如果可能的话,我想保持代码尽可能简单.

Ale*_*lli 10

在Windows中,它被称为"服务",你可以很容易地实现它,例如使用win32serviceutil模块,pywin32的一部分.不幸的是,两个"心理模型" - 服务与守护进程 - 在细节上有很大的不同,即使它们用于类似的目的,我知道没有Python外观试图将它们统一到一个框架中.


Nic*_*ger 5

这个问题已有6年历史了,但是我遇到了同样的问题,现有的答案还不足以适用于我的用例。尽管Windows服务通常以与Unix守护程序相似的方式使用,但最终它们还是有很大的不同,并且“细节上是魔鬼”。长话短说,我着手尝试寻找可以使我在Unix和Windows上运行完全相同的应用程序代码的东西,同时尽可能满足对行为良好的Unix守护程序(在其他地方更好的解释)的期望。在两个平台上:

  1. 关闭打开的文件描述符(通常是所有文件描述符,但是某些应用程序可能需要保护某些描述符免于关闭)
  2. 将过程的工作目录更改到合适的位置,以防止出现“目录繁忙”错误
  3. 更改文件访问创建掩码(os.umask在Python世界中)
  4. 将应用程序移至后台,使其与启动过程脱离关联
  5. 与终端完全脱离,包括将STDINSTDOUT和重定向STDERR到不同的流(通常为DEVNULL),并防止重新获得控制终端
  6. 特别是处理信号SIGTERM

跨平台守护程序的基本问题是,作为操作系统,Windows确实不支持守护程序的概念:从终端启动的应用程序(或在任何其他交互式上下文中,包括从Explorer启动的应用程序)将除非控制应用程序(在此示例中为Python)包含无窗口的GUI,否则继续使用可见窗口运行。此外,Windows信号处理严重不足,尝试将信号发送到独立的 Python进程(与子进程相反,子进程无法在终端关闭后继续存在)几乎总是导致该Python进程立即退出而没有任何清理(否finally:,否atexit,否__del__等)。

Windows服务(尽管在许多情况下是可行的替代方法)对我基本上是不可能的:它们不是跨平台的,它们将需要修改代码。pythonw.exe(所有最近的Windows Python二进制文件都随附了Python无窗口版本),但它仍然没有达到目标:特别是,它无法改善信号处理的情况,并且您仍然无法轻松启动pythonw.exe应用程序 “守护进程” 之前,从终端与它进行交互并在启动过程中与之交互(例如,将动态启动参数传递给脚本,例如,密码,文件路径等)。

最后,我决定使用subprocess.Popencreationflags=subprocess.CREATE_NEW_PROCESS_GROUP关键字来创建一个独立的,没有窗户的过程:

import subprocess

independent_process = subprocess.Popen(
    '/path/to/pythonw.exe /path/to/file.py',
    creationflags=subprocess.CREATE_NEW_PROCESS_GROUP
)
Run Code Online (Sandbox Code Playgroud)

但是,这仍然给我带来了启动通信和信号处理方面的额外挑战。对于前者,我不做过多介绍,我的策略是:

  1. pickle 启动过程名称空间的重要部分
  2. 将其存储在 tempfile
  3. 在启动之前,在子进程的环境中添加该文件的路径
  4. 从“守护程序”函数中提取并返回名称空间

对于信号处理,我必须变得更有创意。在“守护进程”中:

  1. 忽略守护进程中的信号,因为如上所述,它们都会立即终止进程而无需清理
  2. 创建一个新线程来管理信号处理
  3. 该线程启动子信号处理过程,并等待它们完成
  4. 外部应用程序将信号发送到子信号处理过程,导致其终止并完成
  5. 然后,这些进程将信号号用作其返回码
  6. 信号处理线程读取返回代码,然后调用用户定义的信号处理程序,或使用cytpes API在Python主线程内引发适当的异常
  7. 冲洗并重复以获取新信号

综上所述,对于将来遇到此问题的任何人,我都推出了一个名为daemoniker的库,该库将适当的Unix守护程序上述Windows策略包装到一个统一的外观中。在跨平台的API看起来是这样的:

from daemoniker import Daemonizer

with Daemonizer() as (is_setup, daemonizer):
    if is_setup:
        # This code is run before daemonization.
        do_things_here()

    # We need to explicitly pass resources to the daemon; other variables
    # may not be correct
    is_parent, my_arg1, my_arg2 = daemonizer(
        path_to_pid_file,
        my_arg1,
        my_arg2
    )

    if is_parent:
        # Run code in the parent after daemonization
        parent_only_code()

# We are now daemonized, and the parent just exited.
code_continues_here()
Run Code Online (Sandbox Code Playgroud)


Ada*_*tan 4

我想到了两个选择:

  1. 将您的程序移植Windows 服务中。您可能可以在两个实现之间共享大部分代码。

  2. 您的程序真的使用任何守护程序功能吗?如果没有,您可以将其重写为一个在后台运行的简单服务器,通过套接字管理通信并执行其任务。它可能会比守护进程消耗更多的系统资源,但它将独立于引用平台。