diff --git a/eventlet/httpd.py b/eventlet/httpd.py index 27aedad..b00d411 100644 --- a/eventlet/httpd.py +++ b/eventlet/httpd.py @@ -492,12 +492,17 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): try: try: - self.server.site.handle_request(request) - except ErrorResponse, err: - request.response(code=err.code, - reason_phrase=err.reason, - headers=err.headers, - body=err.body) + try: + self.server.site.handle_request(request) + except ErrorResponse, err: + request.response(code=err.code, + reason_phrase=err.reason, + headers=err.headers, + body=err.body) + finally: + # clean up any timers that might have been left around by the handling code + api.get_hub().runloop.cancel_timers(api.getcurrent()) + # throw an exception if it failed to write a body if not request.response_written(): raise NotImplementedError("Handler failed to write response to request: %s" % request) @@ -507,7 +512,7 @@ class HttpProtocol(BaseHTTPServer.BaseHTTPRequestHandler): request.read_body() ## read & discard body except: pass - continue + except socket.error, e: # Broken pipe, connection reset by peer if e[0] in CONNECTION_CLOSED: diff --git a/eventlet/runloop.py b/eventlet/runloop.py index fb93549..3fde1a4 100644 --- a/eventlet/runloop.py +++ b/eventlet/runloop.py @@ -218,10 +218,11 @@ class RunLoop(object): if greenlet not in self.timers_by_greenlet: return for timer in self.timers_by_greenlet[greenlet]: - if timer.seconds: + if not timer.cancelled and timer.seconds: ## If timer.seconds is 0, this isn't a timer, it's ## actually eventlet's silly way of specifying whether ## a coroutine is "ready to run" or not. timer.cancel() + print 'Runloop cancelling left-over timer %s' % timer del self.timers_by_greenlet[greenlet] diff --git a/eventlet/timer.py b/eventlet/timer.py index f261b7d..94237ac 100644 --- a/eventlet/timer.py +++ b/eventlet/timer.py @@ -24,8 +24,12 @@ THE SOFTWARE. """ from eventlet.api import get_hub +""" If true, captures a stack trace for each timer when constructed. This is +useful for debugging leaking timers, to find out where the timer was set up. """ +_g_debug = False + class Timer(object): - __slots__ = ['seconds', 'tpl', 'called', 'cancelled', 'scheduled_time', 'greenlet'] + __slots__ = ['seconds', 'tpl', 'called', 'cancelled', 'scheduled_time', 'greenlet', 'traceback'] def __init__(self, seconds, cb, *args, **kw): """Create a timer. seconds: The minimum number of seconds to wait before calling @@ -40,12 +44,19 @@ class Timer(object): self.seconds = seconds self.tpl = cb, args, kw self.called = False + if _g_debug: + import traceback, cStringIO + self.traceback = cStringIO.StringIO() + traceback.print_stack(file=self.traceback) def __repr__(self): secs = getattr(self, 'seconds', None) cb, args, kw = getattr(self, 'tpl', (None, None, None)) - return "Timer(%s, %s, *%s, **%s)" % ( + retval = "Timer(%s, %s, *%s, **%s)" % ( secs, cb, args, kw) + if _g_debug and hasattr(self, 'traceback': + retval += '\n' + self.traceback.getvalue() + return retval def copy(self): cb, args, kw = self.tpl