diff --git a/eventlet/greenthread.py b/eventlet/greenthread.py index b7e0a81..5fce489 100644 --- a/eventlet/greenthread.py +++ b/eventlet/greenthread.py @@ -23,8 +23,9 @@ def sleep(seconds=0): occasionally; otherwise nothing else will run. """ hub = hubs.get_hub() - assert hub.greenlet is not greenlet.getcurrent(), 'do not call blocking functions from the mainloop' - timer = hub.schedule_call_global(seconds, greenlet.getcurrent().switch) + current = greenlet.getcurrent() + assert hub.greenlet is not current, 'do not call blocking functions from the mainloop' + timer = hub.schedule_call_global(seconds, current.switch) try: hub.switch() finally: diff --git a/eventlet/hubs/hub.py b/eventlet/hubs/hub.py index fe7c18c..2dcc2b1 100644 --- a/eventlet/hubs/hub.py +++ b/eventlet/hubs/hub.py @@ -63,7 +63,7 @@ class BaseHub(object): self.next_timers = [] self.lclass = FdListener self.debug_exceptions = True - self.timers_cancelled = 0 + self.timers_canceled = 0 def add(self, evtype, fileno, cb): """ Signals an intent to or write a particular file descriptor. @@ -183,18 +183,30 @@ class BaseHub(object): else: self.wait(0) else: + self.canceled_timers = 0 del self.timers[:] del self.next_timers[:] finally: self.running = False self.stopping = False - def abort(self): - """Stop the runloop. If run is executing, it will exit after completing - the next runloop iteration. + def abort(self, wait=False): + """Stop the runloop. If run is executing, it will exit after + completing the next runloop iteration. + + Set *wait* to True to cause abort to switch to the hub immediately and + wait until it's finished processing. Waiting for the hub will only + work from the main greenthread; all other greenthreads will become + unreachable. """ if self.running: self.stopping = True + if wait: + # schedule an immediate timer just so the hub doesn't sleep + self.schedule_call_global(0, lambda: None) + # switch to it; when done the hub will switch back to its parent, + # the main greenlet + self.switch() def squelch_generic_exception(self, exc_info): if self.debug_exceptions: @@ -215,10 +227,10 @@ class BaseHub(object): pass def timer_canceled(self, timer): - self.timers_cancelled += 1 + self.timers_canceled += 1 len_timers = len(self.timers) - if len_timers > 1000 and len_timers/2 <= self.timers_cancelled: - self.timers_cancelled = 0 + if len_timers > 1000 and len_timers/2 <= self.timers_canceled: + self.timers_canceled = 0 self.timers = [t for t in self.timers if not t[1].called] heapq.heapify(self.timers) self.timer_finished(timer) @@ -227,7 +239,10 @@ class BaseHub(object): heappush = heapq.heappush t = self.timers for item in self.next_timers: - heappush(t, item) + if item[1].called: + self.timers_canceled -= 1 + else: + heappush(t, item) del self.next_timers[:] def schedule_call_local(self, seconds, cb, *args, **kw): @@ -244,7 +259,7 @@ class BaseHub(object): def schedule_call_global(self, seconds, cb, *args, **kw): """Schedule a callable to be called after 'seconds' seconds have - elapsed. The timer will NOT be cancelled if the current greenlet has + elapsed. The timer will NOT be canceled if the current greenlet has exited before the timer fires. seconds: The number of seconds to wait. cb: The callable to call after the given time. @@ -273,7 +288,7 @@ class BaseHub(object): try: try: if timer.called: - self.timers_cancelled -= 1 + self.timers_canceled -= 1 else: timer() except self.SYSTEM_EXCEPTIONS: diff --git a/eventlet/hubs/timer.py b/eventlet/hubs/timer.py index 519d7c4..827d08e 100644 --- a/eventlet/hubs/timer.py +++ b/eventlet/hubs/timer.py @@ -6,7 +6,6 @@ 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'] def __init__(self, seconds, cb, *args, **kw): """Create a timer. seconds: The minimum number of seconds to wait before calling @@ -64,7 +63,7 @@ class Timer(object): def cancel(self): """Prevent this timer from being called. If the timer has already - been called or cancelled, has no effect. + been called or canceled, has no effect. """ if not self.called: self.called = True diff --git a/eventlet/timeout.py b/eventlet/timeout.py index e50c868..135545b 100644 --- a/eventlet/timeout.py +++ b/eventlet/timeout.py @@ -52,7 +52,7 @@ class Timeout(BaseException): def start(self): """Schedule the timeout. This is called on construction, so it should not be called explicitly, unless the timer has been - cancelled.""" + canceled.""" assert not self.pending, \ '%r is already started; to restart it, cancel it first' % self if self.seconds is None: # "fake" timeout (never expires) @@ -77,7 +77,7 @@ class Timeout(BaseException): """If the timeout is pending, cancel it. If not using Timeouts in ``with`` statements, always call cancel() in a ``finally`` after the block of code that is getting timed out. - If not cancelled, the timeout will be raised later on, in some + If not canceled, the timeout will be raised later on, in some unexpected section of the application.""" if self.timer is not None: self.timer.cancel() diff --git a/tests/__init__.py b/tests/__init__.py index 72cf7e7..6349a96 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -129,6 +129,21 @@ class LimitedTestCase(unittest.TestCase): print debug.format_hub_timers() print debug.format_hub_listeners() + def assert_less_than(self, a,b,msg=None): + if msg: + self.assert_(a