~ubuntu-branches/ubuntu/natty/moin/natty-updates

« back to all changes in this revision

Viewing changes to MoinMoin/server/daemon.py

  • Committer: Bazaar Package Importer
  • Author(s): Jonas Smedegaard
  • Date: 2008-06-22 21:17:13 UTC
  • mto: This revision was merged to the branch mainline in revision 18.
  • Revision ID: james.westby@ubuntu.com-20080622211713-inlv5k4eifxckelr
ImportĀ upstreamĀ versionĀ 1.7.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""
 
2
Daemon - daemon script and controller
 
3
 
 
4
This module is based on twisted.scripts.twistd, modified by Nir Soffer
 
5
to work with non Twisted code.
 
6
 
 
7
The Daemon class, represents a background process using a pid
 
8
file. When you create an instance, the process may be running or not.
 
9
After creating an instance, you can call one of its do_xxx() methods.
 
10
 
 
11
The DaemonScript class is a script that control a daemon, with a
 
12
functionality similar to apachectl. To create a daemon script, create an
 
13
instacne and call its run() method.
 
14
 
 
15
Typical usage::
 
16
 
 
17
    # Daemon script
 
18
    import daemon
 
19
    import myserver
 
20
    script = daemon.DaemonScript('myserver', myserver.run, myserver.Config)
 
21
    script.run()
 
22
 
 
23
 
 
24
Copyright (c) 2005 Nir Soffer <nirs@freeshell.org>
 
25
 
 
26
Twisted, the Framework of Your Internet
 
27
Copyright (c) 2001-2004 Twisted Matrix Laboratories.
 
28
 
 
29
Permission is hereby granted, free of charge, to any person obtaining
 
30
a copy of this software and associated documentation files (the
 
31
"Software"), to deal in the Software without restriction, including
 
32
without limitation the rights to use, copy, modify, merge, publish,
 
33
distribute, sublicense, and/or sell copies of the Software, and to
 
34
permit persons to whom the Software is furnished to do so, subject to
 
35
the following conditions:
 
36
 
 
37
The above copyright notice and this permission notice shall be
 
38
included in all copies or substantial portions of the Software.
 
39
 
 
40
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 
41
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 
42
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 
43
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 
44
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 
45
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 
46
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
47
"""
 
48
 
 
49
import sys, os, errno, signal, time
 
50
 
 
51
 
 
52
class Error(Exception):
 
53
    """ Daemon error """
 
54
 
 
55
 
 
56
class Daemon:
 
57
    """ A background process
 
58
 
 
59
    Represent a background process, which may be running or not. The
 
60
    process can be started, stopped, restarted or killed.
 
61
    """
 
62
    commandPrefix = 'do_'
 
63
 
 
64
    def __init__(self, name, function, *args, **kw):
 
65
        """ Create a daemon
 
66
 
 
67
        @param name: name of the process (determines pid filename, too)
 
68
        @param function: the server main function, will block until the
 
69
            server is done.
 
70
        @param args: arguments to pass to function
 
71
        @param kw: keyword arguments to pass to function
 
72
        """
 
73
        self.name = name
 
74
        self.function = function
 
75
        self.args = args
 
76
        self.kw = kw
 
77
        self.pidFile = os.path.abspath(name + '.pid')
 
78
 
 
79
    # --------------------------------------------------------------------
 
80
    # Commands
 
81
 
 
82
    def do_start(self):
 
83
        """ Start the daemon process
 
84
 
 
85
        Start will daemonize then block until the server is killed and
 
86
        then cleanup the pid file on the way out.
 
87
        """
 
88
        running, pid = self.status()
 
89
        if running:
 
90
            raise Error("another application is running with pid %s "
 
91
                        "(try restart)" % pid)
 
92
        self.daemonize()
 
93
        self.writePID()
 
94
        try:
 
95
            self.function(*self.args, **self.kw)
 
96
        finally:
 
97
            self.removePID()
 
98
 
 
99
    def do_stop(self):
 
100
        """ Stop the daemon process
 
101
 
 
102
        Terminate or raise an error we can't handle here. On success,
 
103
        the pid file will be cleaned by the terminated process.
 
104
        """
 
105
        running, pid = self.status()
 
106
        if not running:
 
107
            return self.log("%s is not running" % self.name)
 
108
        os.kill(pid, signal.SIGINT)
 
109
 
 
110
    def do_kill(self):
 
111
        """ Kill the daemon process
 
112
 
 
113
        Kill or raise an error which we can't handle here. Clean the
 
114
        pid file for the killed process.
 
