From bcbd9d96c29f43e9c8637b81ba951dcc68d6693f Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Sun, 23 Nov 2014 10:53:37 +0100 Subject: Fix to run an event loop in a thread different than the main thread in debug mode: disable eventlet "debug_blocking", it is implemented with the SIGALRM signal, but signal handlers can only be set in the main thread. Add a test: run an event loop in a thread different than the main thread. --- aiogreen.py | 11 +++++++++-- doc/changelog.rst | 3 +++ doc/status.rst | 2 +- doc/using.rst | 16 ++++++++++++++++ tests/test_thread.py | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 76 insertions(+), 3 deletions(-) diff --git a/aiogreen.py b/aiogreen.py index 79d005f..dead9a4 100644 --- a/aiogreen.py +++ b/aiogreen.py @@ -229,8 +229,15 @@ class EventLoop(asyncio.SelectorEventLoop): self._hub.debug_exceptions = debug # Detect blocking eventlet functions. The feature is implemented with - # signal.alarm() which is is not available on Windows. - self._hub.debug_blocking = debug and (sys.platform != 'win32') + # signal.alarm() which is is not available on Windows. Signal handlers + # can only be set from the main loop. So detecting blocking functions + # cannot be used on Windows nor from a thread different than the main + # thread. + self._hub.debug_blocking = ( + debug + and (sys.platform != 'win32') + and isinstance(threading.current_thread(), threading._MainThread)) + if (self._hub.debug_blocking and hasattr(self, 'slow_callback_duration')): self._hub.debug_blocking_resolution = self.slow_callback_duration diff --git a/doc/changelog.rst b/doc/changelog.rst index 7c98919..f932f61 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -12,6 +12,9 @@ Version 0.3 (development version) greenthread of the aiogreen event loop. * Fix eventlet detection of blocking tasks: cancel the alarm when the aiogreen event loop stops. +* Fix to run an event loop in a thread different than the main thread in debug + mode: disable eventlet "debug_blocking", it is implemented with the SIGALRM + signal, but signal handlers can only be set in the main thread. 2014-10-21: version 0.2 ----------------------- diff --git a/doc/status.rst b/doc/status.rst index 545e974..c1fc995 100644 --- a/doc/status.rst +++ b/doc/status.rst @@ -12,7 +12,7 @@ To do - signals - subprocesses -* run an event loop in a thread different than the main thread +* experiment running an event loop in a thread different than the main thread * tox.ini: test Python 3.3 with monkey-patching, see eventlet bug: https://github.com/eventlet/eventlet/pull/168 diff --git a/doc/using.rst b/doc/using.rst index 0956085..d36a34a 100644 --- a/doc/using.rst +++ b/doc/using.rst @@ -79,6 +79,22 @@ Hello World:: `_. +Threads +------- + +Running an event loop in a thread different than the main thread is currently +experimental. + +An eventlet Event object is not thread-safe, it must only be used in the +same thread. Use threading.Event to signal events between threads, +and threading.Queue to pass data between threads. + +Use ``threading = eventlet.patcher.original('threading')`` to get the original +threading instead of ``import threading``. + +It is not possible to run two aiogreen event loops in the same thread. + + Debug mode ---------- diff --git a/tests/test_thread.py b/tests/test_thread.py index 2c9caae..c41eeaa 100644 --- a/tests/test_thread.py +++ b/tests/test_thread.py @@ -55,6 +55,53 @@ class ThreadTests(tests.TestCase): self.loop.run_until_complete(fut) self.assertIsInstance(result['loop'], AssertionError) + def test_run_in_thread(self): + class LoopThread(threading.Thread): + def __init__(self, event): + super(LoopThread, self).__init__() + self.loop = None + self.event = event + + def run(self): + self.loop = asyncio.new_event_loop() + try: + self.loop.set_debug(True) + asyncio.set_event_loop(self.loop) + + self.event.set() + self.loop.run_forever() + finally: + self.loop.close() + asyncio.set_event_loop(None) + + result = [] + + # start an event loop in a thread + event = threading.Event() + thread = LoopThread(event) + thread.start() + event.wait() + loop = thread.loop + + def func(loop): + result.append(threading.current_thread().ident) + loop.stop() + + # FIXME: call_soon() must raise an exception if if the main thread + # has no event loop, bugs.python.org/issue22926 + #self.loop.close() + #asyncio.set_event_loop(None) + # call_soon() must fail when called from the wrong thread + self.assertRaises(RuntimeError, loop.call_soon, func, loop) + + # call func() in a different thread using the event loop + tid = thread.ident + loop.call_soon_threadsafe(func, loop) + + # stop the event loop + thread.join() + self.assertEqual(result, [tid]) + if __name__ == '__main__': import unittest -- cgit v1.2.1