Reputation:
Currently I have a basic HTTP server set up using BaseHTTPRequestHandler
and I use the do_GET
method of the same. Id like a function check
to be invoked if a request does not come in for 5 seconds.
I'm considering using multiprocessing along with the time module for the same, but I'm concerned about its reliability. Are there any suggestions for best practices relating to the same?
Thanks.
[EDIT]
Marjin's solution is really cool but I end up with the following traceback :-
Traceback (most recent call last):
File "test.py", line 89, in <module>
main()
File "test.py", line 83, in main
server.serve_forever()
File "/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/SocketServer.py", line 224, in serve_forever
r, w, e = select.select([self], [], [], poll_interval)
select.error: (4, 'Interrupted system call')
[EDIT 2] I tried it on Python 2.7 but the error still occurs.
[EDIT 3]
Traceback (most recent call last):
File "test.py", line 90, in <module>
main()
File "test.py", line 84, in main
server.serve_forever()
File "/usr/local/lib/python2.7/SocketServer.py", line 225, in serve_forever
r, w, e = select.select([self], [], [], poll_interval)
select.error: (4, 'Interrupted system call')
Upvotes: 0
Views: 1539
Reputation: 1121814
For a simple server such as one based on BaseHTTPRequestHandler
you could use a signal handler:
import time
import signal
import sys
last_request = sys.maxint # arbitrary high value to *not* trigger until there has been 1 requests at least
def itimer_handler(signum, frame):
print 'itimer heartbeat'
if time.time() - last_request > 300: # 5 minutes have passed at least with no request
# do stuff now to log, kill, restart, etc.
print 'Timeout, no requests for 5 minutes!'
signal.signal(signal.SIGALRM, itimer_handler)
signal.setitimer(signal.ITIMER_REAL, 30, 30) # check for a timeout every 30 seconds
# ...
def do_GET(..):
global last_request
last_request = time.time() # reset the timer again
The signal.setitimer()
call causes the OS to send a periodic SIGALRM
signal to our process. This isn't too precise; the setitimer)
call is set for 30 second intervals. Any incoming request resets a global timestamp and the itimer_handler
being called every 30 seconds compares checks if 5 minutes have passed since the last time the timestamp has been set.
The SIGALRM
signal will interrupt a running request as well, so whatever you do in that handler needs to finish quickly. When the function returns the normal python code flow resumes, just like a thread.
Note that this requires at least Python 2.7.4 for this to work; see issue 7978, and 2.7.4 is not yet released. You can either download the SocketServer.py
file that will be included in Python 2.7.4, or you could apply the following backport to add the errorno.EINTR
handling introduced in that version:
'''Backport of 2.7.4 EINTR handling'''
import errno
import select
import SocketServer
def _eintr_retry(func, *args):
"""restart a system call interrupted by EINTR"""
while True:
try:
return func(*args)
except (OSError, select.error) as e:
if e.args[0] != errno.EINTR:
raise
def serve_forever(self, poll_interval=0.5):
"""Handle one request at a time until shutdown.
Polls for shutdown every poll_interval seconds. Ignores
self.timeout. If you need to do periodic tasks, do them in
another thread.
"""
self._BaseServer__is_shut_down.clear()
try:
while not self._BaseServer__shutdown_request:
# XXX: Consider using another file descriptor or
# connecting to the socket to wake this up instead of
# polling. Polling reduces our responsiveness to a
# shutdown request and wastes cpu at all other times.
r, w, e = _eintr_retry(select.select, [self], [], [],
poll_interval)
if self in r:
self._handle_request_noblock()
finally:
self._BaseServer__shutdown_request = False
self._BaseServer__is_shut_down.set()
def handle_request(self):
"""Handle one request, possibly blocking.
Respects self.timeout.
"""
# Support people who used socket.settimeout() to escape
# handle_request before self.timeout was available.
timeout = self.socket.gettimeout()
if timeout is None:
timeout = self.timeout
elif self.timeout is not None:
timeout = min(timeout, self.timeout)
fd_sets = _eintr_retry(select.select, [self], [], [], timeout)
if not fd_sets[0]:
self.handle_timeout()
return
self._handle_request_noblock()
# patch in updated methods
SocketServer.BaseServer.serve_forever = serve_forever
SocketServer.BaseServer.handle_request = handle_request
Upvotes: 1