forked from dimier/python-daemon
-
Notifications
You must be signed in to change notification settings - Fork 1
/
pythonDaemon.py
234 lines (197 loc) · 6.71 KB
/
pythonDaemon.py
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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
"""
***
Modified generic daemon class
***
Author: http://www.jejik.com/articles/2007/02/
a_simple_unix_linux_daemon_in_python/www.boxedice.com
https://github.com/serverdensity/python-daemon
License: http://creativecommons.org/licenses/by-sa/3.0/
Changes: Various fixes where added in signal handling, pid file handling,
return codes and exception handling
"""
# Core modules
import atexit
import os
import sys
import time
import signal
import logging
import traceback
class Daemon(object):
"""
A generic daemon class.
Usage: subclass the Daemon class and override the run() method
"""
def __init__(self, pidfile, stdin=os.devnull,
stdout=os.devnull, stderr=os.devnull,
home_dir='.', umask=022, verbose=1):
self.stdin = stdin
self.stdout = stdout
self.stderr = stderr
self.pidfile = pidfile
self.home_dir = home_dir
self.verbose = verbose
self.umask = umask
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))
os._exit(1)
# Decouple from parent environment
os.chdir(self.home_dir)
os.setsid()
os.umask(self.umask)
# 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))
os._exit(1)
if sys.platform != 'darwin': # This block breaks on OS X
# Redirect standard file descriptors
sys.stdout.flush()
sys.stderr.flush()
si = file(self.stdin, 'r')
so = file(self.stdout, 'a+')
if self.stderr:
se = file(self.stderr, 'a+', 0)
else:
se = so
os.dup2(si.fileno(), sys.stdin.fileno())
os.dup2(so.fileno(), sys.stdout.fileno())
os.dup2(se.fileno(), sys.stderr.fileno())
signal.signal(signal.SIGTERM, self._shutdown)
signal.signal(signal.SIGINT, self._shutdown)
if self.verbose >= 1:
logging.info("Started")
# Write pidfile
atexit.register(
self.delpid) # Make sure pid file is removed if we quit
pid = str(os.getpid())
file(self.pidfile, 'w+').write("%s\n" % pid)
def delpid(self):
if os.path.exists(self.pidfile):
try:
os.remove(self.pidfile)
except OSError, e:
sys.stderr.write("Could not delete pidfile: %d (%s)\n" % (e.errno, e.strerror))
def start(self, return_on_exit=False, overwrite_pid=False, *args, **kwargs):
"""
Start the daemon
"""
if self.verbose >= 1:
logging.info('Start daemon')
# Check for a pidfile to see if the daemon already runs
if os.path.exists(self.pidfile):
if overwrite_pid:
self.delpid()
else:
message = "pidfile %s already exists. Daemon already running?\n"
sys.stderr.write(message % self.pidfile)
if return_on_exit:
return False
sys.exit(1)
# Start the daemon
_exitcode = 0
self.daemonize()
try:
logging.info('Running process')
self.run(*args, **kwargs)
logging.info('Process finished normally.')
except Exception as e:
logging.error('Daemonized process threw an exception [%s].' % e)
tb = traceback.format_exc()
logging.error(tb)
_exitcode = 255
finally:
# Make sure the pid file gets deleted even in case of errors
self.delpid()
if return_on_exit:
return _exitcode if _exitcode else False
sys.exit(_exitcode)
def _shutdown(self, signum=None, frame=None):
self.shutdown(frame)
def shutdown(self, frame=None):
os.kill(self.pid, signal.SIGTERM)
def stop(self):
"""
Stop the daemon
"""
if self.verbose >= 1:
logging.info("Stopping daemon...")
# Get the pid from the pidfile
pid = self.get_pid()
if not pid:
if os.path.exists(self.pidfile):
message = "pidfile %s does not exist. Daemon not running?\n"
else:
message = "could not read pid from pidfile %s\n"
sys.stderr.write(message % self.pidfile)
# Just to be sure. A ValueError might occur if the PID file is
# empty but does actually exist
self.delpid()
return # Not an error in a restart
# Try killing the daemon process
_signal = signal.SIGTERM
try:
os.kill(pid, _signal)
_stime = time.time() + 10
while 1:
os.getpgid(pid)
time.sleep(0.1)
if time.time() > _stime:
os.kill(pid, _signal)
_signal = signal.SIGKILL
_stime += 10
except OSError, err:
err = str(err)
if err.find("No such process") > 0:
self.delpid()
else:
print str(err)
sys.exit(1)
if self.verbose >= 1:
logging.info("Stopped daemon")
def restart(self):
"""
Restart the daemon
"""
self.stop()
self.start()
def get_pid(self):
try:
pf = file(self.pidfile, 'r')
pid = int(pf.read().strip())
pf.close()
except IOError:
pid = None
except SystemExit:
pid = None
return pid
def is_running(self):
return self.running
@property
def running(self):
pid = self.get_pid()
return pid and os.path.exists('/proc/%d' % pid)
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().
"""
raise NotImplemented('You should override this method when you subclass Daemon')