115
        """
 
116
        running, pid = self.status()
 
117
        if not running:
 
118
            return self.log("%s is not running" % self.name)
 
119
        os.kill(pid, signal.SIGKILL)
 
120
        self.removePID()
 
121
 
 
122
    def do_restart(self):
 
123
        """ stop, wait until pid file gone and start again """
 
124
        running, pid = self.status()
 
125
        if not running:
 
126
            self.log("%s is not running, trying to start" % self.name)
 
127
        else:
 
128
            self.do_stop()
 
129
        timeoutSeconds = 2.0
 
130
        start = time.time()
 
131
        while time.time() - start < timeoutSeconds:
 
132
            running, pid = self.status()
 
133
            if not running:
 
134
                break
 
135
            time.sleep(0.1)
 
136
        else:
 
137
            raise Error("could not start after %s seconds" % timeoutSeconds)
 
138
        self.do_start()
 
139
 
 
140
    # -------------------------------------------------------------------
 
141
    # Private
 
142
 
 
143
    def status(self):
 
144
        """ Return status tuple (running, pid)
 
145
 
 
146
        See http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC18
 
147
        """
 
148
        running = False
 
149
        pid = self.readPID()
 
150
        if pid is not None:
 
151
            try:
 
152
                os.kill(pid, 0)
 
153
                running = True
 
154
            except OSError, err:
 
155
                if err.errno == errno.ESRCH:
 
156
                    # No such process or security enhancements are causing
 
157
                    # the system to deny its existence.
 
158
                    self.log("removing stale pid file: %s" % self.pidFile)
 
159
                    self.removePID()
 
160
                else:
 
161
                    raise
 
162
        return running, pid
 
163
 
 
164
    def readPID(self):
 
165
        """ Return the pid from the pid file
 
166
 
 
167
        If there is no pid file, return None. If pid file is corrupted,
 
168
        remove it. If its not readable, raise.
 
169
        """
 
170
        pid = None
 
171
        try:
 
172
            pid = int(file(self.pidFile).read())
 
173
        except IOError, err:
 
174
            if err.errno != errno.ENOENT:
 
175
                raise
 
176
        except ValueError:
 
177
            self.warn("removing corrupted pid file: %s" % self.pidFile)
 
178
            self.removePID()
 
179
        return pid
 
180
 
 
181
    def daemonize(self):
 
182
        """ Make the current process a daemon
 
183
 
 
184
        See http://www.erlenstar.demon.co.uk/unix/faq_toc.html#TOC16
 
185
        """
 
186
        if os.fork():   # launch child and...
 
187
            os._exit(0) # kill off parent
 
188
        os.setsid()
 
189
        if os.fork():   # launch child and...
 
190
            os._exit(0) # kill off parent again.
 
191
        os.umask(0077)
 
192
        null = os.open('/dev/null', os.O_RDWR)
 
193
        for i in range(3):
 
194
            try:
 
195
                os.dup2(null, i)
 
196
            except OSError, e:
 
197
                if e.errno != errno.EBADF:
 
198
                    raise
 
199
        os.close(null)
 
200
 
 
201
    def writePID(self):
 
202
        pid = str(os.getpid())
 
203
        open(self.pidFile, 'wb').write(pid)
 
204
 
 
205
    def removePID(self):
 
206
        try:
 
207
            os.remove(self.pidFile)
 
208
        except OSError, err:
 
209
            if err.errno != errno.ENOENT:
 
210
                raise
 
211
 
 
212
    def warn(self, message):
 
213
        self.log('warning: %s' % message)
 
214
 
 
215
    def log(self, message):
 
216
        """ TODO: does it work after daemonize? """
 
217
        sys.stderr.write(message + '\n')
 
218
 
 
219
 
 
220
class DaemonScript(Daemon):
 
221
    """ A script controlling a daemon
 
222
 
 
223
    TODO: add --pid-dir option?
 
224
    """
 
225
 
 
226
    def run(self):
 
227
        """ Check commandline arguments and run a command """
 
228
        args = len(sys.argv)
 
229
        if args == 1:
 
230
            self.usage('nothing to do')
 
231
        elif args > 2:
 
232
            self.usage("too many arguments")
 
233
        try:
 
234
            command = sys.argv[1]
 
235
            func = getattr(self, self.commandPrefix + command)
 
236
            func()
 
237
        except AttributeError:
 
238
            self.usage('unknown command %r' % command)
 
239
        except Exception, why:
 
240
            sys.exit("error: %s" % str(why))
 
241
 
 
242
    def usage(self, message):
 
243
        sys.stderr.write('error: %s\n' % message)
 
244
        sys.stderr.write(self.help())
 
245
        sys.exit(1)
 
246
 
 
247
    def help(self):
 
248
        return """
 
249
%(name)s - MoinMoin daemon
 
250
 
 
251
usage: %(name)s command
 
252
 
 
253
commands:
 
254
  start     start the server
 
255
  stop      stop the server
 
256
  restart   stop then start the server
 
257
  kill      kill the server
 
258
 
 
259
@copyright: 2004-2005 Thomas Waldmann, Nir Soffer
 
260
@license: GNU GPL, see COPYING for details.
 
261
""" % {
 
262
    'name': self.name,
 
263
}
 
264