-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMonitor.py
More file actions
executable file
·345 lines (273 loc) · 8.98 KB
/
Copy pathMonitor.py
File metadata and controls
executable file
·345 lines (273 loc) · 8.98 KB
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
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
#!/usr/bin/env python
"""Fault tolerance system for WebKit.
Contributed to Webware for Python by Jay Love.
This module is intended to provide additional assurance that the
AppServer continues running at all times. This module will be
responsible for starting the AppServer, and monitoring its health.
It does that by periodically sending a status check message to the
AppServer to ensure that it is responding. If it finds that the
AppServer does not respond within a specified time, it will start a
new copy of the AppServer, after killing the previous process.
Use::
$ python Monitor.py start
$ python Monitor.py stop
The default AppServer specified below will be used, or you can list
the AppServer you would like after ``start``.
You can have the whole process run as a daemon by specifying ``daemon``
after ``start`` on the command line.
To stop the processes, run ``Monitor.py stop``.
"""
# Future:
# Add ability to limit number of requests served. When some count is reached,
# send a message to the server to save its sessions, then exit. Then start
# a new AppServer that will pick up those sessions.
# It should be possible on both Unix and Windows to monitor the AppServer
# process in 2 ways:
# 1) The method used here, i.e. can it service requests?
# 2) is the process still running?
# Combining these with a timer lends itself to load balancing of some kind.
defaultServer = "ThreadedAppServer"
monitorInterval = 10 # add to config if this implementation is adopted
maxStartTime = 120
"""Module global:
`defaultServer`:
default ``"ThreadedAppServer"``. The type of AppServer
to start up (as listed in ``Launch.py``)
`monitorInterval`:
default 10. Seconds between checks.
`maxStartTime`:
default 120. Seconds to wait for AppServer to start
before killing it and trying again.
"""
import os, sys, time, socket, signal
from marshal import dumps
# Initialize some more global variables
serverName = defaultServer
srvpid = 0
addr = None
running = False
debug = True
statstr = dumps({'format': 'STATUS'})
statstr = dumps(len(statstr)) + statstr
quitstr = dumps({'format': 'QUIT'})
quitstr = dumps(len(quitstr)) + quitstr
## Start ##
def createServer(setupPath=0):
"""Unix only, executed after forking for daemonization."""
print "Starting Server..."
import WebKit
code = 'from WebKit.%s import main' % serverName
exec code
main(['start'])
def startupCheck():
"""Make sure the AppServer starts up correctly."""
if os.name == 'posix':
print "Waiting for start..."
time.sleep(monitorInterval / 2) # give the server a chance to start
count = 0
while 1:
if checkServer(False):
break
count += monitorInterval
if count > maxStartTime:
print "Couldn't start AppServer."
print "Killing AppServer..."
os.kill(srvpid, signal.SIGKILL)
sys.exit(1)
print "Waiting for start..."
time.sleep(monitorInterval)
def startServer(killcurrent=True):
"""Start the AppServer.
If `killcurrent` is true or not provided, kill the current AppServer.
"""
global srvpid
if os.name == 'posix':
if killcurrent:
try:
os.kill(srvpid, signal.SIGTERM)
except Exception:
pass
try:
os.waitpid(srvpid, 0)
except Exception:
pass
srvpid = os.fork()
if srvpid == 0:
createServer(not killcurrent)
sys.exit()
def checkServer(restart=True):
"""Send a check request to the AppServer.
If restart is 1, then attempt to restart the server
if we can't connect to it.
This function could also be used to see how busy an AppServer
is by measuring the delay in getting a response when using the
standard port.
"""
try:
sts = time.time()
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(addr)
s.send(statstr)
s.shutdown(1)
resp = s.recv(9) # up to 1 billion requests!
monwait = time.time() - sts
if debug:
print "Processed %s Requests." % resp
print "Delay %s." % monwait
return True
except Exception:
print "No Response from AppServer."
if running and restart:
startServer()
startupCheck()
else:
return False
def main(args):
"""The main loop.
Starts the server with `startServer(False)`,
checks it's started up (`startupCheck`), and does a
loop checking the server (`checkServer`).
"""
global running
running = True
f = open('monitor.pid', 'w')
if os.name == 'posix':
f.write(str(os.getpid()))
f.flush()
f.close()
startServer(False)
try:
startupCheck()
except Exception, e:
if debug:
print "Startup check exception:", e
print "Exiting monitor..."
try:
os.kill(srvpid, signal.SIGTERM)
except Exception:
pass
sys.exit()
while running:
try:
if debug:
print "Checking server..."
checkServer()
time.sleep(monitorInterval)
except Exception, e:
if debug:
print "Exception:", e
if not running:
return
print "Exiting Monitor..."
try:
os.kill(srvpid, signal.SIGTERM)
except Exception:
sys.exit(0)
try:
os.waitpid(srvpid, 0) # prevent zombies
except Exception:
sys.exit(0)
def shutDown(signum, frame):
"""Shutdown handler.
For when Ctrl-C has been hit, or this process is being cleanly killed.
"""
global running
print "Monitor Shutdown Called."
sys.stdout.flush()
running = False
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(addr)
s.send(quitstr)
s.shutdown(1)
resp = s.recv(10)
s.close()
print "AppServer response to shutdown request:", resp
except Exception, e:
print e
print "No Response to shutdown request, performing hard kill."
os.kill(srvpid, signal.SIGINT)
os.waitpid(srvpid, 0)
sys.stdout.flush()
sys.stderr.flush()
return False
signal.signal(signal.SIGINT, shutDown)
signal.signal(signal.SIGTERM, shutDown)
## Stop ##
def stop():
"""Stop the monitor.
This kills the other monitor process that has been opened
(from the PID file ``monitor.pid``).
"""
pid = int(open("monitor.pid", "r").read())
# this goes to the other running instance of this module
os.kill(pid, signal.SIGINT)
## Command line interface ##
def usage():
print """
This module serves as a watcher for the AppServer process.
The required command line argument is one of:
start: Starts the monitor and default appserver.
stop: Stops the currently running Monitor process and the AppServer
if is running. This is the only way to stop the process other
than hunting down the individual process ID's and killing them.
Optional arguments:
"AppServer": The AppServer class to use (currently only ThreadedAppServer)
daemon: If "daemon" is specified, the Monitor will run
as a background process.
"""
arguments = ["start", "stop"]
servernames = ["ThreadedAppServer"]
optionalargs = ["daemon"]
if __name__ == '__main__':
if os.name != 'posix':
print "This service can only be run on Posix machines (UNIX)."
sys.exit()
if len(sys.argv) == 1:
usage()
sys.exit()
args = sys.argv[1:]
if args[0] not in arguments:
usage()
sys.exit()
if True: # setup path:
if '' not in sys.path:
sys.path = [''] + sys.path
try:
import WebwarePathLocation
wwdir = os.path.abspath(os.path.join(os.path.dirname(
WebwarePathLocation.__file__), '..'))
except Exception, e:
print e
usage()
if not wwdir in sys.path:
sys.path.insert(0, wwdir)
sys.path.remove('')
try:
sys.path.remove('.')
except Exception:
pass
cfgfile = open(os.path.join(wwdir, 'WebKit',
'Configs/AppServer.config'), 'rU').read()
cfg = dict(WebwarePath=wwdir)
if cfgfile.lstrip().startswith('{'):
cfg = eval(cfgfile, cfg)
else:
exec cfgfile in cfg
if not cfg.get('EnableMonitor'):
print "Monitoring has not been enabled in AppServer.config!"
sys.exit()
host = cfg.get('Host', '127.0.0.1')
port = cfg.get('MonitorPort', 8085)
addr = (host, port)
if 'stop' in args:
stop()
sys.exit()
for i in servernames:
if i in args:
serverName = i
if 'daemon' in args: # fork and become a daemon
daemon = os.fork()
if daemon:
sys.exit()
main(args)