Issue #28600: Optimize loop.call_soon().

Run expensive type checks only in debug mode.  In addition, stop
supporting passing handles to loop.run_in_executor.
This commit is contained in:
Yury Selivanov 2016-11-03 15:09:24 -07:00
parent 9e80eeb22d
commit 491a912659
5 changed files with 29 additions and 57 deletions

View File

@ -528,12 +528,10 @@ class BaseEventLoop(events.AbstractEventLoop):
Absolute time corresponds to the event loop's time() method.
"""
if (coroutines.iscoroutine(callback)
or coroutines.iscoroutinefunction(callback)):
raise TypeError("coroutines cannot be used with call_at()")
self._check_closed()
if self._debug:
self._check_thread()
self._check_callback(callback, 'call_at')
timer = events.TimerHandle(when, callback, args, self)
if timer._source_traceback:
del timer._source_traceback[-1]
@ -551,18 +549,27 @@ class BaseEventLoop(events.AbstractEventLoop):
Any positional arguments after the callback will be passed to
the callback when it is called.
"""
self._check_closed()
if self._debug:
self._check_thread()
self._check_callback(callback, 'call_soon')
handle = self._call_soon(callback, args)
if handle._source_traceback:
del handle._source_traceback[-1]
return handle
def _check_callback(self, callback, method):
if (coroutines.iscoroutine(callback) or
coroutines.iscoroutinefunction(callback)):
raise TypeError(
"coroutines cannot be used with {}()".format(method))
if not callable(callback):
raise TypeError(
'a callable object was expected by {}(), got {!r}'.format(
method, callback))
def _call_soon(self, callback, args):
if (coroutines.iscoroutine(callback)
or coroutines.iscoroutinefunction(callback)):
raise TypeError("coroutines cannot be used with call_soon()")
self._check_closed()
handle = events.Handle(callback, args, self)
if handle._source_traceback:
del handle._source_traceback[-1]
@ -588,6 +595,9 @@ class BaseEventLoop(events.AbstractEventLoop):
def call_soon_threadsafe(self, callback, *args):
"""Like call_soon(), but thread-safe."""
self._check_closed()
if self._debug:
self._check_callback(callback, 'call_soon_threadsafe')
handle = self._call_soon(callback, args)
if handle._source_traceback:
del handle._source_traceback[-1]
@ -595,21 +605,9 @@ class BaseEventLoop(events.AbstractEventLoop):
return handle
def run_in_executor(self, executor, func, *args):
if (coroutines.iscoroutine(func)
or coroutines.iscoroutinefunction(func)):
raise TypeError("coroutines cannot be used with run_in_executor()")
self._check_closed()
if isinstance(func, events.Handle):
assert not args
assert not isinstance(func, events.TimerHandle)
warnings.warn(
"Passing Handle to loop.run_in_executor() is deprecated",
DeprecationWarning)
if func._cancelled:
f = self.create_future()
f.set_result(None)
return f
func, args = func._callback, func._args
if self._debug:
self._check_callback(func, 'run_in_executor')
if executor is None:
executor = self._default_executor
if executor is None:

View File

@ -82,7 +82,6 @@ class Handle:
'_source_traceback', '_repr', '__weakref__')
def __init__(self, callback, args, loop):
assert not isinstance(callback, Handle), 'A Handle is not a callback'
self._loop = loop
self._callback = callback
self._args = args

View File

@ -235,6 +235,11 @@ class BaseEventLoopTests(test_utils.TestCase):
self.assertIsInstance(h, asyncio.Handle)
self.assertIn(h, self.loop._ready)
def test_call_soon_non_callable(self):
self.loop.set_debug(True)
with self.assertRaisesRegex(TypeError, 'a callable object'):
self.loop.call_soon(1)
def test_call_later(self):
def cb():
pass
@ -341,47 +346,21 @@ class BaseEventLoopTests(test_utils.TestCase):
# check disabled if debug mode is disabled
test_thread(self.loop, False, create_loop=True)
def test_run_once_in_executor_handle(self):
def cb():
pass
self.assertRaises(
AssertionError, self.loop.run_in_executor,
None, asyncio.Handle(cb, (), self.loop), ('',))
self.assertRaises(
AssertionError, self.loop.run_in_executor,
None, asyncio.TimerHandle(10, cb, (), self.loop))
def test_run_once_in_executor_cancelled(self):
def cb():
pass
h = asyncio.Handle(cb, (), self.loop)
h.cancel()
with self.assertWarnsRegex(DeprecationWarning, "Passing Handle"):
f = self.loop.run_in_executor(None, h)
self.assertIsInstance(f, asyncio.Future)
self.assertTrue(f.done())
self.assertIsNone(f.result())
def test_run_once_in_executor_plain(self):
def cb():
pass
h = asyncio.Handle(cb, (), self.loop)
f = asyncio.Future(loop=self.loop)
executor = mock.Mock()
executor.submit.return_value = f
self.loop.set_default_executor(executor)
with self.assertWarnsRegex(DeprecationWarning, "Passing Handle"):
res = self.loop.run_in_executor(None, h)
res = self.loop.run_in_executor(None, cb)
self.assertIs(f, res)
executor = mock.Mock()
executor.submit.return_value = f
with self.assertWarnsRegex(DeprecationWarning, "Passing Handle"):
res = self.loop.run_in_executor(executor, h)
res = self.loop.run_in_executor(executor, cb)
self.assertIs(f, res)
self.assertTrue(executor.submit.called)
@ -1666,6 +1645,7 @@ class BaseEventLoopWithSelectorTests(test_utils.TestCase):
def simple_coroutine():
pass
self.loop.set_debug(True)
coro_func = simple_coroutine
coro_obj = coro_func()
self.addCleanup(coro_obj.close)

View File

@ -2249,13 +2249,6 @@ class HandleTests(test_utils.TestCase):
h.cancel()
self.assertTrue(h._cancelled)
def test_handle_from_handle(self):
def callback(*args):
return args
h1 = asyncio.Handle(callback, (), loop=self.loop)
self.assertRaises(
AssertionError, asyncio.Handle, h1, (), self.loop)
def test_callback_with_exception(self):
def callback():
raise ValueError()

View File

@ -441,6 +441,8 @@ Library
threadpool executor.
Initial patch by Hans Lawrenz.
- Issue #28600: Optimize loop.call_soon().
IDLE
----