mirror of
https://github.com/python/cpython.git
synced 2025-10-02 21:25:24 +00:00
gh-97696: asyncio eager tasks factory (#102853)
Co-authored-by: Jacob Bower <jbower@meta.com> Co-authored-by: Carol Willing <carolcode@willingconsulting.com>
This commit is contained in:
parent
59bc36aacd
commit
a474e04388
12 changed files with 945 additions and 47 deletions
|
@ -527,6 +527,42 @@ Running Tasks Concurrently
|
||||||
and there is no running event loop.
|
and there is no running event loop.
|
||||||
|
|
||||||
|
|
||||||
|
Eager Task Factory
|
||||||
|
==================
|
||||||
|
|
||||||
|
.. function:: eager_task_factory(loop, coro, *, name=None, context=None)
|
||||||
|
|
||||||
|
A task factory for eager task execution.
|
||||||
|
|
||||||
|
When using this factory (via :meth:`loop.set_task_factory(asyncio.eager_task_factory) <loop.set_task_factory>`),
|
||||||
|
coroutines begin execution synchronously during :class:`Task` construction.
|
||||||
|
Tasks are only scheduled on the event loop if they block.
|
||||||
|
This can be a performance improvement as the overhead of loop scheduling
|
||||||
|
is avoided for coroutines that complete synchronously.
|
||||||
|
|
||||||
|
A common example where this is beneficial is coroutines which employ
|
||||||
|
caching or memoization to avoid actual I/O when possible.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Immediate execution of the coroutine is a semantic change.
|
||||||
|
If the coroutine returns or raises, the task is never scheduled
|
||||||
|
to the event loop. If the coroutine execution blocks, the task is
|
||||||
|
scheduled to the event loop. This change may introduce behavior
|
||||||
|
changes to existing applications. For example,
|
||||||
|
the application's task execution order is likely to change.
|
||||||
|
|
||||||
|
.. versionadded:: 3.12
|
||||||
|
|
||||||
|
.. function:: create_eager_task_factory(custom_task_constructor)
|
||||||
|
|
||||||
|
Create an eager task factory, similar to :func:`eager_task_factory`,
|
||||||
|
using the provided *custom_task_constructor* when creating a new task instead
|
||||||
|
of the default :class:`Task`.
|
||||||
|
|
||||||
|
.. versionadded:: 3.12
|
||||||
|
|
||||||
|
|
||||||
Shielding From Cancellation
|
Shielding From Cancellation
|
||||||
===========================
|
===========================
|
||||||
|
|
||||||
|
|
|
@ -613,6 +613,11 @@ Optimizations
|
||||||
* Speed up :class:`asyncio.Task` creation by deferring expensive string formatting.
|
* Speed up :class:`asyncio.Task` creation by deferring expensive string formatting.
|
||||||
(Contributed by Itamar O in :gh:`103793`.)
|
(Contributed by Itamar O in :gh:`103793`.)
|
||||||
|
|
||||||
|
* Added :func:`asyncio.eager_task_factory` and :func:`asyncio.create_eager_task_factory`
|
||||||
|
functions to allow opting an event loop in to eager task execution,
|
||||||
|
speeding up some use-cases by up to 50%.
|
||||||
|
(Contributed by Jacob Bower & Itamar O in :gh:`102853`)
|
||||||
|
|
||||||
|
|
||||||
CPython bytecode changes
|
CPython bytecode changes
|
||||||
========================
|
========================
|
||||||
|
|
|
@ -882,6 +882,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
|
||||||
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(dst_dir_fd));
|
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(dst_dir_fd));
|
||||||
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(duration));
|
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(duration));
|
||||||
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(e));
|
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(e));
|
||||||
|
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(eager_start));
|
||||||
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(effective_ids));
|
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(effective_ids));
|
||||||
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(element_factory));
|
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(element_factory));
|
||||||
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(encode));
|
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(encode));
|
||||||
|
@ -972,6 +973,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
|
||||||
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(instructions));
|
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(instructions));
|
||||||
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(intern));
|
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(intern));
|
||||||
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(intersection));
|
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(intersection));
|
||||||
|
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(is_running));
|
||||||
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(isatty));
|
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(isatty));
|
||||||
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(isinstance));
|
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(isinstance));
|
||||||
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(isoformat));
|
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(isoformat));
|
||||||
|
|
|
@ -370,6 +370,7 @@ struct _Py_global_strings {
|
||||||
STRUCT_FOR_ID(dst_dir_fd)
|
STRUCT_FOR_ID(dst_dir_fd)
|
||||||
STRUCT_FOR_ID(duration)
|
STRUCT_FOR_ID(duration)
|
||||||
STRUCT_FOR_ID(e)
|
STRUCT_FOR_ID(e)
|
||||||
|
STRUCT_FOR_ID(eager_start)
|
||||||
STRUCT_FOR_ID(effective_ids)
|
STRUCT_FOR_ID(effective_ids)
|
||||||
STRUCT_FOR_ID(element_factory)
|
STRUCT_FOR_ID(element_factory)
|
||||||
STRUCT_FOR_ID(encode)
|
STRUCT_FOR_ID(encode)
|
||||||
|
@ -460,6 +461,7 @@ struct _Py_global_strings {
|
||||||
STRUCT_FOR_ID(instructions)
|
STRUCT_FOR_ID(instructions)
|
||||||
STRUCT_FOR_ID(intern)
|
STRUCT_FOR_ID(intern)
|
||||||
STRUCT_FOR_ID(intersection)
|
STRUCT_FOR_ID(intersection)
|
||||||
|
STRUCT_FOR_ID(is_running)
|
||||||
STRUCT_FOR_ID(isatty)
|
STRUCT_FOR_ID(isatty)
|
||||||
STRUCT_FOR_ID(isinstance)
|
STRUCT_FOR_ID(isinstance)
|
||||||
STRUCT_FOR_ID(isoformat)
|
STRUCT_FOR_ID(isoformat)
|
||||||
|
|
2
Include/internal/pycore_runtime_init_generated.h
generated
2
Include/internal/pycore_runtime_init_generated.h
generated
|
@ -876,6 +876,7 @@ extern "C" {
|
||||||
INIT_ID(dst_dir_fd), \
|
INIT_ID(dst_dir_fd), \
|
||||||
INIT_ID(duration), \
|
INIT_ID(duration), \
|
||||||
INIT_ID(e), \
|
INIT_ID(e), \
|
||||||
|
INIT_ID(eager_start), \
|
||||||
INIT_ID(effective_ids), \
|
INIT_ID(effective_ids), \
|
||||||
INIT_ID(element_factory), \
|
INIT_ID(element_factory), \
|
||||||
INIT_ID(encode), \
|
INIT_ID(encode), \
|
||||||
|
@ -966,6 +967,7 @@ extern "C" {
|
||||||
INIT_ID(instructions), \
|
INIT_ID(instructions), \
|
||||||
INIT_ID(intern), \
|
INIT_ID(intern), \
|
||||||
INIT_ID(intersection), \
|
INIT_ID(intersection), \
|
||||||
|
INIT_ID(is_running), \
|
||||||
INIT_ID(isatty), \
|
INIT_ID(isatty), \
|
||||||
INIT_ID(isinstance), \
|
INIT_ID(isinstance), \
|
||||||
INIT_ID(isoformat), \
|
INIT_ID(isoformat), \
|
||||||
|
|
|
@ -963,6 +963,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
|
||||||
string = &_Py_ID(e);
|
string = &_Py_ID(e);
|
||||||
assert(_PyUnicode_CheckConsistency(string, 1));
|
assert(_PyUnicode_CheckConsistency(string, 1));
|
||||||
_PyUnicode_InternInPlace(interp, &string);
|
_PyUnicode_InternInPlace(interp, &string);
|
||||||
|
string = &_Py_ID(eager_start);
|
||||||
|
assert(_PyUnicode_CheckConsistency(string, 1));
|
||||||
|
_PyUnicode_InternInPlace(interp, &string);
|
||||||
string = &_Py_ID(effective_ids);
|
string = &_Py_ID(effective_ids);
|
||||||
assert(_PyUnicode_CheckConsistency(string, 1));
|
assert(_PyUnicode_CheckConsistency(string, 1));
|
||||||
_PyUnicode_InternInPlace(interp, &string);
|
_PyUnicode_InternInPlace(interp, &string);
|
||||||
|
@ -1233,6 +1236,9 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
|
||||||
string = &_Py_ID(intersection);
|
string = &_Py_ID(intersection);
|
||||||
assert(_PyUnicode_CheckConsistency(string, 1));
|
assert(_PyUnicode_CheckConsistency(string, 1));
|
||||||
_PyUnicode_InternInPlace(interp, &string);
|
_PyUnicode_InternInPlace(interp, &string);
|
||||||
|
string = &_Py_ID(is_running);
|
||||||
|
assert(_PyUnicode_CheckConsistency(string, 1));
|
||||||
|
_PyUnicode_InternInPlace(interp, &string);
|
||||||
string = &_Py_ID(isatty);
|
string = &_Py_ID(isatty);
|
||||||
assert(_PyUnicode_CheckConsistency(string, 1));
|
assert(_PyUnicode_CheckConsistency(string, 1));
|
||||||
_PyUnicode_InternInPlace(interp, &string);
|
_PyUnicode_InternInPlace(interp, &string);
|
||||||
|
|
|
@ -15,11 +15,13 @@ def _task_repr_info(task):
|
||||||
|
|
||||||
info.insert(1, 'name=%r' % task.get_name())
|
info.insert(1, 'name=%r' % task.get_name())
|
||||||
|
|
||||||
|
if task._fut_waiter is not None:
|
||||||
|
info.insert(2, f'wait_for={task._fut_waiter!r}')
|
||||||
|
|
||||||
|
if task._coro:
|
||||||
coro = coroutines._format_coroutine(task._coro)
|
coro = coroutines._format_coroutine(task._coro)
|
||||||
info.insert(2, f'coro=<{coro}>')
|
info.insert(2, f'coro=<{coro}>')
|
||||||
|
|
||||||
if task._fut_waiter is not None:
|
|
||||||
info.insert(3, f'wait_for={task._fut_waiter!r}')
|
|
||||||
return info
|
return info
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ __all__ = (
|
||||||
'wait', 'wait_for', 'as_completed', 'sleep',
|
'wait', 'wait_for', 'as_completed', 'sleep',
|
||||||
'gather', 'shield', 'ensure_future', 'run_coroutine_threadsafe',
|
'gather', 'shield', 'ensure_future', 'run_coroutine_threadsafe',
|
||||||
'current_task', 'all_tasks',
|
'current_task', 'all_tasks',
|
||||||
|
'create_eager_task_factory', 'eager_task_factory',
|
||||||
'_register_task', '_unregister_task', '_enter_task', '_leave_task',
|
'_register_task', '_unregister_task', '_enter_task', '_leave_task',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -43,22 +44,26 @@ def all_tasks(loop=None):
|
||||||
"""Return a set of all tasks for the loop."""
|
"""Return a set of all tasks for the loop."""
|
||||||
if loop is None:
|
if loop is None:
|
||||||
loop = events.get_running_loop()
|
loop = events.get_running_loop()
|
||||||
# Looping over a WeakSet (_all_tasks) isn't safe as it can be updated from another
|
# capturing the set of eager tasks first, so if an eager task "graduates"
|
||||||
# thread while we do so. Therefore we cast it to list prior to filtering. The list
|
# to a regular task in another thread, we don't risk missing it.
|
||||||
# cast itself requires iteration, so we repeat it several times ignoring
|
eager_tasks = list(_eager_tasks)
|
||||||
# RuntimeErrors (which are not very likely to occur). See issues 34970 and 36607 for
|
# Looping over the WeakSet isn't safe as it can be updated from another
|
||||||
# details.
|
# thread, therefore we cast it to list prior to filtering. The list cast
|
||||||
|
# itself requires iteration, so we repeat it several times ignoring
|
||||||
|
# RuntimeErrors (which are not very likely to occur).
|
||||||
|
# See issues 34970 and 36607 for details.
|
||||||
|
scheduled_tasks = None
|
||||||
i = 0
|
i = 0
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
tasks = list(_all_tasks)
|
scheduled_tasks = list(_scheduled_tasks)
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
i += 1
|
i += 1
|
||||||
if i >= 1000:
|
if i >= 1000:
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
return {t for t in tasks
|
return {t for t in itertools.chain(scheduled_tasks, eager_tasks)
|
||||||
if futures._get_loop(t) is loop and not t.done()}
|
if futures._get_loop(t) is loop and not t.done()}
|
||||||
|
|
||||||
|
|
||||||
|
@ -93,7 +98,8 @@ class Task(futures._PyFuture): # Inherit Python Task implementation
|
||||||
# status is still pending
|
# status is still pending
|
||||||
_log_destroy_pending = True
|
_log_destroy_pending = True
|
||||||
|
|
||||||
def __init__(self, coro, *, loop=None, name=None, context=None):
|
def __init__(self, coro, *, loop=None, name=None, context=None,
|
||||||
|
eager_start=False):
|
||||||
super().__init__(loop=loop)
|
super().__init__(loop=loop)
|
||||||
if self._source_traceback:
|
if self._source_traceback:
|
||||||
del self._source_traceback[-1]
|
del self._source_traceback[-1]
|
||||||
|
@ -117,6 +123,9 @@ class Task(futures._PyFuture): # Inherit Python Task implementation
|
||||||
else:
|
else:
|
||||||
self._context = context
|
self._context = context
|
||||||
|
|
||||||
|
if eager_start and self._loop.is_running():
|
||||||
|
self.__eager_start()
|
||||||
|
else:
|
||||||
self._loop.call_soon(self.__step, context=self._context)
|
self._loop.call_soon(self.__step, context=self._context)
|
||||||
_register_task(self)
|
_register_task(self)
|
||||||
|
|
||||||
|
@ -250,6 +259,25 @@ class Task(futures._PyFuture): # Inherit Python Task implementation
|
||||||
self._num_cancels_requested -= 1
|
self._num_cancels_requested -= 1
|
||||||
return self._num_cancels_requested
|
return self._num_cancels_requested
|
||||||
|
|
||||||
|
def __eager_start(self):
|
||||||
|
prev_task = _swap_current_task(self._loop, self)
|
||||||
|
try:
|
||||||
|
_register_eager_task(self)
|
||||||
|
try:
|
||||||
|
self._context.run(self.__step_run_and_handle_result, None)
|
||||||
|
finally:
|
||||||
|
_unregister_eager_task(self)
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
curtask = _swap_current_task(self._loop, prev_task)
|
||||||
|
assert curtask is self
|
||||||
|
finally:
|
||||||
|
if self.done():
|
||||||
|
self._coro = None
|
||||||
|
self = None # Needed to break cycles when an exception occurs.
|
||||||
|
else:
|
||||||
|
_register_task(self)
|
||||||
|
|
||||||
def __step(self, exc=None):
|
def __step(self, exc=None):
|
||||||
if self.done():
|
if self.done():
|
||||||
raise exceptions.InvalidStateError(
|
raise exceptions.InvalidStateError(
|
||||||
|
@ -258,11 +286,17 @@ class Task(futures._PyFuture): # Inherit Python Task implementation
|
||||||
if not isinstance(exc, exceptions.CancelledError):
|
if not isinstance(exc, exceptions.CancelledError):
|
||||||
exc = self._make_cancelled_error()
|
exc = self._make_cancelled_error()
|
||||||
self._must_cancel = False
|
self._must_cancel = False
|
||||||
coro = self._coro
|
|
||||||
self._fut_waiter = None
|
self._fut_waiter = None
|
||||||
|
|
||||||
_enter_task(self._loop, self)
|
_enter_task(self._loop, self)
|
||||||
# Call either coro.throw(exc) or coro.send(None).
|
try:
|
||||||
|
self.__step_run_and_handle_result(exc)
|
||||||
|
finally:
|
||||||
|
_leave_task(self._loop, self)
|
||||||
|
self = None # Needed to break cycles when an exception occurs.
|
||||||
|
|
||||||
|
def __step_run_and_handle_result(self, exc):
|
||||||
|
coro = self._coro
|
||||||
try:
|
try:
|
||||||
if exc is None:
|
if exc is None:
|
||||||
# We use the `send` method directly, because coroutines
|
# We use the `send` method directly, because coroutines
|
||||||
|
@ -334,7 +368,6 @@ class Task(futures._PyFuture): # Inherit Python Task implementation
|
||||||
self._loop.call_soon(
|
self._loop.call_soon(
|
||||||
self.__step, new_exc, context=self._context)
|
self.__step, new_exc, context=self._context)
|
||||||
finally:
|
finally:
|
||||||
_leave_task(self._loop, self)
|
|
||||||
self = None # Needed to break cycles when an exception occurs.
|
self = None # Needed to break cycles when an exception occurs.
|
||||||
|
|
||||||
def __wakeup(self, future):
|
def __wakeup(self, future):
|
||||||
|
@ -897,8 +930,27 @@ def run_coroutine_threadsafe(coro, loop):
|
||||||
return future
|
return future
|
||||||
|
|
||||||
|
|
||||||
# WeakSet containing all alive tasks.
|
def create_eager_task_factory(custom_task_constructor):
|
||||||
_all_tasks = weakref.WeakSet()
|
|
||||||
|
if "eager_start" not in inspect.signature(custom_task_constructor).parameters:
|
||||||
|
raise TypeError(
|
||||||
|
"Provided constructor does not support eager task execution")
|
||||||
|
|
||||||
|
def factory(loop, coro, *, name=None, context=None):
|
||||||
|
return custom_task_constructor(
|
||||||
|
coro, loop=loop, name=name, context=context, eager_start=True)
|
||||||
|
|
||||||
|
|
||||||
|
return factory
|
||||||
|
|
||||||
|
eager_task_factory = create_eager_task_factory(Task)
|
||||||
|
|
||||||
|
|
||||||
|
# Collectively these two sets hold references to the complete set of active
|
||||||
|
# tasks. Eagerly executed tasks use a faster regular set as an optimization
|
||||||
|
# but may graduate to a WeakSet if the task blocks on IO.
|
||||||
|
_scheduled_tasks = weakref.WeakSet()
|
||||||
|
_eager_tasks = set()
|
||||||
|
|
||||||
# Dictionary containing tasks that are currently active in
|
# Dictionary containing tasks that are currently active in
|
||||||
# all running event loops. {EventLoop: Task}
|
# all running event loops. {EventLoop: Task}
|
||||||
|
@ -906,8 +958,13 @@ _current_tasks = {}
|
||||||
|
|
||||||
|
|
||||||
def _register_task(task):
|
def _register_task(task):
|
||||||
"""Register a new task in asyncio as executed by loop."""
|
"""Register an asyncio Task scheduled to run on an event loop."""
|
||||||
_all_tasks.add(task)
|
_scheduled_tasks.add(task)
|
||||||
|
|
||||||
|
|
||||||
|
def _register_eager_task(task):
|
||||||
|
"""Register an asyncio Task about to be eagerly executed."""
|
||||||
|
_eager_tasks.add(task)
|
||||||
|
|
||||||
|
|
||||||
def _enter_task(loop, task):
|
def _enter_task(loop, task):
|
||||||
|
@ -926,28 +983,49 @@ def _leave_task(loop, task):
|
||||||
del _current_tasks[loop]
|
del _current_tasks[loop]
|
||||||
|
|
||||||
|
|
||||||
|
def _swap_current_task(loop, task):
|
||||||
|
prev_task = _current_tasks.get(loop)
|
||||||
|
if task is None:
|
||||||
|
del _current_tasks[loop]
|
||||||
|
else:
|
||||||
|
_current_tasks[loop] = task
|
||||||
|
return prev_task
|
||||||
|
|
||||||
|
|
||||||
def _unregister_task(task):
|
def _unregister_task(task):
|
||||||
"""Unregister a task."""
|
"""Unregister a completed, scheduled Task."""
|
||||||
_all_tasks.discard(task)
|
_scheduled_tasks.discard(task)
|
||||||
|
|
||||||
|
|
||||||
|
def _unregister_eager_task(task):
|
||||||
|
"""Unregister a task which finished its first eager step."""
|
||||||
|
_eager_tasks.discard(task)
|
||||||
|
|
||||||
|
|
||||||
_py_current_task = current_task
|
_py_current_task = current_task
|
||||||
_py_register_task = _register_task
|
_py_register_task = _register_task
|
||||||
|
_py_register_eager_task = _register_eager_task
|
||||||
_py_unregister_task = _unregister_task
|
_py_unregister_task = _unregister_task
|
||||||
|
_py_unregister_eager_task = _unregister_eager_task
|
||||||
_py_enter_task = _enter_task
|
_py_enter_task = _enter_task
|
||||||
_py_leave_task = _leave_task
|
_py_leave_task = _leave_task
|
||||||
|
_py_swap_current_task = _swap_current_task
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from _asyncio import (_register_task, _unregister_task,
|
from _asyncio import (_register_task, _register_eager_task,
|
||||||
_enter_task, _leave_task,
|
_unregister_task, _unregister_eager_task,
|
||||||
_all_tasks, _current_tasks,
|
_enter_task, _leave_task, _swap_current_task,
|
||||||
|
_scheduled_tasks, _eager_tasks, _current_tasks,
|
||||||
current_task)
|
current_task)
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
_c_current_task = current_task
|
_c_current_task = current_task
|
||||||
_c_register_task = _register_task
|
_c_register_task = _register_task
|
||||||
|
_c_register_eager_task = _register_eager_task
|
||||||
_c_unregister_task = _unregister_task
|
_c_unregister_task = _unregister_task
|
||||||
|
_c_unregister_eager_task = _unregister_eager_task
|
||||||
_c_enter_task = _enter_task
|
_c_enter_task = _enter_task
|
||||||
_c_leave_task = _leave_task
|
_c_leave_task = _leave_task
|
||||||
|
_c_swap_current_task = _swap_current_task
|
||||||
|
|
344
Lib/test/test_asyncio/test_eager_task_factory.py
Normal file
344
Lib/test/test_asyncio/test_eager_task_factory.py
Normal file
|
@ -0,0 +1,344 @@
|
||||||
|
"""Tests for base_events.py"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import contextvars
|
||||||
|
import gc
|
||||||
|
import time
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from types import GenericAlias
|
||||||
|
from unittest import mock
|
||||||
|
from asyncio import base_events
|
||||||
|
from asyncio import tasks
|
||||||
|
from test.test_asyncio import utils as test_utils
|
||||||
|
from test.test_asyncio.test_tasks import get_innermost_context
|
||||||
|
from test import support
|
||||||
|
|
||||||
|
MOCK_ANY = mock.ANY
|
||||||
|
|
||||||
|
|
||||||
|
def tearDownModule():
|
||||||
|
asyncio.set_event_loop_policy(None)
|
||||||
|
|
||||||
|
|
||||||
|
class EagerTaskFactoryLoopTests:
|
||||||
|
|
||||||
|
Task = None
|
||||||
|
|
||||||
|
def run_coro(self, coro):
|
||||||
|
"""
|
||||||
|
Helper method to run the `coro` coroutine in the test event loop.
|
||||||
|
It helps with making sure the event loop is running before starting
|
||||||
|
to execute `coro`. This is important for testing the eager step
|
||||||
|
functionality, since an eager step is taken only if the event loop
|
||||||
|
is already running.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def coro_runner():
|
||||||
|
self.assertTrue(asyncio.get_event_loop().is_running())
|
||||||
|
return await coro
|
||||||
|
|
||||||
|
return self.loop.run_until_complete(coro)
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.loop = asyncio.new_event_loop()
|
||||||
|
self.eager_task_factory = asyncio.create_eager_task_factory(self.Task)
|
||||||
|
self.loop.set_task_factory(self.eager_task_factory)
|
||||||
|
self.set_event_loop(self.loop)
|
||||||
|
|
||||||
|
def test_eager_task_factory_set(self):
|
||||||
|
self.assertIsNotNone(self.eager_task_factory)
|
||||||
|
self.assertIs(self.loop.get_task_factory(), self.eager_task_factory)
|
||||||
|
|
||||||
|
async def noop(): pass
|
||||||
|
|
||||||
|
async def run():
|
||||||
|
t = self.loop.create_task(noop())
|
||||||
|
self.assertIsInstance(t, self.Task)
|
||||||
|
await t
|
||||||
|
|
||||||
|
self.run_coro(run())
|
||||||
|
|
||||||
|
def test_await_future_during_eager_step(self):
|
||||||
|
|
||||||
|
async def set_result(fut, val):
|
||||||
|
fut.set_result(val)
|
||||||
|
|
||||||
|
async def run():
|
||||||
|
fut = self.loop.create_future()
|
||||||
|
t = self.loop.create_task(set_result(fut, 'my message'))
|
||||||
|
# assert the eager step completed the task
|
||||||
|
self.assertTrue(t.done())
|
||||||
|
return await fut
|
||||||
|
|
||||||
|
self.assertEqual(self.run_coro(run()), 'my message')
|
||||||
|
|
||||||
|
def test_eager_completion(self):
|
||||||
|
|
||||||
|
async def coro():
|
||||||
|
return 'hello'
|
||||||
|
|
||||||
|
async def run():
|
||||||
|
t = self.loop.create_task(coro())
|
||||||
|
# assert the eager step completed the task
|
||||||
|
self.assertTrue(t.done())
|
||||||
|
return await t
|
||||||
|
|
||||||
|
self.assertEqual(self.run_coro(run()), 'hello')
|
||||||
|
|
||||||
|
def test_block_after_eager_step(self):
|
||||||
|
|
||||||
|
async def coro():
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
return 'finished after blocking'
|
||||||
|
|
||||||
|
async def run():
|
||||||
|
t = self.loop.create_task(coro())
|
||||||
|
self.assertFalse(t.done())
|
||||||
|
result = await t
|
||||||
|
self.assertTrue(t.done())
|
||||||
|
return result
|
||||||
|
|
||||||
|
self.assertEqual(self.run_coro(run()), 'finished after blocking')
|
||||||
|
|
||||||
|
def test_cancellation_after_eager_completion(self):
|
||||||
|
|
||||||
|
async def coro():
|
||||||
|
return 'finished without blocking'
|
||||||
|
|
||||||
|
async def run():
|
||||||
|
t = self.loop.create_task(coro())
|
||||||
|
t.cancel()
|
||||||
|
result = await t
|
||||||
|
# finished task can't be cancelled
|
||||||
|
self.assertFalse(t.cancelled())
|
||||||
|
return result
|
||||||
|
|
||||||
|
self.assertEqual(self.run_coro(run()), 'finished without blocking')
|
||||||
|
|
||||||
|
def test_cancellation_after_eager_step_blocks(self):
|
||||||
|
|
||||||
|
async def coro():
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
return 'finished after blocking'
|
||||||
|
|
||||||
|
async def run():
|
||||||
|
t = self.loop.create_task(coro())
|
||||||
|
t.cancel('cancellation message')
|
||||||
|
self.assertGreater(t.cancelling(), 0)
|
||||||
|
result = await t
|
||||||
|
|
||||||
|
with self.assertRaises(asyncio.CancelledError) as cm:
|
||||||
|
self.run_coro(run())
|
||||||
|
|
||||||
|
self.assertEqual('cancellation message', cm.exception.args[0])
|
||||||
|
|
||||||
|
def test_current_task(self):
|
||||||
|
captured_current_task = None
|
||||||
|
|
||||||
|
async def coro():
|
||||||
|
nonlocal captured_current_task
|
||||||
|
captured_current_task = asyncio.current_task()
|
||||||
|
# verify the task before and after blocking is identical
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
self.assertIs(asyncio.current_task(), captured_current_task)
|
||||||
|
|
||||||
|
async def run():
|
||||||
|
t = self.loop.create_task(coro())
|
||||||
|
self.assertIs(captured_current_task, t)
|
||||||
|
await t
|
||||||
|
|
||||||
|
self.run_coro(run())
|
||||||
|
captured_current_task = None
|
||||||
|
|
||||||
|
def test_all_tasks_with_eager_completion(self):
|
||||||
|
captured_all_tasks = None
|
||||||
|
|
||||||
|
async def coro():
|
||||||
|
nonlocal captured_all_tasks
|
||||||
|
captured_all_tasks = asyncio.all_tasks()
|
||||||
|
|
||||||
|
async def run():
|
||||||
|
t = self.loop.create_task(coro())
|
||||||
|
self.assertIn(t, captured_all_tasks)
|
||||||
|
self.assertNotIn(t, asyncio.all_tasks())
|
||||||
|
|
||||||
|
self.run_coro(run())
|
||||||
|
|
||||||
|
def test_all_tasks_with_blocking(self):
|
||||||
|
captured_eager_all_tasks = None
|
||||||
|
|
||||||
|
async def coro(fut1, fut2):
|
||||||
|
nonlocal captured_eager_all_tasks
|
||||||
|
captured_eager_all_tasks = asyncio.all_tasks()
|
||||||
|
await fut1
|
||||||
|
fut2.set_result(None)
|
||||||
|
|
||||||
|
async def run():
|
||||||
|
fut1 = self.loop.create_future()
|
||||||
|
fut2 = self.loop.create_future()
|
||||||
|
t = self.loop.create_task(coro(fut1, fut2))
|
||||||
|
self.assertIn(t, captured_eager_all_tasks)
|
||||||
|
self.assertIn(t, asyncio.all_tasks())
|
||||||
|
fut1.set_result(None)
|
||||||
|
await fut2
|
||||||
|
self.assertNotIn(t, asyncio.all_tasks())
|
||||||
|
|
||||||
|
self.run_coro(run())
|
||||||
|
|
||||||
|
def test_context_vars(self):
|
||||||
|
cv = contextvars.ContextVar('cv', default=0)
|
||||||
|
|
||||||
|
coro_first_step_ran = False
|
||||||
|
coro_second_step_ran = False
|
||||||
|
|
||||||
|
async def coro():
|
||||||
|
nonlocal coro_first_step_ran
|
||||||
|
nonlocal coro_second_step_ran
|
||||||
|
self.assertEqual(cv.get(), 1)
|
||||||
|
cv.set(2)
|
||||||
|
self.assertEqual(cv.get(), 2)
|
||||||
|
coro_first_step_ran = True
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
self.assertEqual(cv.get(), 2)
|
||||||
|
cv.set(3)
|
||||||
|
self.assertEqual(cv.get(), 3)
|
||||||
|
coro_second_step_ran = True
|
||||||
|
|
||||||
|
async def run():
|
||||||
|
cv.set(1)
|
||||||
|
t = self.loop.create_task(coro())
|
||||||
|
self.assertTrue(coro_first_step_ran)
|
||||||
|
self.assertFalse(coro_second_step_ran)
|
||||||
|
self.assertEqual(cv.get(), 1)
|
||||||
|
await t
|
||||||
|
self.assertTrue(coro_second_step_ran)
|
||||||
|
self.assertEqual(cv.get(), 1)
|
||||||
|
|
||||||
|
self.run_coro(run())
|
||||||
|
|
||||||
|
|
||||||
|
class PyEagerTaskFactoryLoopTests(EagerTaskFactoryLoopTests, test_utils.TestCase):
|
||||||
|
Task = tasks._PyTask
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipUnless(hasattr(tasks, '_CTask'),
|
||||||
|
'requires the C _asyncio module')
|
||||||
|
class CEagerTaskFactoryLoopTests(EagerTaskFactoryLoopTests, test_utils.TestCase):
|
||||||
|
Task = getattr(tasks, '_CTask', None)
|
||||||
|
|
||||||
|
|
||||||
|
class AsyncTaskCounter:
|
||||||
|
def __init__(self, loop, *, task_class, eager):
|
||||||
|
self.suspense_count = 0
|
||||||
|
self.task_count = 0
|
||||||
|
|
||||||
|
def CountingTask(*args, eager_start=False, **kwargs):
|
||||||
|
if not eager_start:
|
||||||
|
self.task_count += 1
|
||||||
|
kwargs["eager_start"] = eager_start
|
||||||
|
return task_class(*args, **kwargs)
|
||||||
|
|
||||||
|
if eager:
|
||||||
|
factory = asyncio.create_eager_task_factory(CountingTask)
|
||||||
|
else:
|
||||||
|
def factory(loop, coro, **kwargs):
|
||||||
|
return CountingTask(coro, loop=loop, **kwargs)
|
||||||
|
loop.set_task_factory(factory)
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
return self.task_count
|
||||||
|
|
||||||
|
|
||||||
|
async def awaitable_chain(depth):
|
||||||
|
if depth == 0:
|
||||||
|
return 0
|
||||||
|
return 1 + await awaitable_chain(depth - 1)
|
||||||
|
|
||||||
|
|
||||||
|
async def recursive_taskgroups(width, depth):
|
||||||
|
if depth == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
async with asyncio.TaskGroup() as tg:
|
||||||
|
futures = [
|
||||||
|
tg.create_task(recursive_taskgroups(width, depth - 1))
|
||||||
|
for _ in range(width)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
async def recursive_gather(width, depth):
|
||||||
|
if depth == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
await asyncio.gather(
|
||||||
|
*[recursive_gather(width, depth - 1) for _ in range(width)]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BaseTaskCountingTests:
|
||||||
|
|
||||||
|
Task = None
|
||||||
|
eager = None
|
||||||
|
expected_task_count = None
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.loop = asyncio.new_event_loop()
|
||||||
|
self.counter = AsyncTaskCounter(self.loop, task_class=self.Task, eager=self.eager)
|
||||||
|
self.set_event_loop(self.loop)
|
||||||
|
|
||||||
|
def test_awaitables_chain(self):
|
||||||
|
observed_depth = self.loop.run_until_complete(awaitable_chain(100))
|
||||||
|
self.assertEqual(observed_depth, 100)
|
||||||
|
self.assertEqual(self.counter.get(), 0 if self.eager else 1)
|
||||||
|
|
||||||
|
def test_recursive_taskgroups(self):
|
||||||
|
num_tasks = self.loop.run_until_complete(recursive_taskgroups(5, 4))
|
||||||
|
self.assertEqual(self.counter.get(), self.expected_task_count)
|
||||||
|
|
||||||
|
def test_recursive_gather(self):
|
||||||
|
self.loop.run_until_complete(recursive_gather(5, 4))
|
||||||
|
self.assertEqual(self.counter.get(), self.expected_task_count)
|
||||||
|
|
||||||
|
|
||||||
|
class BaseNonEagerTaskFactoryTests(BaseTaskCountingTests):
|
||||||
|
eager = False
|
||||||
|
expected_task_count = 781 # 1 + 5 + 5^2 + 5^3 + 5^4
|
||||||
|
|
||||||
|
|
||||||
|
class BaseEagerTaskFactoryTests(BaseTaskCountingTests):
|
||||||
|
eager = True
|
||||||
|
expected_task_count = 0
|
||||||
|
|
||||||
|
|
||||||
|
class NonEagerTests(BaseNonEagerTaskFactoryTests, test_utils.TestCase):
|
||||||
|
Task = asyncio.Task
|
||||||
|
|
||||||
|
|
||||||
|
class EagerTests(BaseEagerTaskFactoryTests, test_utils.TestCase):
|
||||||
|
Task = asyncio.Task
|
||||||
|
|
||||||
|
|
||||||
|
class NonEagerPyTaskTests(BaseNonEagerTaskFactoryTests, test_utils.TestCase):
|
||||||
|
Task = tasks._PyTask
|
||||||
|
|
||||||
|
|
||||||
|
class EagerPyTaskTests(BaseEagerTaskFactoryTests, test_utils.TestCase):
|
||||||
|
Task = tasks._PyTask
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipUnless(hasattr(tasks, '_CTask'),
|
||||||
|
'requires the C _asyncio module')
|
||||||
|
class NonEagerCTaskTests(BaseNonEagerTaskFactoryTests, test_utils.TestCase):
|
||||||
|
Task = getattr(tasks, '_CTask', None)
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipUnless(hasattr(tasks, '_CTask'),
|
||||||
|
'requires the C _asyncio module')
|
||||||
|
class EagerCTaskTests(BaseEagerTaskFactoryTests, test_utils.TestCase):
|
||||||
|
Task = getattr(tasks, '_CTask', None)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
|
@ -0,0 +1,6 @@
|
||||||
|
Implemented an eager task factory in asyncio.
|
||||||
|
When used as a task factory on an event loop, it performs eager execution of
|
||||||
|
coroutines. Coroutines that are able to complete synchronously (e.g. return or
|
||||||
|
raise without blocking) are returned immediately as a finished task, and the
|
||||||
|
task is never scheduled to the event loop. If the coroutine blocks, the
|
||||||
|
(pending) task is scheduled and returned.
|
|
@ -8,6 +8,7 @@
|
||||||
#include "pycore_runtime_init.h" // _Py_ID()
|
#include "pycore_runtime_init.h" // _Py_ID()
|
||||||
#include "pycore_moduleobject.h" // _PyModule_GetState()
|
#include "pycore_moduleobject.h" // _PyModule_GetState()
|
||||||
#include "structmember.h" // PyMemberDef
|
#include "structmember.h" // PyMemberDef
|
||||||
|
#include "cpython/context.h"
|
||||||
#include <stddef.h> // offsetof()
|
#include <stddef.h> // offsetof()
|
||||||
|
|
||||||
|
|
||||||
|
@ -31,8 +32,11 @@ typedef struct {
|
||||||
all running event loops. {EventLoop: Task} */
|
all running event loops. {EventLoop: Task} */
|
||||||
PyObject *current_tasks;
|
PyObject *current_tasks;
|
||||||
|
|
||||||
/* WeakSet containing all alive tasks. */
|
/* WeakSet containing all tasks scheduled to run on event loops. */
|
||||||
PyObject *all_tasks;
|
PyObject *scheduled_tasks;
|
||||||
|
|
||||||
|
/* Set containing all eagerly executing tasks. */
|
||||||
|
PyObject *eager_tasks;
|
||||||
|
|
||||||
/* An isinstance type cache for the 'is_coroutine()' function. */
|
/* An isinstance type cache for the 'is_coroutine()' function. */
|
||||||
PyObject *iscoroutine_typecache;
|
PyObject *iscoroutine_typecache;
|
||||||
|
@ -156,6 +160,9 @@ class _asyncio.Future "FutureObj *" "&Future_Type"
|
||||||
/* Get FutureIter from Future */
|
/* Get FutureIter from Future */
|
||||||
static PyObject * future_new_iter(PyObject *);
|
static PyObject * future_new_iter(PyObject *);
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
task_step_handle_result_impl(asyncio_state *state, TaskObj *task, PyObject *result);
|
||||||
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
_is_coroutine(asyncio_state *state, PyObject *coro)
|
_is_coroutine(asyncio_state *state, PyObject *coro)
|
||||||
|
@ -1830,6 +1837,7 @@ class _asyncio.Task "TaskObj *" "&Task_Type"
|
||||||
static int task_call_step_soon(asyncio_state *state, TaskObj *, PyObject *);
|
static int task_call_step_soon(asyncio_state *state, TaskObj *, PyObject *);
|
||||||
static PyObject * task_wakeup(TaskObj *, PyObject *);
|
static PyObject * task_wakeup(TaskObj *, PyObject *);
|
||||||
static PyObject * task_step(asyncio_state *, TaskObj *, PyObject *);
|
static PyObject * task_step(asyncio_state *, TaskObj *, PyObject *);
|
||||||
|
static int task_eager_start(asyncio_state *state, TaskObj *task);
|
||||||
|
|
||||||
/* ----- Task._step wrapper */
|
/* ----- Task._step wrapper */
|
||||||
|
|
||||||
|
@ -1940,7 +1948,7 @@ static PyMethodDef TaskWakeupDef = {
|
||||||
static int
|
static int
|
||||||
register_task(asyncio_state *state, PyObject *task)
|
register_task(asyncio_state *state, PyObject *task)
|
||||||
{
|
{
|
||||||
PyObject *res = PyObject_CallMethodOneArg(state->all_tasks,
|
PyObject *res = PyObject_CallMethodOneArg(state->scheduled_tasks,
|
||||||
&_Py_ID(add), task);
|
&_Py_ID(add), task);
|
||||||
if (res == NULL) {
|
if (res == NULL) {
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -1949,11 +1957,16 @@ register_task(asyncio_state *state, PyObject *task)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
register_eager_task(asyncio_state *state, PyObject *task)
|
||||||
|
{
|
||||||
|
return PySet_Add(state->eager_tasks, task);
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
unregister_task(asyncio_state *state, PyObject *task)
|
unregister_task(asyncio_state *state, PyObject *task)
|
||||||
{
|
{
|
||||||
PyObject *res = PyObject_CallMethodOneArg(state->all_tasks,
|
PyObject *res = PyObject_CallMethodOneArg(state->scheduled_tasks,
|
||||||
&_Py_ID(discard), task);
|
&_Py_ID(discard), task);
|
||||||
if (res == NULL) {
|
if (res == NULL) {
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -1962,6 +1975,11 @@ unregister_task(asyncio_state *state, PyObject *task)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
unregister_eager_task(asyncio_state *state, PyObject *task)
|
||||||
|
{
|
||||||
|
return PySet_Discard(state->eager_tasks, task);
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
enter_task(asyncio_state *state, PyObject *loop, PyObject *task)
|
enter_task(asyncio_state *state, PyObject *loop, PyObject *task)
|
||||||
|
@ -2015,6 +2033,54 @@ leave_task(asyncio_state *state, PyObject *loop, PyObject *task)
|
||||||
return _PyDict_DelItem_KnownHash(state->current_tasks, loop, hash);
|
return _PyDict_DelItem_KnownHash(state->current_tasks, loop, hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
swap_current_task(asyncio_state *state, PyObject *loop, PyObject *task)
|
||||||
|
{
|
||||||
|
PyObject *prev_task;
|
||||||
|
Py_hash_t hash;
|
||||||
|
hash = PyObject_Hash(loop);
|
||||||
|
if (hash == -1) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
prev_task = _PyDict_GetItem_KnownHash(state->current_tasks, loop, hash);
|
||||||
|
if (prev_task == NULL) {
|
||||||
|
if (PyErr_Occurred()) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
prev_task = Py_None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (task == Py_None) {
|
||||||
|
if (_PyDict_DelItem_KnownHash(state->current_tasks, loop, hash) == -1) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (_PyDict_SetItem_KnownHash(state->current_tasks, loop, task, hash) == -1) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_INCREF(prev_task);
|
||||||
|
|
||||||
|
return prev_task;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
is_loop_running(PyObject *loop)
|
||||||
|
{
|
||||||
|
PyObject *func = PyObject_GetAttr(loop, &_Py_ID(is_running));
|
||||||
|
if (func == NULL) {
|
||||||
|
PyErr_Format(PyExc_TypeError, "Loop missing is_running()");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
PyObject *res = PyObject_CallNoArgs(func);
|
||||||
|
int retval = Py_IsTrue(res);
|
||||||
|
Py_DECREF(func);
|
||||||
|
Py_DECREF(res);
|
||||||
|
return !!retval;
|
||||||
|
}
|
||||||
|
|
||||||
/* ----- Task */
|
/* ----- Task */
|
||||||
|
|
||||||
/*[clinic input]
|
/*[clinic input]
|
||||||
|
@ -2025,15 +2091,16 @@ _asyncio.Task.__init__
|
||||||
loop: object = None
|
loop: object = None
|
||||||
name: object = None
|
name: object = None
|
||||||
context: object = None
|
context: object = None
|
||||||
|
eager_start: bool = False
|
||||||
|
|
||||||
A coroutine wrapped in a Future.
|
A coroutine wrapped in a Future.
|
||||||
[clinic start generated code]*/
|
[clinic start generated code]*/
|
||||||
|
|
||||||
static int
|
static int
|
||||||
_asyncio_Task___init___impl(TaskObj *self, PyObject *coro, PyObject *loop,
|
_asyncio_Task___init___impl(TaskObj *self, PyObject *coro, PyObject *loop,
|
||||||
PyObject *name, PyObject *context)
|
PyObject *name, PyObject *context,
|
||||||
/*[clinic end generated code: output=49ac96fe33d0e5c7 input=924522490c8ce825]*/
|
int eager_start)
|
||||||
|
/*[clinic end generated code: output=7aced2d27836f1a1 input=18e3f113a51b829d]*/
|
||||||
{
|
{
|
||||||
if (future_init((FutureObj*)self, loop)) {
|
if (future_init((FutureObj*)self, loop)) {
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -2083,6 +2150,19 @@ _asyncio_Task___init___impl(TaskObj *self, PyObject *coro, PyObject *loop,
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (eager_start) {
|
||||||
|
int loop_running = is_loop_running(self->task_loop);
|
||||||
|
if (loop_running == -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (loop_running) {
|
||||||
|
if (task_eager_start(state, self)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (task_call_step_soon(state, self, NULL)) {
|
if (task_call_step_soon(state, self, NULL)) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
@ -2831,6 +2911,20 @@ task_step_impl(asyncio_state *state, TaskObj *task, PyObject *exc)
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyObject *ret = task_step_handle_result_impl(state, task, result);
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
task_step_handle_result_impl(asyncio_state *state, TaskObj *task, PyObject *result)
|
||||||
|
{
|
||||||
|
int res;
|
||||||
|
PyObject *o;
|
||||||
|
|
||||||
if (result == (PyObject*)task) {
|
if (result == (PyObject*)task) {
|
||||||
/* We have a task that wants to await on itself */
|
/* We have a task that wants to await on itself */
|
||||||
goto self_await;
|
goto self_await;
|
||||||
|
@ -3062,6 +3156,65 @@ task_step(asyncio_state *state, TaskObj *task, PyObject *exc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
task_eager_start(asyncio_state *state, TaskObj *task)
|
||||||
|
{
|
||||||
|
assert(task != NULL);
|
||||||
|
PyObject *prevtask = swap_current_task(state, task->task_loop, (PyObject *)task);
|
||||||
|
if (prevtask == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (register_eager_task(state, (PyObject *)task) == -1) {
|
||||||
|
Py_DECREF(prevtask);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PyContext_Enter(task->task_context) == -1) {
|
||||||
|
Py_DECREF(prevtask);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int retval = 0;
|
||||||
|
|
||||||
|
PyObject *stepres = task_step_impl(state, task, NULL);
|
||||||
|
if (stepres == NULL) {
|
||||||
|
PyObject *exc = PyErr_GetRaisedException();
|
||||||
|
_PyErr_ChainExceptions1(exc);
|
||||||
|
retval = -1;
|
||||||
|
} else {
|
||||||
|
Py_DECREF(stepres);
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *curtask = swap_current_task(state, task->task_loop, prevtask);
|
||||||
|
Py_DECREF(prevtask);
|
||||||
|
if (curtask == NULL) {
|
||||||
|
retval = -1;
|
||||||
|
} else {
|
||||||
|
assert(curtask == (PyObject *)task);
|
||||||
|
Py_DECREF(curtask);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unregister_eager_task(state, (PyObject *)task) == -1) {
|
||||||
|
retval = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PyContext_Exit(task->task_context) == -1) {
|
||||||
|
retval = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (task->task_state == STATE_PENDING) {
|
||||||
|
if (register_task(state, (PyObject *)task) == -1) {
|
||||||
|
retval = -1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// This seems to really help performance on pyperformance benchmarks
|
||||||
|
Py_CLEAR(task->task_coro);
|
||||||
|
}
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
task_wakeup(TaskObj *task, PyObject *o)
|
task_wakeup(TaskObj *task, PyObject *o)
|
||||||
{
|
{
|
||||||
|
@ -3225,6 +3378,27 @@ _asyncio__register_task_impl(PyObject *module, PyObject *task)
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*[clinic input]
|
||||||
|
_asyncio._register_eager_task
|
||||||
|
|
||||||
|
task: object
|
||||||
|
|
||||||
|
Register a new task in asyncio as executed by loop.
|
||||||
|
|
||||||
|
Returns None.
|
||||||
|
[clinic start generated code]*/
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
_asyncio__register_eager_task_impl(PyObject *module, PyObject *task)
|
||||||
|
/*[clinic end generated code: output=dfe1d45367c73f1a input=237f684683398c51]*/
|
||||||
|
{
|
||||||
|
asyncio_state *state = get_asyncio_state(module);
|
||||||
|
if (register_eager_task(state, task) < 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*[clinic input]
|
/*[clinic input]
|
||||||
_asyncio._unregister_task
|
_asyncio._unregister_task
|
||||||
|
@ -3247,6 +3421,27 @@ _asyncio__unregister_task_impl(PyObject *module, PyObject *task)
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*[clinic input]
|
||||||
|
_asyncio._unregister_eager_task
|
||||||
|
|
||||||
|
task: object
|
||||||
|
|
||||||
|
Unregister a task.
|
||||||
|
|
||||||
|
Returns None.
|
||||||
|
[clinic start generated code]*/
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
_asyncio__unregister_eager_task_impl(PyObject *module, PyObject *task)
|
||||||
|
/*[clinic end generated code: output=a426922bd07f23d1 input=9d07401ef14ee048]*/
|
||||||
|
{
|
||||||
|
asyncio_state *state = get_asyncio_state(module);
|
||||||
|
if (unregister_eager_task(state, task) < 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*[clinic input]
|
/*[clinic input]
|
||||||
_asyncio._enter_task
|
_asyncio._enter_task
|
||||||
|
@ -3298,6 +3493,27 @@ _asyncio__leave_task_impl(PyObject *module, PyObject *loop, PyObject *task)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*[clinic input]
|
||||||
|
_asyncio._swap_current_task
|
||||||
|
|
||||||
|
loop: object
|
||||||
|
task: object
|
||||||
|
|
||||||
|
Temporarily swap in the supplied task and return the original one (or None).
|
||||||
|
|
||||||
|
This is intended for use during eager coroutine execution.
|
||||||
|
|
||||||
|
[clinic start generated code]*/
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
_asyncio__swap_current_task_impl(PyObject *module, PyObject *loop,
|
||||||
|
PyObject *task)
|
||||||
|
/*[clinic end generated code: output=9f88de958df74c7e input=c9c72208d3d38b6c]*/
|
||||||
|
{
|
||||||
|
return swap_current_task(get_asyncio_state(module), loop, task);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*[clinic input]
|
/*[clinic input]
|
||||||
_asyncio.current_task
|
_asyncio.current_task
|
||||||
|
|
||||||
|
@ -3379,7 +3595,8 @@ module_traverse(PyObject *mod, visitproc visit, void *arg)
|
||||||
Py_VISIT(state->asyncio_InvalidStateError);
|
Py_VISIT(state->asyncio_InvalidStateError);
|
||||||
Py_VISIT(state->asyncio_CancelledError);
|
Py_VISIT(state->asyncio_CancelledError);
|
||||||
|
|
||||||
Py_VISIT(state->all_tasks);
|
Py_VISIT(state->scheduled_tasks);
|
||||||
|
Py_VISIT(state->eager_tasks);
|
||||||
Py_VISIT(state->current_tasks);
|
Py_VISIT(state->current_tasks);
|
||||||
Py_VISIT(state->iscoroutine_typecache);
|
Py_VISIT(state->iscoroutine_typecache);
|
||||||
|
|
||||||
|
@ -3416,7 +3633,8 @@ module_clear(PyObject *mod)
|
||||||
Py_CLEAR(state->asyncio_InvalidStateError);
|
Py_CLEAR(state->asyncio_InvalidStateError);
|
||||||
Py_CLEAR(state->asyncio_CancelledError);
|
Py_CLEAR(state->asyncio_CancelledError);
|
||||||
|
|
||||||
Py_CLEAR(state->all_tasks);
|
Py_CLEAR(state->scheduled_tasks);
|
||||||
|
Py_CLEAR(state->eager_tasks);
|
||||||
Py_CLEAR(state->current_tasks);
|
Py_CLEAR(state->current_tasks);
|
||||||
Py_CLEAR(state->iscoroutine_typecache);
|
Py_CLEAR(state->iscoroutine_typecache);
|
||||||
|
|
||||||
|
@ -3496,9 +3714,14 @@ module_init(asyncio_state *state)
|
||||||
PyObject *weak_set;
|
PyObject *weak_set;
|
||||||
WITH_MOD("weakref")
|
WITH_MOD("weakref")
|
||||||
GET_MOD_ATTR(weak_set, "WeakSet");
|
GET_MOD_ATTR(weak_set, "WeakSet");
|
||||||
state->all_tasks = PyObject_CallNoArgs(weak_set);
|
state->scheduled_tasks = PyObject_CallNoArgs(weak_set);
|
||||||
Py_CLEAR(weak_set);
|
Py_CLEAR(weak_set);
|
||||||
if (state->all_tasks == NULL) {
|
if (state->scheduled_tasks == NULL) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
state->eager_tasks = PySet_New(NULL);
|
||||||
|
if (state->eager_tasks == NULL) {
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3522,9 +3745,12 @@ static PyMethodDef asyncio_methods[] = {
|
||||||
_ASYNCIO__GET_RUNNING_LOOP_METHODDEF
|
_ASYNCIO__GET_RUNNING_LOOP_METHODDEF
|
||||||
_ASYNCIO__SET_RUNNING_LOOP_METHODDEF
|
_ASYNCIO__SET_RUNNING_LOOP_METHODDEF
|
||||||
_ASYNCIO__REGISTER_TASK_METHODDEF
|
_ASYNCIO__REGISTER_TASK_METHODDEF
|
||||||
|
_ASYNCIO__REGISTER_EAGER_TASK_METHODDEF
|
||||||
_ASYNCIO__UNREGISTER_TASK_METHODDEF
|
_ASYNCIO__UNREGISTER_TASK_METHODDEF
|
||||||
|
_ASYNCIO__UNREGISTER_EAGER_TASK_METHODDEF
|
||||||
_ASYNCIO__ENTER_TASK_METHODDEF
|
_ASYNCIO__ENTER_TASK_METHODDEF
|
||||||
_ASYNCIO__LEAVE_TASK_METHODDEF
|
_ASYNCIO__LEAVE_TASK_METHODDEF
|
||||||
|
_ASYNCIO__SWAP_CURRENT_TASK_METHODDEF
|
||||||
{NULL, NULL}
|
{NULL, NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -3561,7 +3787,11 @@ module_exec(PyObject *mod)
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PyModule_AddObjectRef(mod, "_all_tasks", state->all_tasks) < 0) {
|
if (PyModule_AddObjectRef(mod, "_scheduled_tasks", state->scheduled_tasks) < 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PyModule_AddObjectRef(mod, "_eager_tasks", state->eager_tasks) < 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
201
Modules/clinic/_asynciomodule.c.h
generated
201
Modules/clinic/_asynciomodule.c.h
generated
|
@ -482,14 +482,15 @@ _asyncio_Future__make_cancelled_error(FutureObj *self, PyObject *Py_UNUSED(ignor
|
||||||
}
|
}
|
||||||
|
|
||||||
PyDoc_STRVAR(_asyncio_Task___init____doc__,
|
PyDoc_STRVAR(_asyncio_Task___init____doc__,
|
||||||
"Task(coro, *, loop=None, name=None, context=None)\n"
|
"Task(coro, *, loop=None, name=None, context=None, eager_start=False)\n"
|
||||||
"--\n"
|
"--\n"
|
||||||
"\n"
|
"\n"
|
||||||
"A coroutine wrapped in a Future.");
|
"A coroutine wrapped in a Future.");
|
||||||
|
|
||||||
static int
|
static int
|
||||||
_asyncio_Task___init___impl(TaskObj *self, PyObject *coro, PyObject *loop,
|
_asyncio_Task___init___impl(TaskObj *self, PyObject *coro, PyObject *loop,
|
||||||
PyObject *name, PyObject *context);
|
PyObject *name, PyObject *context,
|
||||||
|
int eager_start);
|
||||||
|
|
||||||
static int
|
static int
|
||||||
_asyncio_Task___init__(PyObject *self, PyObject *args, PyObject *kwargs)
|
_asyncio_Task___init__(PyObject *self, PyObject *args, PyObject *kwargs)
|
||||||
|
@ -497,14 +498,14 @@ _asyncio_Task___init__(PyObject *self, PyObject *args, PyObject *kwargs)
|
||||||
int return_value = -1;
|
int return_value = -1;
|
||||||
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
|
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
|
||||||
|
|
||||||
#define NUM_KEYWORDS 4
|
#define NUM_KEYWORDS 5
|
||||||
static struct {
|
static struct {
|
||||||
PyGC_Head _this_is_not_used;
|
PyGC_Head _this_is_not_used;
|
||||||
PyObject_VAR_HEAD
|
PyObject_VAR_HEAD
|
||||||
PyObject *ob_item[NUM_KEYWORDS];
|
PyObject *ob_item[NUM_KEYWORDS];
|
||||||
} _kwtuple = {
|
} _kwtuple = {
|
||||||
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
|
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
|
||||||
.ob_item = { &_Py_ID(coro), &_Py_ID(loop), &_Py_ID(name), &_Py_ID(context), },
|
.ob_item = { &_Py_ID(coro), &_Py_ID(loop), &_Py_ID(name), &_Py_ID(context), &_Py_ID(eager_start), },
|
||||||
};
|
};
|
||||||
#undef NUM_KEYWORDS
|
#undef NUM_KEYWORDS
|
||||||
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
|
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
|
||||||
|
@ -513,14 +514,14 @@ _asyncio_Task___init__(PyObject *self, PyObject *args, PyObject *kwargs)
|
||||||
# define KWTUPLE NULL
|
# define KWTUPLE NULL
|
||||||
#endif // !Py_BUILD_CORE
|
#endif // !Py_BUILD_CORE
|
||||||
|
|
||||||
static const char * const _keywords[] = {"coro", "loop", "name", "context", NULL};
|
static const char * const _keywords[] = {"coro", "loop", "name", "context", "eager_start", NULL};
|
||||||
static _PyArg_Parser _parser = {
|
static _PyArg_Parser _parser = {
|
||||||
.keywords = _keywords,
|
.keywords = _keywords,
|
||||||
.fname = "Task",
|
.fname = "Task",
|
||||||
.kwtuple = KWTUPLE,
|
.kwtuple = KWTUPLE,
|
||||||
};
|
};
|
||||||
#undef KWTUPLE
|
#undef KWTUPLE
|
||||||
PyObject *argsbuf[4];
|
PyObject *argsbuf[5];
|
||||||
PyObject * const *fastargs;
|
PyObject * const *fastargs;
|
||||||
Py_ssize_t nargs = PyTuple_GET_SIZE(args);
|
Py_ssize_t nargs = PyTuple_GET_SIZE(args);
|
||||||
Py_ssize_t noptargs = nargs + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - 1;
|
Py_ssize_t noptargs = nargs + (kwargs ? PyDict_GET_SIZE(kwargs) : 0) - 1;
|
||||||
|
@ -528,6 +529,7 @@ _asyncio_Task___init__(PyObject *self, PyObject *args, PyObject *kwargs)
|
||||||
PyObject *loop = Py_None;
|
PyObject *loop = Py_None;
|
||||||
PyObject *name = Py_None;
|
PyObject *name = Py_None;
|
||||||
PyObject *context = Py_None;
|
PyObject *context = Py_None;
|
||||||
|
int eager_start = 0;
|
||||||
|
|
||||||
fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, 1, 1, 0, argsbuf);
|
fastargs = _PyArg_UnpackKeywords(_PyTuple_CAST(args)->ob_item, nargs, kwargs, NULL, &_parser, 1, 1, 0, argsbuf);
|
||||||
if (!fastargs) {
|
if (!fastargs) {
|
||||||
|
@ -549,9 +551,18 @@ _asyncio_Task___init__(PyObject *self, PyObject *args, PyObject *kwargs)
|
||||||
goto skip_optional_kwonly;
|
goto skip_optional_kwonly;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (fastargs[3]) {
|
||||||
context = fastargs[3];
|
context = fastargs[3];
|
||||||
|
if (!--noptargs) {
|
||||||
|
goto skip_optional_kwonly;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
eager_start = PyObject_IsTrue(fastargs[4]);
|
||||||
|
if (eager_start < 0) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
skip_optional_kwonly:
|
skip_optional_kwonly:
|
||||||
return_value = _asyncio_Task___init___impl((TaskObj *)self, coro, loop, name, context);
|
return_value = _asyncio_Task___init___impl((TaskObj *)self, coro, loop, name, context, eager_start);
|
||||||
|
|
||||||
exit:
|
exit:
|
||||||
return return_value;
|
return return_value;
|
||||||
|
@ -1064,6 +1075,63 @@ exit:
|
||||||
return return_value;
|
return return_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyDoc_STRVAR(_asyncio__register_eager_task__doc__,
|
||||||
|
"_register_eager_task($module, /, task)\n"
|
||||||
|
"--\n"
|
||||||
|
"\n"
|
||||||
|
"Register a new task in asyncio as executed by loop.\n"
|
||||||
|
"\n"
|
||||||
|
"Returns None.");
|
||||||
|
|
||||||
|
#define _ASYNCIO__REGISTER_EAGER_TASK_METHODDEF \
|
||||||
|
{"_register_eager_task", _PyCFunction_CAST(_asyncio__register_eager_task), METH_FASTCALL|METH_KEYWORDS, _asyncio__register_eager_task__doc__},
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
_asyncio__register_eager_task_impl(PyObject *module, PyObject *task);
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
_asyncio__register_eager_task(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
|
||||||
|
{
|
||||||
|
PyObject *return_value = NULL;
|
||||||
|
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
|
||||||
|
|
||||||
|
#define NUM_KEYWORDS 1
|
||||||
|
static struct {
|
||||||
|
PyGC_Head _this_is_not_used;
|
||||||
|
PyObject_VAR_HEAD
|
||||||
|
PyObject *ob_item[NUM_KEYWORDS];
|
||||||
|
} _kwtuple = {
|
||||||
|
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
|
||||||
|
.ob_item = { &_Py_ID(task), },
|
||||||
|
};
|
||||||
|
#undef NUM_KEYWORDS
|
||||||
|
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
|
||||||
|
|
||||||
|
#else // !Py_BUILD_CORE
|
||||||
|
# define KWTUPLE NULL
|
||||||
|
#endif // !Py_BUILD_CORE
|
||||||
|
|
||||||
|
static const char * const _keywords[] = {"task", NULL};
|
||||||
|
static _PyArg_Parser _parser = {
|
||||||
|
.keywords = _keywords,
|
||||||
|
.fname = "_register_eager_task",
|
||||||
|
.kwtuple = KWTUPLE,
|
||||||
|
};
|
||||||
|
#undef KWTUPLE
|
||||||
|
PyObject *argsbuf[1];
|
||||||
|
PyObject *task;
|
||||||
|
|
||||||
|
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf);
|
||||||
|
if (!args) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
task = args[0];
|
||||||
|
return_value = _asyncio__register_eager_task_impl(module, task);
|
||||||
|
|
||||||
|
exit:
|
||||||
|
return return_value;
|
||||||
|
}
|
||||||
|
|
||||||
PyDoc_STRVAR(_asyncio__unregister_task__doc__,
|
PyDoc_STRVAR(_asyncio__unregister_task__doc__,
|
||||||
"_unregister_task($module, /, task)\n"
|
"_unregister_task($module, /, task)\n"
|
||||||
"--\n"
|
"--\n"
|
||||||
|
@ -1121,6 +1189,63 @@ exit:
|
||||||
return return_value;
|
return return_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyDoc_STRVAR(_asyncio__unregister_eager_task__doc__,
|
||||||
|
"_unregister_eager_task($module, /, task)\n"
|
||||||
|
"--\n"
|
||||||
|
"\n"
|
||||||
|
"Unregister a task.\n"
|
||||||
|
"\n"
|
||||||
|
"Returns None.");
|
||||||
|
|
||||||
|
#define _ASYNCIO__UNREGISTER_EAGER_TASK_METHODDEF \
|
||||||
|
{"_unregister_eager_task", _PyCFunction_CAST(_asyncio__unregister_eager_task), METH_FASTCALL|METH_KEYWORDS, _asyncio__unregister_eager_task__doc__},
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
_asyncio__unregister_eager_task_impl(PyObject *module, PyObject *task);
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
_asyncio__unregister_eager_task(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
|
||||||
|
{
|
||||||
|
PyObject *return_value = NULL;
|
||||||
|
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
|
||||||
|
|
||||||
|
#define NUM_KEYWORDS 1
|
||||||
|
static struct {
|
||||||
|
PyGC_Head _this_is_not_used;
|
||||||
|
PyObject_VAR_HEAD
|
||||||
|
PyObject *ob_item[NUM_KEYWORDS];
|
||||||
|
} _kwtuple = {
|
||||||
|
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
|
||||||
|
.ob_item = { &_Py_ID(task), },
|
||||||
|
};
|
||||||
|
#undef NUM_KEYWORDS
|
||||||
|
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
|
||||||
|
|
||||||
|
#else // !Py_BUILD_CORE
|
||||||
|
# define KWTUPLE NULL
|
||||||
|
#endif // !Py_BUILD_CORE
|
||||||
|
|
||||||
|
static const char * const _keywords[] = {"task", NULL};
|
||||||
|
static _PyArg_Parser _parser = {
|
||||||
|
.keywords = _keywords,
|
||||||
|
.fname = "_unregister_eager_task",
|
||||||
|
.kwtuple = KWTUPLE,
|
||||||
|
};
|
||||||
|
#undef KWTUPLE
|
||||||
|
PyObject *argsbuf[1];
|
||||||
|
PyObject *task;
|
||||||
|
|
||||||
|
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 1, 1, 0, argsbuf);
|
||||||
|
if (!args) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
task = args[0];
|
||||||
|
return_value = _asyncio__unregister_eager_task_impl(module, task);
|
||||||
|
|
||||||
|
exit:
|
||||||
|
return return_value;
|
||||||
|
}
|
||||||
|
|
||||||
PyDoc_STRVAR(_asyncio__enter_task__doc__,
|
PyDoc_STRVAR(_asyncio__enter_task__doc__,
|
||||||
"_enter_task($module, /, loop, task)\n"
|
"_enter_task($module, /, loop, task)\n"
|
||||||
"--\n"
|
"--\n"
|
||||||
|
@ -1243,6 +1368,66 @@ exit:
|
||||||
return return_value;
|
return return_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyDoc_STRVAR(_asyncio__swap_current_task__doc__,
|
||||||
|
"_swap_current_task($module, /, loop, task)\n"
|
||||||
|
"--\n"
|
||||||
|
"\n"
|
||||||
|
"Temporarily swap in the supplied task and return the original one (or None).\n"
|
||||||
|
"\n"
|
||||||
|
"This is intended for use during eager coroutine execution.");
|
||||||
|
|
||||||
|
#define _ASYNCIO__SWAP_CURRENT_TASK_METHODDEF \
|
||||||
|
{"_swap_current_task", _PyCFunction_CAST(_asyncio__swap_current_task), METH_FASTCALL|METH_KEYWORDS, _asyncio__swap_current_task__doc__},
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
_asyncio__swap_current_task_impl(PyObject *module, PyObject *loop,
|
||||||
|
PyObject *task);
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
_asyncio__swap_current_task(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
|
||||||
|
{
|
||||||
|
PyObject *return_value = NULL;
|
||||||
|
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
|
||||||
|
|
||||||
|
#define NUM_KEYWORDS 2
|
||||||
|
static struct {
|
||||||
|
PyGC_Head _this_is_not_used;
|
||||||
|
PyObject_VAR_HEAD
|
||||||
|
PyObject *ob_item[NUM_KEYWORDS];
|
||||||
|
} _kwtuple = {
|
||||||
|
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
|
||||||
|
.ob_item = { &_Py_ID(loop), &_Py_ID(task), },
|
||||||
|
};
|
||||||
|
#undef NUM_KEYWORDS
|
||||||
|
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
|
||||||
|
|
||||||
|
#else // !Py_BUILD_CORE
|
||||||
|
# define KWTUPLE NULL
|
||||||
|
#endif // !Py_BUILD_CORE
|
||||||
|
|
||||||
|
static const char * const _keywords[] = {"loop", "task", NULL};
|
||||||
|
static _PyArg_Parser _parser = {
|
||||||
|
.keywords = _keywords,
|
||||||
|
.fname = "_swap_current_task",
|
||||||
|
.kwtuple = KWTUPLE,
|
||||||
|
};
|
||||||
|
#undef KWTUPLE
|
||||||
|
PyObject *argsbuf[2];
|
||||||
|
PyObject *loop;
|
||||||
|
PyObject *task;
|
||||||
|
|
||||||
|
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 2, 2, 0, argsbuf);
|
||||||
|
if (!args) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
loop = args[0];
|
||||||
|
task = args[1];
|
||||||
|
return_value = _asyncio__swap_current_task_impl(module, loop, task);
|
||||||
|
|
||||||
|
exit:
|
||||||
|
return return_value;
|
||||||
|
}
|
||||||
|
|
||||||
PyDoc_STRVAR(_asyncio_current_task__doc__,
|
PyDoc_STRVAR(_asyncio_current_task__doc__,
|
||||||
"current_task($module, /, loop=None)\n"
|
"current_task($module, /, loop=None)\n"
|
||||||
"--\n"
|
"--\n"
|
||||||
|
@ -1302,4 +1487,4 @@ skip_optional_pos:
|
||||||
exit:
|
exit:
|
||||||
return return_value;
|
return return_value;
|
||||||
}
|
}
|
||||||
/*[clinic end generated code: output=00f494214f2fd008 input=a9049054013a1b77]*/
|
/*[clinic end generated code: output=6b0e283177b07639 input=a9049054013a1b77]*/
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue