summaryrefslogtreecommitdiff
path: root/daemon.py
blob: a59754102f50cb89fcd72451fb5d543ad5dfbca5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
"""A daemon mix-in class.

:copyright: 2000-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
:license: General Public License version 2 - http://www.gnu.org/licenses
"""
__docformat__ = "restructuredtext en"

import os, signal, sys, time
from logilab.common.logger import make_logger, LOG_ALERT, LOG_NOTICE

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
        treshold = configmod.LOG_TRESHOLD
        if configmod.NODETACH:
            configmod.log = make_logger('print', treshold, self.name).log
        else:
            configmod.log = make_logger('syslog', treshold, self.name).log
        self.config = configmod

    def _daemonize(self):
        if not self.config.NODETACH:
            # fork so the parent can exist
            if (os.fork()):
                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()):
                return -1
            # move to the root to avoit mount pb
            os.chdir('/')
            # set paranoid umask
            os.umask(077)
            # write pid in a file
            f = open(self._pid_file, 'w')
            f.write(str(os.getpid()))
            f.close()
            # close standard descriptors
            sys.stdin.close()
            sys.stdout.close()
            sys.stderr.close()
            # put signal handler
            signal.signal(signal.SIGTERM, self.signal_handler)
            signal.signal(signal.SIGHUP, self.signal_handler)

    def run(self):
        """ optionaly go in daemon mode and
        do what concrete classe has to do and pauses for delay between runs
        If self.delay is negative, do a pause before starting
        """
        if self._daemonize() == -1:
            return
        self.config.log(LOG_NOTICE, '%s instance started' % self.name)
        if self.delay < 0:
            self.delay = -self.delay
            time.sleep(self.delay)
        while 1:
            try:
                self._run()
            except Exception, e:
                # display for info, sleep, and hope the problem will be solved
                # later.
                self.config.log(LOG_ALERT, 'Internal error: %s'%(e))
            if not self._alive:
                break
            try:
                self._sleeping = 1
                time.sleep(self.delay)
                self._sleeping = 0
            except SystemExit:
                break
        self.config.log(LOG_NOTICE, '%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.log(LOG_NOTICE, 'exit on SIGTERM')
                sys.exit(0)
            else:
                self.config.log(LOG_NOTICE, 'exit on SIGTERM (on next turn)')
                self._alive = 0
        elif sig_num == signal.SIGHUP:
            self.config.log(LOG_NOTICE, 'reloading configuration on SIGHUP')
            reload(self.config)

    def _run(self):
        """should be overidden in the mixed class"""
        raise NotImplementedError()

## 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_level>
    log treshold (7 record everything, 0 record only emergency.)
    Defaults to %s
  --delay <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