【问题标题】:How do I set up a daemon with python-daemon?如何使用 python-daemon 设置守护进程?
【发布时间】:2012-10-17 20:35:35
【问题描述】:

我是守护进程的新手,如果这是一个新手问题,我深表歉意。

在其他几个答案(例如this question)中,人们建议使用python-daemon 包,因为它完全实现了PEP 3143 标准。

不幸的是,python-daemon 是a bit light on documentation(或者更可能我对知识/经验有点了解...... ;)),我想我可能错过了一些非常基本的东西。这就是我正在做的事情:

我有以下几点:

import daemon

logfile = open('daemon.log', 'w')

context = daemon.DaemonContext(stdout = logfile, stderr = logfile)

context.open()

with context:
    do_something_1()
    do_something_2()

问题:如何使用 python-daemon 设置守护程序,如何启动和停止它?


旁注:

我基本上是在猜测这里应该如何/是否应该使用 .open() 方法——文档在这一点上并不清楚。无论我是否包含它,似乎都会发生同样的事情。

那么,现在我该怎么办?当我尝试运行这个文件时,例如:

python startConsumerDaemons.py

它似乎运行do_something_1(),但不是第二个。而且,它似乎将程序 附加 留在了终端窗口中。 IE,stdout 没有被重定向,当我关闭终端窗口时,进程被终止。所以,我很确定我在这里做错了什么......我应该做些什么不同的事情?

最后,一旦我让守护程序运行,我该如何停止/重新启动它(例如,如果我对底层代码进行了更改)?

【问题讨论】:

  • 最后一个with 应该做什么? (模块将在第一次导入时运行;除非我遗漏了什么,否则最后的语句将什么都不做)你所说的“运行第一个脚本而不是第二个”是什么意思?如果没有引发异常,那么我很有信心两者都成功运行。
  • 不知道你所说的“last” with 是什么意思(只有一个),但是在 python-daemon 的示例代码中,他将要作为守护进程执行的脚本放在 with 语句中像那样(python.org/dev/peps/pep-3143)。第一个脚本执行并能够接收/处理消息(它是 MQ 代理上的消费者)。因为第一个是无限循环的,也许 startConsumerDaemons 脚本永远不会到达第二个?
  • 您发布的示例导入来自另一个文件(initial_program_setupdo_main_program 等)的一些方法并调用它们(do_main_program() ),其中一个在 with 声明中。除非 Python 有一些我不知道的晦涩特性,否则我相信这些语句不会有任何作用。无论如何,据我所知,python-daemon 只是将当前正在执行的程序变成一个 UNIX 守护进程,它不会创建新进程或新线程。如果程序的某一部分进入了无限循环,那么后面的部分就根本不会运行。
  • 如果你想守护python进程,考虑使用supervisord.org。 python-daemon 有错误的文档并且已经失去了开发。此外,使用主管,您不必修改任何现有代码。

标签: python daemon python-daemon


【解决方案1】:

这是带有 argparse 和信号处理的 2020 python-daemon 样板。如需带日志记录的版本,请访问here

import os, sys
import datetime
import time
import daemon
import daemon.pidfile
import argparse
import signal
import logging

PROGNAME = 'monitor'
PATHCTRL = '/tmp/' #path to control files pid and lock
pidpath = os.path.join(PATHCTRL,  PROGNAME + ".pid")
parser = argparse.ArgumentParser(prog = PROGNAME)

sp = parser.add_subparsers()
sp_start = sp.add_parser('start', help='Starts %(prog)s daemon')
sp_stop = sp.add_parser('stop', help='Stops %(prog)s daemon')
sp_status = sp.add_parser('status', help='Show the status of %(prog)s daemon')
sp_restart = sp.add_parser('restart', help='Restarts %(prog)s daemon')
sp_debug = sp.add_parser('debug', help='Starts %(prog)s daemon in debug mode')
sp_start.add_argument('-v', '--verbose', action='store_true', help='log extra informations')
sp_debug.add_argument('-v', '--verbose', action='store_true', help='log extra informations')

class MainCtrl:
  thread_continue = True
  thread_token = "token"

mainctrl = MainCtrl()

def main_thread_stop(signum=None, frame=None):
    mainctrl.thread_continue = False
    mainctrl.thread_token = "test"
 
