Package logilab :: Package common :: Module daemon
[frames] | no frames]

Source Code for Module logilab.common.daemon

  1  # copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. 
  2  # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr 
  3  # 
  4  # This file is part of logilab-common. 
  5  # 
  6  # logilab-common is free software: you can redistribute it and/or modify it under 
  7  # the terms of the GNU Lesser General Public License as published by the Free 
  8  # Software Foundation, either version 2.1 of the License, or (at your option) any 
  9  # later version. 
 10  # 
 11  # logilab-common is distributed in the hope that it will be useful, but WITHOUT 
 12  # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
 13  # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more 
 14  # details. 
 15  # 
 16  # You should have received a copy of the GNU Lesser General Public License along 
 17  # with logilab-common.  If not, see <http://www.gnu.org/licenses/>. 
 18  """A daemonize function (for Unices) and daemon mix-in class""" 
 19   
 20  __docformat__ = "restructuredtext en" 
 21   
 22  import os 
 23  import errno 
 24  import signal 
 25  import sys 
 26  import time 
 27  import warnings 
 28   
 29   
30 -def daemonize(pidfile=None, uid=None, umask=077):
31 """daemonize a Unix process. Set paranoid umask by default. 32 33 Return 1 in the original process, 2 in the first fork, and None for the 34 second fork (eg daemon process). 35 """ 36 # See http://www.erlenstar.demon.co.uk/unix/faq_toc.html#TOC16 37 # XXX unix specific 38 # 39 # fork so the parent can exit 40 if os.fork(): # launch child and... 41 return 1 42 # deconnect from tty and create a new session 43 os.setsid() 44 # fork again so the parent, (the session group leader), can exit. 45 # as a non-session group leader, we can never regain a controlling 46 # terminal. 47 if os.fork(): # launch child again. 48 return 2 49 # move to the root to avoit mount pb 50 os.chdir('/') 51 # set umask if specified 52 if umask is not None: 53 os.umask(umask) 54 # redirect standard descriptors 55 null = os.open('/dev/null', os.O_RDWR) 56 for i in range(3): 57 try: 58 os.dup2(null, i) 59 except OSError, e: 60 if e.errno != errno.EBADF: 61 raise 62 os.close(null) 63 # filter warnings 64 warnings.filterwarnings('ignore') 65 # write pid in a file 66 if pidfile: 67 # ensure the directory where the pid-file should be set exists (for 68 # instance /var/run/cubicweb may be deleted on computer restart) 69 piddir = os.path.dirname(pidfile) 70 if not os.path.exists(piddir): 71 os.makedirs(piddir) 72 f = file(pidfile, 'w') 73 f.write(str(os.getpid())) 74 f.close() 75 # change process uid 76 if uid: 77 try: 78 uid = int(uid) 79 except ValueError: 80 from pwd import getpwnam 81 uid = getpwnam(uid).pw_uid 82 os.setuid(uid) 83 return None
84 85
86 -class DaemonMixIn:
87 """Mixin to make a daemon from watchers/queriers. 88 """ 89
90 - def __init__(self, configmod) :
91 self.delay = configmod.DELAY 92 self.name = str(self.__class__).split('.')[-1] 93 self._pid_file = os.path.join('/tmp', '%s.pid'%self.name) 94 if os.path.exists(self._pid_file): 95 raise Exception('''Another instance of %s must be running. 96 If it i not the case, remove the file %s''' % (self.name, self._pid_file)) 97 self._alive = 1 98 self._sleeping = 0 99 self.config = configmod
100
101 - def _daemonize(self):
102 if not self.config.NODETACH: 103 if daemonize(self._pid_file) is None: 104 # put signal handler 105 signal.signal(signal.SIGTERM, self.signal_handler) 106 signal.signal(signal.SIGHUP, self.signal_handler) 107 else: 108 return -1
109
110 - def run(self):
111 """ optionally go in daemon mode and 112 do what concrete class has to do and pauses for delay between runs 113 If self.delay is negative, do a pause before starting 114 """ 115 if self._daemonize() == -1: 116 return 117 if self.delay < 0: 118 self.delay = -self.delay 119 time.sleep(self.delay) 120 while True: 121 try: 122 self._run() 123 except Exception, ex: 124 # display for info, sleep, and hope the problem will be solved 125 # later. 126 self.config.exception('Internal error: %s', ex) 127 if not self._alive: 128 break 129 try: 130 self._sleeping = 1 131 time.sleep(self.delay) 132 self._sleeping = 0 133 except SystemExit: 134 break 135 self.config.info('%s instance exited', self.name) 136 # remove pid file 137 os.remove(self._pid_file)
138
139 - def signal_handler(self, sig_num, stack_frame):
140 if sig_num == signal.SIGTERM: 141 if self._sleeping: 142 # we are sleeping so we can exit without fear 143 self.config.debug('exit on SIGTERM') 144 sys.exit(0) 145 else: 146 self.config.debug('exit on SIGTERM (on next turn)') 147 self._alive = 0 148 elif sig_num == signal.SIGHUP: 149 self.config.info('reloading configuration on SIGHUP') 150 reload(self.config)
151
152 - def _run(self):
153 """should be overridden in the mixed class""" 154 raise NotImplementedError()
155 156 157 import logging 158 from logilab.common.logging_ext import set_log_methods 159 set_log_methods(DaemonMixIn, logging.getLogger('lgc.daemon')) 160 161 ## command line utilities ###################################################### 162 163 L_OPTIONS = ["help", "log=", "delay=", 'no-detach'] 164 S_OPTIONS = 'hl:d:n' 165 175
176 -def handle_option(modconfig, opt_name, opt_value, help_meth):
177 if opt_name in ('-h', '--help'): 178 help_meth() 179 sys.exit(0) 180 elif opt_name in ('-l', '--log'): 181 modconfig.LOG_TRESHOLD = int(opt_value) 182 elif opt_name in ('-d', '--delay'): 183 modconfig.DELAY = int(opt_value) 184 elif opt_name in ('-n', '--no-detach'): 185 modconfig.NODETACH = 1
186