diff --git a/eventlet/__init__.py b/eventlet/__init__.py index 211ab2f..f56a53e 100644 --- a/eventlet/__init__.py +++ b/eventlet/__init__.py @@ -10,6 +10,7 @@ try: spawn = greenthread.spawn spawn_n = greenthread.spawn_n + spawn_after = greenthread.spawn_after call_after_global = greenthread.call_after_global TimeoutError = greenthread.TimeoutError exc_after = greenthread.exc_after diff --git a/eventlet/greenthread.py b/eventlet/greenthread.py index bfaa555..043f436 100644 --- a/eventlet/greenthread.py +++ b/eventlet/greenthread.py @@ -4,6 +4,7 @@ from eventlet import event from eventlet import hubs from eventlet.hubs import timer from eventlet.support import greenlets as greenlet +import warnings __all__ = ['getcurrent', 'sleep', 'spawn', 'spawn_n', 'call_after_global', 'call_after_local', 'GreenThread'] @@ -81,7 +82,30 @@ def spawn_after(seconds, func, *args, **kwargs): g = GreenThread(hub.greenlet) hub.schedule_call_global(seconds, g.switch, func, args, kwargs) return g + + +def spawn_after_local(seconds, func, *args, **kwargs): + """Spawns *func* after *seconds* have elapsed. The function will NOT be + called if the current greenthread has exited. + *seconds* may be specified as an integer, or a float if fractional seconds + are desired. The *func* will be called with the given *args* and + keyword arguments *kwargs*, and will be executed within its own greenthread. + + The return value of :func:`spawn_after` is a :class:`GreenThread` object, + which can be used to retrieve the results of the call. + + To cancel the spawn and prevent *func* from being called, + call :meth:`GreenThread.cancel` on the return value. This will not abort the + function if it's already started running. If terminating *func* regardless + of whether it's started or not is the desired behavior, call + :meth:`GreenThread.kill`. + """ + hub = hubs.get_hub() + g = GreenThread(hub.greenlet) + hub.schedule_call_local(seconds, g.switch, func, args, kwargs) + return g + def call_after_global(seconds, func, *args, **kwargs): """Schedule *function* to be called after *seconds* have elapsed. @@ -92,6 +116,10 @@ def call_after_global(seconds, func, *args, **kwargs): keyword arguments *kwargs*, and will be executed within its own greenthread. Its return value is discarded.""" + warnings.warn("call_after_global is renamed to spawn_after, which" + "has the same signature and semantics (plus a bit extra). Please do a" + " quick search-and-replace on your codebase, thanks!", + DeprecationWarning, stacklevel=2) return _spawn_n(seconds, func, args, kwargs)[0] @@ -105,6 +133,9 @@ def call_after_local(seconds, function, *args, **kwargs): Its return value is discarded. """ + warnings.warn("call_after_local is renamed to spawn_after_local, which" + "has the same signature and semantics (plus a bit extra).", + DeprecationWarning, stacklevel=2) hub = hubs.get_hub() g = greenlet.greenlet(_main_wrapper, parent=hub.greenlet) t = hub.schedule_call_local(seconds, g.switch, function, args, kwargs) diff --git a/tests/greenthread_test.py b/tests/greenthread_test.py index 75aefc6..e2d1047 100644 --- a/tests/greenthread_test.py +++ b/tests/greenthread_test.py @@ -107,3 +107,35 @@ class SpawnAfter(LimitedTestCase, Asserts): greenthread.sleep(0) gt.kill() self.assert_dead(gt) + +class SpawnAfterLocal(LimitedTestCase, Asserts): + def setUp(self): + super(SpawnAfterLocal, self).setUp() + self.lst = [1] + + def test_timer_fired(self): + def func(): + greenthread.spawn_after_local(0.1, self.lst.pop) + greenthread.sleep(0.2) + + greenthread.spawn(func) + assert self.lst == [1], self.lst + greenthread.sleep(0.3) + assert self.lst == [], self.lst + + def test_timer_cancelled_upon_greenlet_exit(self): + def func(): + greenthread.spawn_after_local(0.1, self.lst.pop) + + greenthread.spawn(func) + assert self.lst == [1], self.lst + greenthread.sleep(0.2) + assert self.lst == [1], self.lst + + def test_spawn_is_not_cancelled(self): + def func(): + greenthread.spawn(self.lst.pop) + # exiting immediatelly, but self.lst.pop must be called + greenthread.spawn(func) + greenthread.sleep(0.1) + assert self.lst == [], self.lst diff --git a/tests/test__timers.py b/tests/test__timers.py deleted file mode 100644 index 60826b0..0000000 --- a/tests/test__timers.py +++ /dev/null @@ -1,42 +0,0 @@ -import unittest -from eventlet.api import call_after, spawn, sleep - -class test(unittest.TestCase): - - def setUp(self): - self.lst = [1] - - def test_timer_fired(self): - - def func(): - call_after(0.1, self.lst.pop) - sleep(0.2) - - spawn(func) - assert self.lst == [1], self.lst - sleep(0.3) - assert self.lst == [], self.lst - - def test_timer_cancelled_upon_greenlet_exit(self): - - def func(): - call_after(0.1, self.lst.pop) - - spawn(func) - assert self.lst == [1], self.lst - sleep(0.2) - assert self.lst == [1], self.lst - - def test_spawn_is_not_cancelled(self): - - def func(): - spawn(self.lst.pop) - # exiting immediatelly, but self.lst.pop must be called - spawn(func) - sleep(0.1) - assert self.lst == [], self.lst - - -if __name__=='__main__': - unittest.main() - diff --git a/tests/timer_test.py b/tests/timer_test.py index a59767f..4cbd646 100644 --- a/tests/timer_test.py +++ b/tests/timer_test.py @@ -31,6 +31,7 @@ class TestTimer(TestCase): hub.switch() assert called assert not hub.running + if __name__ == '__main__': main()