From 88bf0d65af930b9a5bd650aa397d2d3adcc91952 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 21 Nov 2014 22:29:37 +0100 Subject: wrap_greenthread() now raises an exception if the greenthread is running or already finished. --- aiogreen.py | 37 ++++++++++++++-------------- doc/changelog.rst | 4 +++- doc/status.rst | 2 -- doc/using.rst | 17 +++++++------ tests/test_eventlet.py | 65 ++++++++++++++++++++++++++++++++++---------------- 5 files changed, 74 insertions(+), 51 deletions(-) diff --git a/aiogreen.py b/aiogreen.py index 988b9cc..84759f4 100644 --- a/aiogreen.py +++ b/aiogreen.py @@ -240,31 +240,35 @@ class EventLoopPolicy(asyncio.DefaultEventLoopPolicy): def wrap_greenthread(gt, loop=None): """Wrap an eventlet GreenThread or a greenlet into a Future object. - The greenlet must not be running.""" + The greenthread or greenlet must be wrapped before its execution starts. + If the greenthread or greenlet is running or already finished, an exception + is raised. + """ if loop is None: loop = asyncio.get_event_loop() fut = asyncio.Future(loop=loop) - if isinstance(gt, eventlet.greenthread.GreenThread): - if loop.get_debug() and gt: - logger.warning("wrap_greenthread() called on " - "a running greenthread") + if not isinstance(gt, greenlet.greenlet): + raise TypeError("greenthread or greenlet request, not %s" + % type(gt)) - def copy_result(gt): + if gt: + raise RuntimeError("wrap_greenthread: the greenthread is running") + if gt.dead: + raise RuntimeError("wrap_greenthread: the greenthread already finished") + + if isinstance(gt, eventlet.greenthread.GreenThread): + orig_main = gt.run + def wrap_func(*args, **kw): try: - result = gt.wait() + orig_main(*args, **kw) except Exception as exc: loop.call_soon(fut.set_exception, exc) else: + result = gt.wait() loop.call_soon(fut.set_result, result) - - gt.link(copy_result) - elif isinstance(gt, greenlet.greenlet): - if gt: - raise RuntimeError("cannot wrap a running greenlet") - if gt.dead: - raise RuntimeError("cannot wrap a greenlet which already finished") - + gt.run = wrap_func + else: orig_func = gt.run def wrap_func(*args, **kw): try: @@ -274,9 +278,6 @@ def wrap_greenthread(gt, loop=None): else: loop.call_soon(fut.set_result, result) gt.run = wrap_func - else: - raise TypeError("greenthread or greenlet request, not %s" - % type(gt)) return fut diff --git a/doc/changelog.rst b/doc/changelog.rst index 5853cce..41343a2 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -4,7 +4,9 @@ Changelog Version 0.3 (development version) --------------------------------- -* :func:`wrap_greenthread` now also works on greenlet objects. +* :func:`wrap_greenthread` now raises an exception if the greenthread is + running or already finished. In debug mode, the exception is not more logged + to sys.stderr for greenthreads. 2014-10-21: version 0.2 ----------------------- diff --git a/doc/status.rst b/doc/status.rst index 7b0802d..439af37 100644 --- a/doc/status.rst +++ b/doc/status.rst @@ -1,8 +1,6 @@ To do ===== -* wrap_greenthread() must not log the exception to sys.stderr if the - greenthread didn't start * register signals in eventlet hub, only needed for pyevent hub? * port greenio examples to aiogreen * write unit tests for, and maybe also examples for: diff --git a/doc/using.rst b/doc/using.rst index 8e62110..e647614 100644 --- a/doc/using.rst +++ b/doc/using.rst @@ -111,12 +111,13 @@ aiogreen specific functions: raise Return(x + y) def green_sum(): - task = asyncio.async(coro_slow_sum(1, 2)) - loop.call_soon(progress) + task = asyncio.async(coro_slow_sum(1, 2)) + value = aiogreen.link_future(task) print("1 + 2 = %s" % value) + loop.stop() asyncio.set_event_loop_policy(aiogreen.EventLoopPolicy()) @@ -138,11 +139,9 @@ aiogreen specific functions: The Future object waits for the completion of a greenthread. - The greenlet must not be running. - - In debug mode, if the greenthread raises an exception, the exception is - logged to ``sys.stderr`` by eventlet, even if the exception is copied to the - Future object. + The greenthread or greenlet must be wrapped before its execution starts. + If the greenthread or greenlet is running or already finished, an exception + is raised. Example of trollius coroutine waiting for a greenthread. The ``progress()`` callback is called regulary to see that the event loop in not blocked:: @@ -162,11 +161,11 @@ aiogreen specific functions: @asyncio.coroutine def coro_sum(): - gt = eventlet.spawn(slow_sum, 1, 2) - loop.call_soon(progress) + gt = eventlet.spawn(slow_sum, 1, 2) fut = aiogreen.wrap_greenthread(gt, loop=loop) + result = yield From(fut) print("1 + 2 = %s" % result) diff --git a/tests/test_eventlet.py b/tests/test_eventlet.py index 07dde9d..80b3080 100644 --- a/tests/test_eventlet.py +++ b/tests/test_eventlet.py @@ -1,5 +1,6 @@ import aiogreen import eventlet +import sys import tests from tests import unittest @@ -169,6 +170,21 @@ class EventletTests(tests.TestCase): self.loop.run_forever() self.assertEqual(result, [1, 10, 2, 20, 'error', 4]) + def test_set_debug(self): + hub = eventlet.hubs.get_hub() + self.assertIs(self.loop._hub, hub) + + self.loop.set_debug(False) + self.assertEqual(hub.debug_exceptions, False) + self.assertEqual(hub.debug_blocking, False) + + self.loop.set_debug(True) + self.assertEqual(hub.debug_exceptions, True) + if sys.platform != 'win32': + self.assertEqual(hub.debug_blocking, True) + else: + self.assertEqual(hub.debug_blocking, False) + class WrapGreenthreadTests(tests.TestCase): def test_wrap_greenthread(self): @@ -181,31 +197,29 @@ class WrapGreenthreadTests(tests.TestCase): result = self.loop.run_until_complete(fut) self.assertEqual(result, 'ok') - def test_wrap_greenthread_running(self): - event = eventlet.event.Event() + def test_wrap_greenthread_exc(self): + self.loop.set_debug(True) def func(): - return aiogreen.wrap_greenthread(gt) + raise ValueError(7) - self.loop.set_debug(False) - gt = eventlet.spawn(func) - fut1 = aiogreen.wrap_greenthread(gt) - fut2 = self.loop.run_until_complete(fut1) - fut3 = self.loop.run_until_complete(fut2) - self.assertIs(fut3, fut2) + # FIXME: the unit test must fail!? + with tests.mock.patch('traceback.print_exception') as print_exception: + gt = eventlet.spawn(func) + fut = aiogreen.wrap_greenthread(gt) + self.assertRaises(ValueError, self.loop.run_until_complete, fut) + + # the exception must not be logger by traceback: the caller must + # consume the exception from the future object + self.assertFalse(print_exception.called) - @tests.mock.patch('aiogreen.logger') - def test_wrap_greenthread_running_log(self, m_log): + def test_wrap_greenthread_running(self): def func(): return aiogreen.wrap_greenthread(gt) - self.loop.set_debug(True) + self.loop.set_debug(False) gt = eventlet.spawn(func) - fut1 = aiogreen.wrap_greenthread(gt) - fut2 = self.loop.run_until_complete(fut1) - m_log.warning.assert_called_with("wrap_greenthread() called on " - "a running greenthread") - + self.assertRaises(RuntimeError, gt.wait) def test_wrap_greenthread_dead(self): def func(): @@ -215,9 +229,7 @@ class WrapGreenthreadTests(tests.TestCase): result = gt.wait() self.assertEqual(result, 'ok') - fut = aiogreen.wrap_greenthread(gt) - result = self.loop.run_until_complete(fut) - self.assertEqual(result, 'ok') + self.assertRaises(RuntimeError, aiogreen.wrap_greenthread, gt) def test_coro_wrap_greenthread(self): result = self.loop.run_until_complete(coro_wrap_greenthread()) @@ -241,11 +253,22 @@ class WrapGreenletTests(tests.TestCase): def func(): eventlet.sleep(0.010) return "ok" + gt = eventlet.spawn_n(func) fut = aiogreen.wrap_greenthread(gt) result = self.loop.run_until_complete(fut) self.assertEqual(result, "ok") + def test_wrap_greenlet_exc(self): + self.loop.set_debug(True) + + def func(): + raise ValueError(7) + + gt = eventlet.spawn_n(func) + fut = aiogreen.wrap_greenthread(gt) + self.assertRaises(ValueError, self.loop.run_until_complete, fut) + def test_wrap_greenlet_running(self): event = eventlet.event.Event() @@ -265,9 +288,9 @@ class WrapGreenletTests(tests.TestCase): event = eventlet.event.Event() def func(): event.send('done') + gt = eventlet.spawn_n(func) event.wait() - self.assertRaises(RuntimeError, aiogreen.wrap_greenthread, gt) -- cgit v1.2.1