2012-04-18 50 views
1

我目前正在嘗試手動創建一個簡單的守護進程,我不想使用現有的外部庫來避免開銷。實現一個完整的Python Unix風格的守護進程

我目前正在檢查我的進程運行時是否沒有已經創建的PID文件(意味着它正在運行),如this post中所述。

我也有一個daemonizing模塊從當前進程分離PID和重定向輸出和錯誤(所以我的守護進程將繼續運行,即使我結束我的演講):

import os 
import sys 

def daemonize(stdin="/dev/null", stdout="/dev/null", stderr="/dev/null"): 
    try: 
     pid = os.fork() 
     if pid > 0: 
      sys.exit(0) 
    except OSError, e: 
     sys.stderr.write ("fork #1 failed: (%d) %s\n" % (e.errno, e.strerror)) 
     sys.exit(1) 

    os.chdir("/") 
    os.umask(0) 
    os.setsid() 

    try: 
     pid = os.fork() 
     if pid > 0: 
      sys.exit(0) 
    except OSError, e: 
     sys.stderr.write ("fork #2 failed: (%d) %s\n" % (e.errno, e.strerror)) 
     sys.exit(1) 

    stdin_par = os.path.dirname(stdin) 
    stdout_par = os.path.dirname(stdout) 
    stderr_par = os.path.dirname(stderr) 
    if not stdin_par: 
     os.path.makedirs(stdin_par) 
    if not stdout_par: 
     os.path.makedirs(stdout_par) 
    if not stderr_par: 
     os.path.makedirs(stderr_par) 

    si = open(stdin, 'r') 
    so = open(stdout, 'a+') 
    se = open(stderr, 'a+', 0) 
    os.dup2(si.fileno(), sys.stdin.fileno()) 
    os.dup2(so.fileno(), sys.stdout.fileno()) 
    os.dup2(se.fileno(), sys.stderr.fileno()) 

所以目前我可以運行我的過程是怎樣的示例如下行,它會正常運行我的守護:

>$ python myapp.py 

但阻止它,我已經到grep的PID(或把它從鎖文件),並手動刪除PID後:

>$ ps -ef | grep myapp 
xxxxx 11901  1 0 19:48 ?  00:00:00 python src/myapp.py 
xxxxx 12282 7600 0 19:54 pts/7 00:00:00 grep myapp 
>$ kill -9 11901 
>$ rm -rf /path/to/lock.pid 

我想有一個更類似於Unix的守護在那裏我可以管理使用以下命令守護生命週期:

>$ python myapp.py start 
>$ python myapp.py stop 
>$ python myapp.py restart 

我當然可以與​​模塊做到這一點,但似乎有點乏味和醜陋。

你知道一個簡單而優雅的解決方案來在Python中擁有Unix風格的守護進程嗎?

+1

我認爲你正在將守護進程與啓動腳本混淆。他們通常是不同的東西。我的意思是,_Unix-like_守護進程行爲不必包括停止和重新啓動設施,它只是TTY分離機制。 – C2H5OH 2012-04-18 20:06:01

+0

你可以嘗試使用'daemon'包:http://pypi.python.org/pypi/python-daemon/ – Blender 2012-04-18 20:06:07

+0

@ C2H5OH是的,你說得對,我確實有守護進程工作正常,但我想最後讓它由python應用程序本身管理,而不必手動殺死或rm。 – 2012-04-18 20:07:32

回答

5

看了一會兒後,我發現一個很好的例子,只是這個here

它採用了通用Daemon類,它可以事後被繼承:

#!/usr/bin/env python 

import sys, os, time, atexit 
from signal import SIGTERM 