def main_thread(args, mainctrl):
    verbose = False
    if hasattr(args, 'mainctrl'):
      verbose = args.verbose
    if verbose:
      print("MAIN:{0}".format(args))
    try:
      while mainctrl.thread_continue:
         if verbose:
            print("TOKEN:{0}".format(mainctrl.thread_token))
         time.sleep(1)
    except KeyboardInterrupt as ke:
      if verbose:
        print("INFO: Existing...") 
    except Exception as e:
      if verbose:
        print("ERROR: Exception:{0}".format(str(e)))    

def daemon_start(args=None):
    print("INFO: Daemon Start")
    if os.path.exists(pidpath):
      print("INFO: Daemon already running (according to {0}).".format(pidpath))
      sys.exit(1)
    else:
      with daemon.DaemonContext(
        stdout=sys.stdout,
        stderr=sys.stderr,
        signal_map={
            signal.SIGTERM: main_thread_stop,
            signal.SIGTSTP: main_thread_stop,
            signal.SIGINT: main_thread_stop,
            #signal.SIGKILL: daemon_stop, #SIGKILL is an Invalid argument
            signal.SIGUSR1: daemon_status,
            signal.SIGUSR2: daemon_status,
          },
          pidfile = daemon.pidfile.PIDLockFile(pidpath)
          ):
        print("INFO: Running daemon...")
        main_thread(args, mainctrl)

def daemon_restart(args):
    print("INFO: Daemon Restart")
    daemon_stop()
    time.sleep(1)
    daemon_start(args)

def daemon_stop(args=None):
    print("INFO: Daemon Stop {0}".format(args))
    if os.path.exists(pidpath):
      with open(pidpath) as pid:
        try:
          os.kill(int(pid.readline()), signal.SIGINT)
        except ProcessLookupError as ple:
          os.remove(pidpath)
          print("ERROR: {0}".format(ple))
    else:
      print("INFO: process isn't running (according to the absence of {0}).".format(pidpath))

def daemon_debug(args):
    print("INFO: Daemon debug")
    main_thread(args, mainctrl)

def daemon_status(args):
    print("INFO: Daemon Status")
    if os.path.exists(pidpath):
      print("INFO: Daemon is running")
    else:
      print("INFO: Daemon is not running.")

sp_stop.set_defaults(callback=daemon_stop)
sp_status.set_defaults(callback=daemon_status)
sp_start.set_defaults(callback=daemon_start)
sp_restart.set_defaults(callback=daemon_restart)
sp_debug.set_defaults(callback=daemon_debug)

args = parser.parse_args()

if hasattr(args, 'callback'):
  args.callback(args)
else:
  parser.print_help()

