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 weakref
import sys
import socket
import errno
@@ -237,14 +238,20 @@ class BaseHub(object):
def track_timer(self, timer):
current_greenlet = greenlet.getcurrent()
timer.greenlet = current_greenlet
if current_greenlet not in self.timers_by_greenlet:
self.timers_by_greenlet[current_greenlet] = {}
self.timers_by_greenlet[current_greenlet][timer] = True
self.timers_by_greenlet.setdefault(
current_greenlet,
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):
del self.timers_by_greenlet[timer.greenlet][timer]
if not self.timers_by_greenlet[timer.greenlet]:
del self.timers_by_greenlet[timer.greenlet]
self.timer_finished(timer)
def prepare_timers(self):
ins = bisect.insort_right
@@ -279,21 +286,26 @@ class BaseHub(object):
except:
self.squelch_timer_exception(timer, sys.exc_info())
finally:
try:
del self.timers_by_greenlet[timer.greenlet][timer]
except KeyError:
pass
self.timer_finished(timer)
del t[:last]
def cancel_timers(self, greenlet, quiet=False):
if greenlet not in self.timers_by_greenlet:
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 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()
try:
# this might be None due to weirdness with weakrefs
timer.cancel()
except TypeError:
pass
if _g_debug and not quiet:
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()
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):
""" Cancels the underlying libevent timer. """
try:
timer.impltimer.stop()
except AttributeError:
del timer.impltimer
except (AttributeError, TypeError):
pass
finally:
super(Hub, self).timer_canceled(timer)

View File

@@ -124,11 +124,22 @@ class Hub(hub.BaseHub):
eventtimer.add()
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):
""" Cancels the underlying libevent timer. """
try:
timer.impltimer.delete()
except AttributeError:
del timer.impltimer
except (AttributeError, TypeError):
pass
finally:
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
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):
"""Create a timer.
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
calling timer.schedule() or runloop.add_timer(timer).
"""
self.impltimer = None
self.cancelled = False
self.seconds = seconds
self.tpl = cb, args, kw
@@ -57,28 +56,28 @@ class Timer(object):
secs, cb, args, kw)
if _g_debug and hasattr(self, 'traceback'):
retval += '\n' + self.traceback.getvalue()
return retval
return retval
def copy(self):
cb, args, kw = self.tpl
return self.__class__(self.seconds, cb, *args, **kw)
def schedule(self):
"""Schedule this timer to run in the current runloop.
"""
self.called = False
self.scheduled_time = get_hub().add_timer(self)
return self
def __call__(self, *args):
if not self.called:
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)
try:
cb(*args, **kw)
finally:
get_hub().timer_finished(self)
def cancel(self):
"""Prevent this timer from being called. If the timer has already
been called, has no effect.