user277465
user277465

Reputation:

setting a counter with a basic HTTP server

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

Answers (1)

Martijn Pieters
Martijn Pieters

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

Related Questions