【讨论】:

    【解决方案2】:

    模块“python-daemon”仍然缺少有用的文档。我个人放弃了使用它,现在我成功使用了 Sander Marechal 的守护进程代码referenced in this answer

    我稍微修改了它,以便在您致电python testdaemon.py stop 时能够执行操作。编辑 2020/05/07:修改并使用 Python 3。

    Here is the code.


    示例用法:

    import sys, daemon, time
    
    class testdaemon(daemon.Daemon):
        def run(self):
            self.i = 0
            with open('test1.txt', 'w') as f:
                f.write(str(self.i))
            while True:
                self.i += 1
                time.sleep(1)
    
        def quit(self):
            with open('test2.txt', 'w') as f:
                f.write(str(self.i))
    
    daemon = testdaemon()
    
    if 'start' == sys.argv[1]: 
        daemon.start()
    elif 'stop' == sys.argv[1]: 
        daemon.stop()
    elif 'restart' == sys.argv[1]: 
        daemon.restart()
    

    【讨论】:

    • 我遇到以下错误:AttributeError: module 'daemon' has no attribute 'Daemon'
    【解决方案3】:

    daemon.DaemonContext 构造函数接受 lockfile 选项。使用将记录进程 PID 的 lockfile 库。

    该库最初推荐使用 lockfile.PIDLockFile 类,但该库现在已被弃用,没有很好的替代品。但是你可以实现另一个具有相同语义的对象。

    然后,只需通过读取命名PID文件的内容即可找到进程的PID。使用该 PID 向正在运行的守护程序发送信号。

    【讨论】:

    • 通过使用lockfile 如何使用daemon.DaemonContext 停止守护进程? @bignose
    【解决方案4】:

    这是我所拥有的,对我有用。它还有一个 sysv 初始化脚本。 Repo is at GitHub,我也有 a brief blog post 以及我找到的其他可能解决方案的链接。

    只有一个守护进程在运行:与大多数其他 Linux 守护进程一样,由 PID 锁定文件管理。要阻止它,请执行

    kill `cat /var/run/eg_daemon.pid`
    

    查看它是否正在运行:

    ps -elf | grep `cat /var/run/eg_daemon.pid`
    

    使用 pidfile 子模块,自动管理 PID 文件。当守护进程停止时,pidfile 被清除。请参阅链接的 GitHub 存储库以获取 init 脚本。

    这是 Python 守护程序代码:

    #!/usr/bin/env python3.5
    import sys
    import os
    import time
    import argparse
    import logging
    import daemon
    from daemon import pidfile
    
    debug_p = False
    
    def do_something(logf):
        ### This does the "work" of the daemon
    
        logger = logging.getLogger('eg_daemon')
        logger.setLevel(logging.INFO)
    
        fh = logging.FileHandler(logf)
        fh.setLevel(logging.INFO)
    
        formatstr = '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
        formatter = logging.Formatter(formatstr)
    
        fh.setFormatter(formatter)
    
        logger.addHandler(fh)
    
        while True:
            logger.debug("this is a DEBUG message")
            logger.info("this is an INFO message")
            logger.error("this is an ERROR message")
            time.sleep(5)
    
    
    def start_daemon(pidf, logf):
        ### This launches the daemon in its context
    
        ### XXX pidfile is a context
        with daemon.DaemonContext(
            working_directory='/var/lib/eg_daemon',
            umask=0o002,
            pidfile=pidfile.TimeoutPIDLockFile(pidf),
            ) as context:
            do_something(logf)
    
    
    if __name__ == "__main__":
        parser = argparse.ArgumentParser(description="Example daemon in Python")
        parser.add_argument('-p', '--pid-file', default='/var/run/eg_daemon.pid')
        parser.add_argument('-l', '--log-file', default='/var/log/eg_daemon.log')
    
        args = parser.parse_args()
    
        start_daemon(pidf=args.pid_file, logf=args.log_file)
    

    为了完整起见,这里是初始化脚本。请注意,“kill”实际上只是一种发送 POSIX 信号的方法——有关概述,请参见 signal(7) 的手册页。 python-daemon 上下文将捕获信号,终止进程干净地关闭文件描述符,并自动删除 PID 文件。所以,这确实是一个干净的终止。

    您可以编写代码来捕获 SIGUSR1 或类似的东西,以便重新加载守护程序配置。编写 Python 停止守护进程没有任何好处。

    #!/bin/bash
    #
    # eg_daemon      Startup script for eg_daemon
    #
    # chkconfig: - 87 12
    # description: eg_daemon is a dummy Python-based daemon
    # config: /etc/eg_daemon/eg_daemon.conf
    # config: /etc/sysconfig/eg_daemon
    # pidfile: /var/run/eg_daemon.pid
    #
    ### BEGIN INIT INFO
    # Provides: eg_daemon
    # Required-Start: $local_fs
    # Required-Stop: $local_fs
    # Short-Description: start and stop eg_daemon server
    # Description: eg_daemon is a dummy Python-based daemon
    ### END INIT INFO
    
    # Source function library.
    . /etc/rc.d/init.d/functions
    
    if [ -f /etc/sysconfig/eg_daemon ]; then
            . /etc/sysconfig/eg_daemon
    fi
    
    eg_daemon=/var/lib/eg_daemon/eg_daemon.py
    prog=eg_daemon
    pidfile=${PIDFILE-/var/run/eg_daemon.pid}
    logfile=${LOGFILE-/var/log/eg_daemon.log}
    RETVAL=0
    
    OPTIONS=""
    
    start() {
            echo -n $"Starting $prog: "
    
            if [[ -f ${pidfile} ]] ; then
                pid=$( cat $pidfile  )
                isrunning=$( ps -elf | grep  $pid | grep $prog | grep -v grep )
    
                if [[ -n ${isrunning} ]] ; then
                    echo $"$prog already running"
                    return 0
                fi
            fi
            $eg_daemon -p $pidfile -l $logfile $OPTIONS
            RETVAL=$?
            [ $RETVAL = 0 ] && success || failure
            echo
            return $RETVAL
    }
    
    stop() {
        if [[ -f ${pidfile} ]] ; then
            pid=$( cat $pidfile )
            isrunning=$( ps -elf | grep $pid | grep $prog | grep -v grep | awk '{print $4}' )
    
            if [[ ${isrunning} -eq ${pid} ]] ; then
                echo -n $"Stopping $prog: "
                kill $pid
            else
                echo -n $"Stopping $prog: "
                success
            fi
            RETVAL=$?
        fi
        echo
        return $RETVAL
    }
    
    reload() {
        echo -n $"Reloading $prog: "
        echo
    }
    
    # See how we were called.
    case "$1" in
      start)
        start
        ;;
      stop)
        stop
        ;;
      status)
        status -p $pidfile $eg_daemon
        RETVAL=$?
        ;;
      restart)
        stop
        start
        ;;
      force-reload|reload)
        reload
        ;;
      *)
        echo $"Usage: $prog {start|stop|restart|force-reload|reload|status}"
        RETVAL=2
    esac
    
    exit $RETVAL
    

    【讨论】:

    • 嗯,stop 方法和kill 不是很好... 用python mydaemon.py stop 等不可以吗?此外,我们可以简化您的示例,仅保留有关启动/停止守护进程等问题的核心。
    • 这是一种标准的 Linux 处理守护进程的方式。没有交互过程。唯一的通信方式是使用 Linux 信号。使用一些 Python 方法意味着结束对 kill 的调用。
    • 在你的 python 代码中我们如何停止 python 进程?
    • Linux中的标准方式:service mydaemon stop
    【解决方案5】:

    正如您在'with' statement documentation 中看到的,it 语句确实执行了一些“魔术”,这与我们的目的有关。具体来说:

    带有一个“项目”的 with 语句的执行过程如下 如下:

    1. 评估上下文表达式(with_item 中给出的表达式)以获得上下文管理器。

    2. 加载上下文管理器的__exit__() 以供以后使用。

    3. 上下文管理器的__enter__()方法被调用。

    4. 如果目标包含在 with 语句中,则将来自 __enter__() 的返回值分配给它。

    5. 套件已执行。

    6. 上下文管理器的__exit__() 方法被调用。如果异常导致套件退出,它的类型、值和 回溯作为参数传递给__exit__()。否则,三个无 提供了参数。

    这是什么意思?如果您仔细查看the PEP in question,它也用作python-daemon 文档(并且确实可以大大改进),您会发现它实现了__enter__()__exit__()

    该类还通过__enter__ 实现上下文管理器协议 和__exit__ 方法。

    __enter__()

    调用实例的 open() 方法,然后返回实例。

    __exit__(exc_type, exc_value, exc_traceback)

    调用实例的close()方法,如果异常被处理则返回True,否则返回False。

    换句话说,不需要 open(),PEP 中给出的示例(尽管没有正确解释)按原样工作。虽然 with 语句确实意味着什么,但它不会保留任何循环,一旦到达其范围的末尾,它就会调用 exit(),这在 python-daemon 中意味着 close()。因此,您需要放一个 while True 或您考虑的无限循环。

    关于你的第二个脚本的行为不工作,我真的不能告诉你,我很惊讶第一个脚本已经工作了。如果您的守护程序正在停止,那么您的脚本肯定有问题,您可以检查您的 consumerDaemonLogFile。 (作为旁注,你有一个错字'sderr' --> 'stderr')

    此外,您可以在 PEP 中看到,如果未指定,则工作目录属性默认为“/”。如果您在脚本中使用相对路径,这可能是问题的根源。

    最后,关于最后一个问题,你可以轻松杀死你的守护进程,找到它的 PID:

    ps ax | grep startConsumerDaemons.py
    

    并向其发送 SIGTERM:

    kill <pid>
    

    gromain 提供的答案确实提供了一种更方便的方法来启动和停止它,使用“daemon.runner()”,但设置起来要复杂得多。

    【讨论】:

      【解决方案6】:

      一个完整的例子是available here

      您应该能够更好地理解 python-daemon 的内部工作原理。

      此外,提供的代码还提供了一个初始化脚本示例,用于简单地启动/停止守护程序。但是,您可以通过使用参数 stop 再次调用原始函数来启动/停止它:

      python original_func.py stop
      

      【讨论】:

        【解决方案7】:

        在 linux 上,你可以通过运行来停止守护进程:

        $ ps -x
        

        并找到与您的守护进程对应的 PID,然后终止该进程。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2014-03-26
          • 1970-01-01
          • 2022-07-15
          • 2023-04-04
          • 1970-01-01
          • 2018-07-03
          • 1970-01-01
          • 2016-02-07
          相关资源
          最近更新 更多