# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of logilab-common. # # logilab-common is free software: you can redistribute it and/or modify it under # the terms of the GNU Lesser General Public License as published by the Free # Software Foundation, either version 2.1 of the License, or (at your option) any # later version. # # logilab-common is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License along # with logilab-common. If not, see . """A daemonize function (for Unices) and daemon mix-in class""" __docformat__ = "restructuredtext en" import os import errno import signal import sys import time import warnings def daemonize(pidfile=None, uid=None): # See http://www.erlenstar.demon.co.uk/unix/faq_toc.html#TOC16 # XXX unix specific # # fork so the parent can exit if os.fork(): # launch child and... return 1 # deconnect from tty and create a new session os.setsid() # fork again so the parent, (the session group leader), can exit. # as a non-session group leader, we can never regain a controlling # terminal. if os.fork(): # launch child again. return 1 # move to the root to avoit mount pb os.chdir('/') # set paranoid umask os.umask(077) # redirect standard descriptors null = os.open('/dev/null', os.O_RDWR) for i in range(3): try: os.dup2(null, i) except OSError, e: if e.errno != errno.EBADF: raise os.close(null) # filter warnings warnings.filterwarnings('ignore') # write pid in a file if pidfile: # ensure the directory where the pid-file should be set exists (for # instance /var/run/cubicweb may be deleted on computer restart) piddir = os.path.dirname(pidfile) if not os.path.exists(piddir): os.makedirs(piddir) f = file(pidfile, 'w') f.write(str(os.getpid())) f.close() # change process uid if uid: try: uid = int(uid) except ValueError: from pwd import getpwnam uid = getpwnam(uid).pw_uid os.setuid(uid) return None class DaemonMixIn: """Mixin to make a daemon from watchers/queriers. """ def __init__(self, configmod) : self.delay = configmod.DELAY self.name = str(self.__class__).split('.')[-1] self._pid_file = os.path.join('/tmp', '%s.pid'%self.name) if os.path.exists(self._pid_file): raise Exception('''Another instance of %s must be running. If it i not the case, remove the file %s''' % (self.name, self._pid_file)) self._alive = 1 self._sleeping = 0 self.config = configmod def _daemonize(self): if not self.config.NODETACH: if daemonize(self._pid_file) is None: # put signal handler signal.signal(signal.SIGTERM, self.signal_handler) signal.signal(signal.SIGHUP, self.signal_handler) else: return -1 def run(self): """ optionally go in daemon mode and do what concrete class has to do and pauses for delay between runs If self.delay is negative, do a pause before starting """ if self._daemonize() == -1: return if self.delay < 0: self.delay = -self.delay time.sleep(self.delay) while 1: try: self._run() except Exception, ex: # display for info, sleep, and hope the problem will be solved # later. self.config.exception('Internal error: %s', ex) if not self._alive: break try: self._sleeping = 1 time.sleep(self.delay) self._sleeping = 0 except SystemExit: break self.config.info('%s instance exited', self.name) # remove pid file os.remove(self._pid_file) def signal_handler(self, sig_num, stack_frame): if sig_num == signal.SIGTERM: if self._sleeping: # we are sleeping so we can exit without fear self.config.debug('exit on SIGTERM') sys.exit(0) else: self.config.debug('exit on SIGTERM (on next turn)') self._alive = 0 elif sig_num == signal.SIGHUP: self.config.info('reloading configuration on SIGHUP') reload(self.config) def _run(self): """should be overridden in the mixed class""" raise NotImplementedError() import logging from logilab.common.logging_ext import set_log_methods set_log_methods(DaemonMixIn, logging.getLogger('lgc.daemon')) ## command line utilities ###################################################### L_OPTIONS = ["help", "log=", "delay=", 'no-detach'] S_OPTIONS = 'hl:d:n' def print_help(modconfig): print """ --help or -h displays this message --log log treshold (7 record everything, 0 record only emergency.) Defaults to %s --delay the number of seconds between two runs. Defaults to %s""" % (modconfig.LOG_TRESHOLD, modconfig.DELAY) def handle_option(modconfig, opt_name, opt_value, help_meth): if opt_name in ('-h','--help'): help_meth() sys.exit(0) elif opt_name in ('-l','--log'): modconfig.LOG_TRESHOLD = int(opt_value) elif opt_name in ('-d', '--delay'): modconfig.DELAY = int(opt_value) elif opt_name in ('-n', '--no-detach'): modconfig.NODETACH = 1