-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgunicorn_hup_on_change.py
executable file
·195 lines (157 loc) · 5.9 KB
/
gunicorn_hup_on_change.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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Send gunicorn a HUP when .py files are changed
"""
import os
import os.path
import sys
import re
import signal
import logging
from threading import Timer
from optparse import OptionParser
try:
import pyinotify as pyi
except ImportError:
print >>sys.stderr, """Pyinotify package not found.
You can try apt-get install python-pyinotify
or maybe pip install pyinotify
"""
raise
class GenericEventHandler(pyi.ProcessEvent):
"""Handles events on specific dirs, then call call the callback method
@ivar manager: The manager to use for watching new directories
@ivar wmask: pyinotify mask to use for watching new directories
@type wmask: int
@ivar wpatterns: patterns that trigger action
@type wpatterns: list of regexp
"""
manager = None
wmask = 0
wpatterns = [ re.compile( '^[^.].*\.py$' ) ]
def __init__(self, watchdirs, manager=None, callback=None):
if not manager:
manager = pyi.WatchManager()
if callback and callable(callback):
self.callback = callback
self.manager = manager
self.wmask = pyi.IN_CREATE | pyi.IN_MODIFY | pyi.IN_MOVED_TO
for dirname in watchdirs:
logger.debug("watch: %s" % dirname)
self.manager.add_watch(dirname, self.wmask, rec=True)
def loop(self):
"""Main loop - B{blocks}
"""
notifier = pyi.Notifier(self.manager, self)
notifier.loop()
def callback(self, event):
"""Default callback does nothing
"""
raise NotImplementedError("Override this")
def changed(self, event):
"""Something changed, trigger callback if matching pattern
or add dir to watchlist
"""
# Add new directories to watch
if event.dir and event.mask & pyi.IN_CREATE:
logger.debug("watch: %s" % event.pathname)
self.manager.add_watch(event.pathname, self.wmask)
return
# Return if none of our pattern matches
for r in self.wpatterns:
if r.match(event.name):
break
else:
# else clause called if no break was reached
return
logger.debug("change: %s %s" % ( event.pathname, event.mask ))
self.callback(event)
process_IN_CREATE = changed
process_IN_MODIFY = changed
process_IN_MOVED_TO = changed
class GunicornHUP(GenericEventHandler):
appname = None
pidfile = None
known_pid = None
wait_time = 500
timer = None
def callback(self, event):
if self.known_pid is not None:
try:
os.kill(self.known_pid, 0)
except OSError, e:
if e.errno != 3:
raise
self.known_pid = None
if self.known_pid is None:
if self.pidfile:
self.known_pid = int(open(self.pidfile).read())
logger.info("found master process %d (%s)" % (
self.known_pid, self.pidfile) )
return
#gunicorn: master [website]
mre = re.compile(r'^gunicorn:\s+master\s+\[(.*)\]')
pids = [ int(pid) for pid in os.listdir('/proc') if pid.isdigit() ]
for pid in pids:
path = os.path.join('/proc', str(pid), 'cmdline')
try:
# process might be gone between ls and open
data = open(path).read()
except:
continue
found = mre.search(data)
if not found:
continue
appname = found.group(1)
if self.appname is None or appname == self.appname:
self.known_pid = pid
logger.info("found master process %d (%s)" % (
self.known_pid, appname) )
break
else:
msg = "Could not find gunicorn master process"
if self.appname:
msg += " for %s" % self.appname
logger.error(msg)
return
def kill(pid):
logger.info("HUP: %d" % pid)
os.kill(pid, signal.SIGHUP)
if self.timer:
self.timer.cancel()
self.timer = Timer(self.wait_time / 1000.0, kill, [ self.known_pid ])
self.timer.start()
if __name__ == '__main__':
usage = "usage: %prog [-q|-v] [-w wait] [-p pidfile] [-a app_module] [watch_dirs]"
parser = OptionParser(usage)
parser.add_option("-a", dest="appmodule", help="application module")
parser.add_option("-p", dest="pidfile", help="pidfile containing master pid")
parser.add_option("-w", dest="wait", type="int", default=500, help="wait"
" interval milliseconds before sending HUP [default: %default]")
parser.add_option("-v", dest="verbose", action="store_true", default=False,
help="be more verbose")
parser.add_option("-q", dest="quiet", action="store_true", default=False,
help="quiet")
(options, args) = parser.parse_args()
watchdirs = args or filter(os.path.isdir, sys.path)
# Setup logging "alamano" since pyinotify already sets up some of its soup
if options.quiet and options.verbose:
parser.error("quiet and verbose options are both set")
elif options.quiet:
loglevel = "ERROR"
elif options.verbose:
loglevel = "DEBUG"
else:
loglevel = "INFO"
logging.setLoggerClass(logging.Logger)
logger = logging.getLogger("zulu")
loghandler = logging.StreamHandler()
loghandler.setFormatter(logging.Formatter('%(asctime)s - %(message)s'))
logger.addHandler(loghandler)
logger.setLevel(logging.getLevelName(loglevel))
handler = GunicornHUP(watchdirs)
handler.appname = options.appmodule
handler.pidfile = options.pidfile
handler.wait_time = max(20, options.wait)
parser.destroy()
handler.loop()