Source code for daemonize

#
# (C) Pywikibot team, 2007-2026
#
# Distributed under the terms of the MIT license.
#
"""Module to daemonize the current process on POSIX systems.

This module provides a function :func:`daemonize` to turn the current
Python process into a background daemon process on POSIX-compatible
operating systems (Linux, macOS, FreeBSD) but not on not WASI Android or
iOS. It uses the standard double-fork technique to detach the process
from the controlling terminal and optionally closes or redirects
standard streams.

Double-fork diagram::

    Original process (parent)
    ├── fork()  → creates first child
    │   └─ Parent exits via os._exit() → returns control to terminal

    └── First child
        ├── os.setsid()  → becomes session leader (detaches from terminal)
        ├── fork()  → creates second child (grandchild)
        │   └─ First child exits → ensures grandchild is NOT a session leader

        └── Second child (Daemon)
            ├── is_daemon = True
            ├── Optionally close/redirect standard streams
            ├── Optionally change working directory
            └── # Daemon continues here
                while True:
                    do_background_work()

The "while True" loop represents the main work of the daemon:

- It runs indefinitely in the background
- Performs tasks such as monitoring files, processing data, or logging
- Everything after :func:`daemonize` runs only in the daemon process

Example usage:

    .. code-block:: Python

       import time
       from pywikibot.daemonize import daemonize

       def background_task():
           while True:
               print("Daemon is working...")
               time.sleep(5)

       daemonize()

       # This code only runs in the daemon process
       background_task()
"""
from __future__ import annotations

import os
import platform
import stat
import sys
from contextlib import suppress
from enum import IntEnum
from pathlib import Path

from pywikibot.tools import deprecated_signature


[docs] class StandardFD(IntEnum): """File descriptors for standard input, output and error.""" STDIN = 0 STDOUT = 1 STDERR = 2
is_daemon = False
[docs] @deprecated_signature(since='10.6.0') def daemonize(*, close_fd: bool = True, chdir: bool = True, redirect_std: str | None = None) -> None: """Daemonize the current process. Only works on POSIX compatible operating systems. The process will fork to the background and return control to terminal. .. version-changed:: 10.6 raises NotImplementedError instead of AttributeError if daemonize is not available for the given platform. Parameters must be given as keyword-only arguments. .. caution:: Do not use it in multithreaded scripts or in a subinterpreter. :param close_fd: Close the standard streams and replace them by /dev/null :param chdir: Change the current working directory to / :param redirect_std: Filename to redirect stdout and stdin to :raises RuntimeError: Must not be run in a subinterpreter :raises NotImplementedError: Daemon mode not supported on given platform """ # platform check for MyPy if not hasattr(os, 'fork') or sys.platform == 'win32': msg = f'Daemon mode not supported on {platform.system()}' raise NotImplementedError(msg) # Fork away if not os.fork(): # Become session leader os.setsid() # Fork again to prevent the process from acquiring a # controlling terminal pid = os.fork() if not pid: global is_daemon is_daemon = True # Optionally close and redirect standard streams if close_fd: for fd in StandardFD: with suppress(OSError): os.close(fd) os.open('/dev/null', os.O_RDWR) if redirect_std: # R/W mode without execute flags mode = (stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IWGRP | stat.S_IROTH | stat.S_IWOTH) os.open(redirect_std, os.O_WRONLY | os.O_APPEND | os.O_CREAT, mode) else: os.dup2(StandardFD.STDIN, StandardFD.STDOUT) os.dup2(StandardFD.STDOUT, StandardFD.STDERR) # Optionally change working directory if chdir: os.chdir('/') return # Daemon continues here # Write out the pid path = Path(Path(sys.argv[0]).name).with_suffix('.pid') path.write_text(str(pid), encoding='utf-8') # Exit to return control to the terminal # os._exit to prevent the cleanup to run os._exit(os.EX_OK)