class Daemon: 
     """ 
     A generic daemon class. 

     Usage: subclass the Daemon class and override the run() method 
     """ 
     def __init__(self, pidfile, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'): 
       self.stdin = stdin 
       self.stdout = stdout 
       self.stderr = stderr 
       self.pidfile = pidfile 

     def daemonize(self): 
       """ 
       do the UNIX double-fork magic, see Stevens' "Advanced 
       Programming in the UNIX Environment" for details (ISBN 0201563177) 
       http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16 
       """ 
       try: 
         pid = os.fork() 
         if pid > 0: 
           # exit first parent 
           sys.exit(0) 
       except OSError, e: 
         sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror)) 
         sys.exit(1) 

       # decouple from parent environment 
       os.chdir("/") 
       os.setsid() 
       os.umask(0) 

       # do second fork 
       try: 
         pid = os.fork() 
         if pid > 0: 
           # exit from second parent 
           sys.exit(0) 
       except OSError, e: 
         sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror)) 
         sys.exit(1) 

       # redirect standard file descriptors 
       sys.stdout.flush() 
       sys.stderr.flush() 
       si = file(self.stdin, 'r') 
       so = file(self.stdout, 'a+') 
       se = file(self.stderr, 'a+', 0) 
       os.dup2(si.fileno(), sys.stdin.fileno()) 
       os.dup2(so.fileno(), sys.stdout.fileno()) 
       os.dup2(se.fileno(), sys.stderr.fileno()) 

       # write pidfile 
       atexit.register(self.delpid) 
       pid = str(os.getpid()) 
       file(self.pidfile,'w+').write("%s\n" % pid) 

     def delpid(self): 
       os.remove(self.pidfile) 

     def start(self): 
       """ 
       Start the daemon 
       """ 
       # Check for a pidfile to see if the daemon already runs 
       try: 
         pf = file(self.pidfile,'r') 
         pid = int(pf.read().strip()) 
         pf.close() 
       except IOError: 
         pid = None 

       if pid: 
         message = "pidfile %s already exist. Daemon already running?\n" 
         sys.stderr.write(message % self.pidfile) 
         sys.exit(1) 

       # Start the daemon 
       self.daemonize() 
       self.run() 

     def stop(self): 
       """ 
       Stop the daemon 
       """ 
       # Get the pid from the pidfile 
       try: 
         pf = file(self.pidfile,'r') 
         pid = int(pf.read().strip()) 
         pf.close() 
       except IOError: 
         pid = None 

       if not pid: 
         message = "pidfile %s does not exist. Daemon not running?\n" 
         sys.stderr.write(message % self.pidfile) 
         return # not an error in a restart 

       # Try killing the daemon process  
       try: 
         while 1: 
           os.kill(pid, SIGTERM) 
           time.sleep(0.1) 
       except OSError, err: 
         err = str(err) 
         if err.find("No such process") > 0: 
           if os.path.exists(self.pidfile): 
             os.remove(self.pidfile) 
         else: 
           print str(err) 
           sys.exit(1) 

     def restart(self): 
       """ 
       Restart the daemon 
       """ 
       self.stop() 
       self.start() 

     def run(self): 
       """ 
       You should override this method when you subclass Daemon. It will be called after the process has been 
      daemonized by start() or restart(). 
       """ 

一旦你有了這個模塊,你可以做以下有你的守護進程的所有不同的模式:

#!/usr/bin/env python 

import sys, time 
from daemon import Daemon 

class MyDaemon(Daemon): 
     def run(self): 
       while True: 
         time.sleep(1) 

if __name__ == "__main__": 
     daemon = MyDaemon('/tmp/daemon-example.pid') 
     if len(sys.argv) == 2: 
       if 'start' == sys.argv[1]: 
         daemon.start() 
       elif 'stop' == sys.argv[1]: 
         daemon.stop() 
       elif 'restart' == sys.argv[1]: 
         daemon.restart() 
       else: 
         print "Unknown command" 
         sys.exit(2) 
       sys.exit(0) 
     else: 
       print "usage: %s start|stop|restart" % sys.argv[0] 
       sys.exit(2) 

希望能幫助其他遇到同樣問題的人!

+0

請注意,第二個叉在現代Unices中不是必需的。 – C2H5OH 2012-04-18 20:28:10

0

爲了緩解信令守護進程的過程中,我們用下面的函數工作:

def set_procname(name): 
    import ctypes 
    lc = ctypes.cdll.LoadLibrary("libc.so.6") 
    lc.prctl(15, name[:15]) 

然後,守護進程可以使用它:

set_procname("foo") 

然後我們可以做pkill foo發送TERM信號。這是不是一個正確的答案,但它可能是有用的。但是,set_procname()函數有一些限制:

  • 它只適用於Linux內核。
  • 進程名稱最多隻能使用15個字符。
  • 如果啓動相同守護程序進程的更多實例,pkill會將它們全部發信號。