You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

通过两个子类扩展Daemon类无法正常工作的技术咨询

解决Daemon基类子类化多守护进程的问题

我明白你在尝试通过两个子类扩展这个Daemon基类时遇到了问题——这类守护进程的继承问题通常出在进程隔离或者run()方法重写不规范上,咱们一步步来解决。

首先,先把你提供的Daemon基类补全完整(双叉机制的实现需要完整的环境脱离和文件描述符重定向逻辑):

import os
import sys
import time
from signal import SIGTERM

class Daemon:
    """通用守护进程类。使用方法:继承该守护进程类并重写run()方法。"""
    def __init__(self, pidfile, outfile='/tmp/daemon_out', errfile='/tmp/daemon_log'):
        self.pidfile = pidfile
        self.outfile = outfile
        self.errfile = errfile

    def daemonize(self):
        """守护进程化类。采用UNIX双叉机制。"""
        try:
            pid = os.fork()
            if pid > 0:
                # 父进程退出,脱离终端控制
                sys.exit(0)
        except OSError as e:
            sys.stderr.write(f"第一次fork失败: {e.errno} ({e.strerror})\n")
            sys.exit(1)

        # 脱离父进程的会话和环境
        os.chdir("/")
        os.setsid()
        os.umask(0)

        # 第二次fork,确保进程不会成为会话首进程,避免再次打开终端
        try:
            pid = os.fork()
            if pid > 0:
                sys.exit(0)
        except OSError as e:
            sys.stderr.write(f"第二次fork失败: {e.errno} ({e.strerror})\n")
            sys.exit(1)

        # 重定向标准输入、输出、错误到指定文件或/dev/null
        sys.stdout.flush()
        sys.stderr.flush()
        si = open(os.devnull, 'r')
        so = open(self.outfile, 'a+')
        se = open(self.errfile, 'a+')

        os.dup2(si.fileno(), sys.stdin.fileno())
        os.dup2(so.fileno(), sys.stdout.fileno())
        os.dup2(se.fileno(), sys.stderr.fileno())

        # 写入PID文件,用于后续启停控制
        with open(self.pidfile, 'w+') as f:
            f.write(f"{os.getpid()}\n")

    def start(self):
        """启动守护进程"""
        # 检查PID文件是否存在,防止重复启动
        try:
            with open(self.pidfile, 'r') as f:
                pid = int(f.read().strip())
        except IOError:
            pid = None

        if pid:
            message = f"PID文件 {self.pidfile} 已存在,守护进程可能已运行?\n"
            sys.stderr.write(message)
            sys.exit(1)

        # 完成守护进程化并执行核心逻辑
        self.daemonize()
        self.run()

    def stop(self):
        """停止守护进程"""
        # 从PID文件读取进程ID
        try:
            with open(self.pidfile, 'r') as f:
                pid = int(f.read().strip())
        except IOError:
            pid = None

        if not pid:
            message = f"PID文件 {self.pidfile} 不存在,守护进程可能未运行?\n"
            sys.stderr.write(message)
            return

        # 发送终止信号并等待进程退出
        try:
            while True:
                os.kill(pid, SIGTERM)
                time.sleep(0.1)
        except OSError as e:
            if e.errno == 3:  # 进程已不存在
                if os.path.exists(self.pidfile):
                    os.remove(self.pidfile)
            else:
                print(str(e))
                sys.exit(1)

    def restart(self):
        """重启守护进程"""
        self.stop()
        self.start()

    def run(self):
        """需要被子类重写的核心方法,子类在这里实现具体业务逻辑"""
        raise NotImplementedError("子类必须重写run()方法")

接下来,创建两个独立的子类,关键是给每个子类分配独立的PID文件和日志文件,确保进程之间完全隔离:

# 第一个子类:日志收集守护进程
class LogCollectorDaemon(Daemon):
    def __init__(self):
        # 指定专属的PID文件和日志路径,避免和其他守护进程冲突
        super().__init__(
            pidfile='/var/run/log_collector.pid',
            outfile='/var/log/log_collector_out.log',
            errfile='/var/log/log_collector_err.log'
        )

    def run(self):
        """模拟日志收集逻辑:每秒写入一条收集记录"""
        while True:
            with open(self.outfile, 'a') as f:
                f.write(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 正在收集系统日志...\n")
            time.sleep(1)

# 第二个子类:定时任务守护进程
class ScheduledTaskDaemon(Daemon):
    def __init__(self):
        super().__init__(
            pidfile='/var/run/scheduled_task.pid',
            outfile='/var/log/scheduled_task_out.log',
            errfile='/var/log/scheduled_task_err.log'
        )

    def run(self):
        """模拟定时任务:每5秒执行一次任务"""
        while True:
            with open(self.outfile, 'a') as f:
                f.write(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 执行定时任务...\n")
            time.sleep(5)

最后,在控制器文件中实现对这两个守护进程的启停控制:

# controller.py
import sys
import time
from your_daemon_module import LogCollectorDaemon, ScheduledTaskDaemon  # 替换为你的Daemon类所在模块名

def main():
    if len(sys.argv) != 3:
        print("用法: python controller.py <daemon_name> <start|stop|restart>")
        print("可选daemon_name: log_collector, scheduled_task")
        sys.exit(1)

    daemon_name = sys.argv[1]
    action = sys.argv[2]

    # 根据参数选择对应的守护进程实例
    daemon = None
    if daemon_name == 'log_collector':
        daemon = LogCollectorDaemon()
    elif daemon_name == 'scheduled_task':
        daemon = ScheduledTaskDaemon()
    else:
        print(f"未知的守护进程名称: {daemon_name}")
        sys.exit(1)

    # 执行对应的操作
    if action == 'start':
        daemon.start()
    elif action == 'stop':
        daemon.stop()
    elif action == 'restart':
        daemon.restart()
    else:
        print(f"未知操作: {action},可选值: start|stop|restart")
        sys.exit(1)

if __name__ == "__main__":
    main()

常见问题排查

  • PID文件冲突:每个子类必须使用独立的PID文件,否则会出现"进程已运行"的错误。
  • 权限不足:如果把PID文件放在/var/run或日志放在/var/log,需要用root权限运行脚本(比如sudo python controller.py log_collector start)。
  • run()方法未重写:如果子类没有实现run()方法,启动时会抛出NotImplementedError,一定要记得重写这个方法。
  • 日志混乱:不要让多个守护进程共享同一个输出/错误文件,否则日志会互相覆盖,难以排查问题。

内容的提问来源于stack exchange,提问作者Srivatsa M

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 12:23:37