diff --git a/eventlet/hubs/hub.py b/eventlet/hubs/hub.py index 9ae5165..ca52c48 100644 --- a/eventlet/hubs/hub.py +++ b/eventlet/hubs/hub.py @@ -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 + diff --git a/eventlet/hubs/libev.py b/eventlet/hubs/libev.py index 6e5d421..9899336 100644 --- a/eventlet/hubs/libev.py +++ b/eventlet/hubs/libev.py @@ -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) diff --git a/eventlet/hubs/libevent.py b/eventlet/hubs/libevent.py index 7492158..bb5ab6a 100644 --- a/eventlet/hubs/libevent.py +++ b/eventlet/hubs/libevent.py @@ -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) diff --git a/eventlet/timer.py b/eventlet/timer.py index 1018320..abeb868 100644 --- a/eventlet/timer.py +++ b/eventlet/timer.py @@ -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.