如何使用python-daemon设置守护进程?

时间:2012-10-28 03:49:38

标签: python daemon python-daemon

我是守护进程的新手,如果这是一个新手问题,请道歉。

在其他几个答案中(例如,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()方法进行了大量的猜测 - 在这一点上,docs并不是很明确。无论我是否包括它,似乎都会发生同样的事情。

所以,现在我该怎么办?当我尝试运行此文件时,例如:

python startConsumerDaemons.py

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

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

7 个答案:

答案 0 :(得分:10)

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

只能运行一个守护程序进程:它由PID锁定文件管理,就像大多数其他Linux守护进程一样。要停止它,请执行

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

查看它是否正在运行:

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

使用pidfile子模块自动管理PID文件。守护程序停止后,pidfile将被清除。请参阅链接的GitHub repo以获取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)

为了完整性'为了这个是init脚本。请注意" kill"实际上只是一种发送POSIX信号的方法 - 有关概述,请参阅信号(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

答案 1 :(得分:5)

一个完整的例子是available here

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

此外,提供的代码还提供了一个简单启动/停止守护程序的init脚本示例。但是,您可以通过使用参数stop:

再次调用原始函数来启动/停止它
python original_func.py stop

答案 2 :(得分:3)

正如你在'with' statement documentation中看到的那样,它的声明确实表现出一些'魔术',这与我们的目的有关。具体做法是:

  

带有一个“item”的with语句的执行继续执行   如下:

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

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

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

  6.   
  7. 如果目标包含在with语句中,则会为__enter__()分配返回值。

  8.   
  9. 套件已执行。

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

  12.   

这是什么意思?如果仔细观察the PEP in question,它也可以作为python-daemon文档(确实可以进行大量改进),你会发现它实现了__enter__()__exit__():< / p>

  

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

     

<强> __enter__()

     

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

     

<强> __exit__(exc_type, exc_value, exc_traceback)

     

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

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

如果你的第二个剧本无法正常工作,我真的不能告诉你,我很惊讶第一个已经有效。如果您的守护程序正在停止,那么您的脚本肯定存在问题,您可以检查您的consumerDaemonLogFile。 (作为附注,你有一个拼写错误' - &gt;'stderr')

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

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

ps ax | grep startConsumerDaemons.py

并发送SIGTERM:

kill <pid>

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

答案 3 :(得分: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()

答案 4 :(得分:0)

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

$ ps -x

找到与你的守护进程对应的PID然后杀死进程。

答案 5 :(得分:0)

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

我稍微对其进行了修改,以便在您拨打python testdaemon.py stop时能够执行操作。

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

答案 6 :(得分:0)

daemon.DaemonContext构造函数接受lockfile选项。使用一个锁定文件库来记录进程的PID。

该库最初建议使用lockfile.PIDLockFile类,但是该库现在已被弃用,没有很好的替代方法。但是您可以使用相同的语义实现另一个对象。

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