asyncio: Ensure call_soon(), call_later() and call_at() are invoked on current

loop in debug mode. Raise a RuntimeError if the event loop of the current
thread is different.  The check should help to debug thread-safetly issue.
Patch written by David Foster.
This commit is contained in:
Victor Stinner 2014-03-21 10:00:52 +01:00
parent 4137465bf5
commit 93569c2b3d
2 changed files with 45 additions and 1 deletions

View file

@ -259,6 +259,8 @@ class BaseEventLoop(events.AbstractEventLoop):
"""Like call_later(), but uses an absolute time.""" """Like call_later(), but uses an absolute time."""
if tasks.iscoroutinefunction(callback): if tasks.iscoroutinefunction(callback):
raise TypeError("coroutines cannot be used with call_at()") raise TypeError("coroutines cannot be used with call_at()")
if self._debug:
self._assert_is_current_event_loop()
timer = events.TimerHandle(when, callback, args, self) timer = events.TimerHandle(when, callback, args, self)
heapq.heappush(self._scheduled, timer) heapq.heappush(self._scheduled, timer)
return timer return timer
@ -273,15 +275,34 @@ class BaseEventLoop(events.AbstractEventLoop):
Any positional arguments after the callback will be passed to Any positional arguments after the callback will be passed to
the callback when it is called. the callback when it is called.
""" """
return self._call_soon(callback, args, check_loop=True)
def _call_soon(self, callback, args, check_loop):
if tasks.iscoroutinefunction(callback): if tasks.iscoroutinefunction(callback):
raise TypeError("coroutines cannot be used with call_soon()") raise TypeError("coroutines cannot be used with call_soon()")
if self._debug and check_loop:
self._assert_is_current_event_loop()
handle = events.Handle(callback, args, self) handle = events.Handle(callback, args, self)
self._ready.append(handle) self._ready.append(handle)
return handle return handle
def _assert_is_current_event_loop(self):
"""Asserts that this event loop is the current event loop.
Non-threadsafe methods of this class make this assumption and will
likely behave incorrectly when the assumption is violated.
Should only be called when (self._debug == True). The caller is
responsible for checking this condition for performance reasons.
"""
if events.get_event_loop() is not self:
raise RuntimeError(
"non-threadsafe operation invoked on an event loop other "
"than the current one")
def call_soon_threadsafe(self, callback, *args): def call_soon_threadsafe(self, callback, *args):
"""XXX""" """XXX"""
handle = self.call_soon(callback, *args) handle = self._call_soon(callback, args, check_loop=False)
self._write_to_self() self._write_to_self()
return handle return handle

View file

@ -136,6 +136,29 @@ class BaseEventLoopTests(unittest.TestCase):
# are really slow # are really slow
self.assertLessEqual(dt, 0.9, dt) self.assertLessEqual(dt, 0.9, dt)
def test_assert_is_current_event_loop(self):
def cb():
pass
other_loop = base_events.BaseEventLoop()
other_loop._selector = unittest.mock.Mock()
asyncio.set_event_loop(other_loop)
# raise RuntimeError if the event loop is different in debug mode
self.loop.set_debug(True)
with self.assertRaises(RuntimeError):
self.loop.call_soon(cb)
with self.assertRaises(RuntimeError):
self.loop.call_later(60, cb)
with self.assertRaises(RuntimeError):
self.loop.call_at(self.loop.time() + 60, cb)
# check disabled if debug mode is disabled
self.loop.set_debug(False)
self.loop.call_soon(cb)
self.loop.call_later(60, cb)
self.loop.call_at(self.loop.time() + 60, cb)
def test_run_once_in_executor_handle(self): def test_run_once_in_executor_handle(self):
def cb(): def cb():
pass pass