mirror of
https://github.com/python/cpython.git
synced 2025-09-27 02:39:58 +00:00
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:
parent
4137465bf5
commit
93569c2b3d
2 changed files with 45 additions and 1 deletions
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue