Combination of patches from the grugq and Augusto Becciu; try really hard not to leak timers as garbage.

This commit is contained in:
donovan
2008-07-16 10:43:05 -07:00
parent 63eb7e78f5
commit a6b7e58671
4 changed files with 58 additions and 25 deletions

View File

@@ -22,6 +22,7 @@ THE SOFTWARE.
""" """
import bisect import bisect
import weakref
import sys import sys
import socket import socket
import errno import errno
@@ -237,14 +238,20 @@ class BaseHub(object):
def track_timer(self, timer): def track_timer(self, timer):
current_greenlet = greenlet.getcurrent() current_greenlet = greenlet.getcurrent()
timer.greenlet = current_greenlet timer.greenlet = current_greenlet
if current_greenlet not in self.timers_by_greenlet: self.timers_by_greenlet.setdefault(
self.timers_by_greenlet[current_greenlet] = {} current_greenlet,
self.timers_by_greenlet[current_greenlet][timer] = True weakref.WeakKeyDictionary())[timer] = True
def timer_finished(self, timer):
try:
del self.timers_by_greenlet[timer.greenlet][timer]
if not self.timers_by_greenlet[timer.greenlet]:
del self.timers_by_greenlet[timer.greenlet]
except KeyError:
pass
def timer_canceled(self, timer): def timer_canceled(self, timer):
del self.timers_by_greenlet[timer.greenlet][timer] self.timer_finished(timer)
if not self.timers_by_greenlet[timer.greenlet]:
del self.timers_by_greenlet[timer.greenlet]
def prepare_timers(self): def prepare_timers(self):
ins = bisect.insort_right ins = bisect.insort_right
@@ -279,21 +286,26 @@ class BaseHub(object):
except: except:
self.squelch_timer_exception(timer, sys.exc_info()) self.squelch_timer_exception(timer, sys.exc_info())
finally: finally:
try: self.timer_finished(timer)
del self.timers_by_greenlet[timer.greenlet][timer]
except KeyError:
pass
del t[:last] del t[:last]
def cancel_timers(self, greenlet, quiet=False): def cancel_timers(self, greenlet, quiet=False):
if greenlet not in self.timers_by_greenlet: if greenlet not in self.timers_by_greenlet:
return return
for timer in self.timers_by_greenlet[greenlet]: for timer in self.timers_by_greenlet[greenlet].keys():
if not timer.cancelled and not timer.called and timer.seconds: if not timer.cancelled and not timer.called and timer.seconds:
## If timer.seconds is 0, this isn't a timer, it's ## If timer.seconds is 0, this isn't a timer, it's
## actually eventlet's silly way of specifying whether ## actually eventlet's silly way of specifying whether
## a coroutine is "ready to run" or not. ## a coroutine is "ready to run" or not.
timer.cancel() try:
# this might be None due to weirdness with weakrefs
timer.cancel()
except TypeError:
pass
if _g_debug and not quiet: if _g_debug and not quiet:
print 'Hub cancelling left-over timer %s' % timer print 'Hub cancelling left-over timer %s' % timer
del self.timers_by_greenlet[greenlet] try:
del self.timers_by_greenlet[greenlet]
except KeyError:
pass

View File

@@ -115,11 +115,22 @@ class Hub(hub.BaseHub):
eventtimer.start() eventtimer.start()
self.track_timer(timer) self.track_timer(timer)
def timer_finished(self, timer):
try:
timer.impltimer.stop()
del timer.impltimer
# XXX might this raise other errors?
except (AttributeError, TypeError):
pass
finally:
super(Hub, self).timer_finished(timer)
def timer_canceled(self, timer): def timer_canceled(self, timer):
""" Cancels the underlying libevent timer. """ """ Cancels the underlying libevent timer. """
try: try:
timer.impltimer.stop() timer.impltimer.stop()
except AttributeError: del timer.impltimer
except (AttributeError, TypeError):
pass pass
finally: finally:
super(Hub, self).timer_canceled(timer) super(Hub, self).timer_canceled(timer)

View File

@@ -124,11 +124,22 @@ class Hub(hub.BaseHub):
eventtimer.add() eventtimer.add()
self.track_timer(timer) self.track_timer(timer)
def timer_finished(self, timer):
try:
timer.impltimer.delete()
del timer.impltimer
# XXX might this raise other exceptions? double delete?
except (AttributeError, TypeError):
pass
finally:
super(Hub, self).timer_finished(timer)
def timer_canceled(self, timer): def timer_canceled(self, timer):
""" Cancels the underlying libevent timer. """ """ Cancels the underlying libevent timer. """
try: try:
timer.impltimer.delete() timer.impltimer.delete()
except AttributeError: del timer.impltimer
except (AttributeError, TypeError):
pass pass
finally: finally:
super(Hub, self).timer_canceled(timer) super(Hub, self).timer_canceled(timer)

View File

@@ -29,7 +29,7 @@ useful for debugging leaking timers, to find out where the timer was set up. """
_g_debug = False _g_debug = False
class Timer(object): class Timer(object):
__slots__ = ['seconds', 'tpl', 'called', 'cancelled', 'scheduled_time', 'greenlet', 'traceback', 'impltimer'] #__slots__ = ['seconds', 'tpl', 'called', 'cancelled', 'scheduled_time', 'greenlet', 'traceback', 'impltimer']
def __init__(self, seconds, cb, *args, **kw): def __init__(self, seconds, cb, *args, **kw):
"""Create a timer. """Create a timer.
seconds: The minimum number of seconds to wait before calling seconds: The minimum number of seconds to wait before calling
@@ -40,7 +40,6 @@ class Timer(object):
This timer will not be run unless it is scheduled in a runloop by This timer will not be run unless it is scheduled in a runloop by
calling timer.schedule() or runloop.add_timer(timer). calling timer.schedule() or runloop.add_timer(timer).
""" """
self.impltimer = None
self.cancelled = False self.cancelled = False
self.seconds = seconds self.seconds = seconds
self.tpl = cb, args, kw self.tpl = cb, args, kw
@@ -57,28 +56,28 @@ class Timer(object):
secs, cb, args, kw) secs, cb, args, kw)
if _g_debug and hasattr(self, 'traceback'): if _g_debug and hasattr(self, 'traceback'):
retval += '\n' + self.traceback.getvalue() retval += '\n' + self.traceback.getvalue()
return retval return retval
def copy(self): def copy(self):
cb, args, kw = self.tpl cb, args, kw = self.tpl
return self.__class__(self.seconds, cb, *args, **kw) return self.__class__(self.seconds, cb, *args, **kw)
def schedule(self): def schedule(self):
"""Schedule this timer to run in the current runloop. """Schedule this timer to run in the current runloop.
""" """
self.called = False self.called = False
self.scheduled_time = get_hub().add_timer(self) self.scheduled_time = get_hub().add_timer(self)
return self return self
def __call__(self, *args): def __call__(self, *args):
if not self.called: if not self.called:
self.called = True self.called = True
if self.impltimer is not None:
del get_hub().timers_by_greenlet[self.greenlet][self]
cb, args, kw = self.tpl cb, args, kw = self.tpl
cb(*args, **kw) try:
cb(*args, **kw)
finally:
get_hub().timer_finished(self)
def cancel(self): def cancel(self):
"""Prevent this timer from being called. If the timer has already """Prevent this timer from being called. If the timer has already
been called, has no effect. been called, has no effect.