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

发布于 2024-08-21 19:50:36 字数 185 浏览 14 评论 0原文

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

I would like to have my Python program run in the background as a daemon, on either Windows or Unix. I see that the python-daemon package is for Unix only; is there an alternative for cross platform? If possible, I would like to keep the code as simple as I can.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

深爱成瘾 2024-08-28 19:50:36

In Windows it's called a "service" and you could implement it pretty easily e.g. with the win32serviceutil module, part of pywin32. Unfortunately the two "mental models" -- service vs daemon -- are very different in detail, even though they serve similar purposes, and I know of no Python facade that tries to unify them into a single framework.

明媚殇 2024-08-28 19:50:36

这个问题已经有 6 年历史了,但我也遇到了同样的问题,并且现有的答案对于我的用例来说不够跨平台。尽管 Windows 服务的使用方式通常与 Unix 守护程序类似,但归根结底,它们之间存在很大差异,“细节决定成败”。长话短说,我开始尝试找到一些东西,让我能够在 Unix 和 Windows 上运行完全相同的应用程序代码,同时满足对行为良好的 Unix 守护进程的期望(即 在其他地方有更好的解释)在两个平台上尽可能最好:

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

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

Windows 服务(尽管在许多情况下是可行的替代方案)对我来说基本上是不可能的:它们不是跨平台的,并且需要修改代码。 pythonw.exePython 的无窗口版本,随所有最新的 Windows 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
)

但是,这仍然给我留下了添加的内容启动通信和信号处理的挑战。对于前者,我的策略是:

  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()

This question is 6 years old, but I had the same problem, and the existing answers weren't cross-platform enough for my use case. Though Windows services are often used in similar ways as Unix daemons, at the end of the day they differ substantially, and "the devil's in the details". Long story short, I set out to try and find something that allows me to run the exact same application code on both Unix and Windows, while fulfilling the expectations for a well-behaved Unix daemon (which is better explained elsewhere) as best as possible on both platforms:

  1. Close open file descriptors (typically all of them, but some applications may need to protect some descriptors from closure)
  2. Change the working directory for the process to a suitable location to prevent "Directory Busy" errors
  3. Change the file access creation mask (os.umask in the Python world)
  4. Move the application into the background and make it dissociate itself from the initiating process
  5. Completely divorce from the terminal, including redirecting STDIN, STDOUT, and STDERR to different streams (often DEVNULL), and prevent reacquisition of a controlling terminal
  6. Handle signals, in particular, SIGTERM.

The fundamental problem with cross-platform daemonization is that Windows, as an operating system, really doesn't support the notion of a daemon: applications that start from a terminal (or in any other interactive context, including launching from Explorer, etc) will continue to run with a visible window, unless the controlling application (in this example, Python) has included a windowless GUI. Furthermore, Windows signal handling is woefully inadequate, and attempts to send signals to an independent Python process (as opposed to a subprocess, which would not survive terminal closure) will almost always result in the immediate exit of that Python process without any cleanup (no finally:, no atexit, no __del__, etc).

Windows services (though a viable alternative in many cases) were basically out of the question for me: they aren't cross-platform, and they're going to require code modification. pythonw.exe (a windowless version of Python that ships with all recent Windows Python binaries) is closer, but it still doesn't quite make the cut: in particular, it fails to improve the situation for signal handling, and you still cannot easily launch a pythonw.exe application from the terminal and interact with it during startup (for example, to deliver dynamic startup arguments to your script, say, perhaps, a password, file path, etc), before "daemonizing".

In the end, I settled on using subprocess.Popen with the creationflags=subprocess.CREATE_NEW_PROCESS_GROUP keyword to create an independent, windowless process:

import subprocess

independent_process = subprocess.Popen(
    '/path/to/pythonw.exe /path/to/file.py',
    creationflags=subprocess.CREATE_NEW_PROCESS_GROUP
)

However, that still left me with the added challenge of startup communications and signal handling. Without going into a ton of detail, for the former, my strategy was:

  1. pickle the important parts of the launching process' namespace
  2. Store that in a tempfile
  3. Add the path to that file in the daughter process' environment before launching
  4. Extract and return the namespace from the "daemonization" function

For signal handling I had to get a bit more creative. Within the "daemonized" process:

  1. Ignore signals in the daemon process, since, as mentioned, they all terminate the process immediately and without cleanup
  2. Create a new thread to manage signal handling
  3. That thread launches daughter signal-handling processes and waits for them to complete
  4. External applications send signals to the daughter signal-handling process, causing it to terminate and complete
  5. Those processes then use the signal number as their return code
  6. The signal handling thread reads the return code, and then calls either a user-defined signal handler, or uses a cytpes API to raise an appropriate exception within the Python main thread
  7. Rinse and repeat for new signals

That all being said, for anyone encountering this problem in the future, I've rolled a library called daemoniker that wraps both proper Unix daemonization and the above Windows strategy into a unified facade. The cross-platform API looks like this:

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()
岁吢 2024-08-28 19:50:36

我想到了两个选择:

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

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

Two options come to mind:

  1. Port your program into a windows service. You can probably share much of your code between the two implementations.

  2. Does your program really use any daemon functionality? If not, you rewrite it as a simple server that runs in the background, manages communications through sockets, and perform its tasks. It will probably consume more system resources than a daemon would, but it would be quote platform independent.

帅哥哥的热头脑 2024-08-28 19:50:36

一般来说,守护进程的概念是 Unix 特定的,特别是关于文件创建掩码、进程层次结构和信号处理的预期行为。

您可能会发现 PEP 3143 很有用,其中建议延续 python-daemon 被考虑用于 Python 3.2,并且许多 相关守护进程模块和实现进行了讨论。

In general the concept of a daemon is Unix specific, in particular expected behaviour with respect to file creation masks, process hierarchy, and signal handling.

You may find PEP 3143 useful wherein a proposed continuation of python-daemon is considered for Python 3.2, and many related daemonizing modules and implementations are discussed.

烙印 2024-08-28 19:50:36

它只是unix的原因是守护进程是Unix特定的概念,即后台进程由操作系统启动,通常作为根 PID 的子进程运行。
Windows 没有直接相当于 unix 守护进程,我能想到的最接近的是 Windows 服务。
有一个名为 pythonservice.exe for windows 的程序。不确定是否所有版本的 python 都支持它

The reason it's unix only is that daemons are a Unix specific concept i.e a background process initiated by the os and usually running as a child of the root PID .
Windows has no direct equivalent of a unix daemon, the closest I can think of is a Windows Service.
There's a program called pythonservice.exe for windows . Not sure if it's supported on all versions of python though

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文