Combination of patches from the grugq and Augusto Becciu; try really hard not to leak timers as garbage.
This commit is contained in:
@@ -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
|
||||
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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.
|
||||
|
Reference in New Issue
Block a user