From 19204e1e3f4a55a1456805fd79e8c9d9f8fabc77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Sun, 29 Jul 2018 12:39:33 +0100 Subject: [PATCH 01/22] bpo-34270: added the "name" attribute to asyncio tasks The ability to name individual tasks helps developers debug complex asyncio applications. Co-authored-by: Antti Haapala --- Doc/library/asyncio-eventloop.rst | 11 ++++- Doc/library/asyncio-task.rst | 24 ++++++++-- Lib/asyncio/base_events.py | 6 ++- Lib/asyncio/base_tasks.py | 6 ++- Lib/asyncio/events.py | 2 +- Lib/asyncio/tasks.py | 20 ++++++-- Lib/test/test_asyncio/test_tasks.py | 45 +++++++++++++++--- Misc/ACKS | 2 + .../2018-07-29-11-32-56.bpo-34270.aL6P-3.rst | 2 + Modules/_asynciomodule.c | 47 ++++++++++++++++++- Modules/clinic/_asynciomodule.c.h | 16 ++++--- 11 files changed, 152 insertions(+), 29 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2018-07-29-11-32-56.bpo-34270.aL6P-3.rst diff --git a/Doc/library/asyncio-eventloop.rst b/Doc/library/asyncio-eventloop.rst index 317f3fb85c54811..0cf97fa46e31ecd 100644 --- a/Doc/library/asyncio-eventloop.rst +++ b/Doc/library/asyncio-eventloop.rst @@ -250,7 +250,7 @@ Futures Tasks ----- -.. method:: AbstractEventLoop.create_task(coro) +.. method:: AbstractEventLoop.create_task(coro, \*, name=None) Schedule the execution of a :ref:`coroutine object `: wrap it in a future. Return a :class:`Task` object. @@ -259,8 +259,17 @@ Tasks interoperability. In this case, the result type is a subclass of :class:`Task`. + Parameters: + + * *name* is a descriptive name for the task. If no explicit name is + supplied, the event loop may assign the task an automatically + generated name. + .. versionadded:: 3.4.2 + .. versionchanged:: 3.8 + Added the ``name`` parameter. + .. method:: AbstractEventLoop.set_task_factory(factory) Set a task factory that will be used by diff --git a/Doc/library/asyncio-task.rst b/Doc/library/asyncio-task.rst index 2b480d4be3fb21f..1b711eb1ac811e7 100644 --- a/Doc/library/asyncio-task.rst +++ b/Doc/library/asyncio-task.rst @@ -387,18 +387,27 @@ with the result. Task ---- -.. function:: create_task(coro) +.. function:: create_task(coro, \*, name=None) Wrap a :ref:`coroutine ` *coro* into a task and schedule - its execution. Return the task object. + its execution. Return the task object. The task is executed in :func:`get_running_loop` context, :exc:`RuntimeError` is raised if there is no running loop in current thread. + Parameters: + + * *name* is a descriptive name for the task. If no explicit name is + supplied, the event loop may assign the task an automatically + generated name. + .. versionadded:: 3.7 -.. class:: Task(coro, \*, loop=None) + .. versionchanged:: 3.8 + Added the ``name`` parameter. + +.. class:: Task(coro, \*, loop=None, name=None) A unit for concurrent running of :ref:`coroutines `, subclass of :class:`Future`. @@ -438,6 +447,9 @@ Task .. versionchanged:: 3.7 Added support for the :mod:`contextvars` module. + .. versionchanged:: 3.8 + Added the ``name`` argument. + .. classmethod:: all_tasks(loop=None) Return a set of all tasks for an event loop. @@ -504,6 +516,12 @@ Task get_stack(). The file argument is an I/O stream to which the output is written; by default output is written to sys.stderr. + .. attribute:: name + + Descriptive name of the task. + + .. versionadded:: 3.8 + Example: Parallel execution of tasks ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py index dc0ca3f02b9bf63..d937aff09f981e7 100644 --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -381,16 +381,18 @@ def create_future(self): """Create a Future object attached to the loop.""" return futures.Future(loop=self) - def create_task(self, coro): + def create_task(self, coro, *, name=None): """Schedule a coroutine object. Return a task object. """ self._check_closed() if self._task_factory is None: - task = tasks.Task(coro, loop=self) + task = tasks.Task(coro, loop=self, name=name) if task._source_traceback: del task._source_traceback[-1] + elif name is not None: + task = self._task_factory(self, coro, name=name) else: task = self._task_factory(self, coro) return task diff --git a/Lib/asyncio/base_tasks.py b/Lib/asyncio/base_tasks.py index 3ce51f6a9866e4d..c4d91a8b63fd6ad 100644 --- a/Lib/asyncio/base_tasks.py +++ b/Lib/asyncio/base_tasks.py @@ -12,11 +12,13 @@ def _task_repr_info(task): # replace status info[0] = 'cancelling' + info.insert(1, 'name=%r' % task.name) + coro = coroutines._format_coroutine(task._coro) - info.insert(1, f'coro=<{coro}>') + info.insert(2, f'coro=<{coro}>') if task._fut_waiter is not None: - info.insert(2, f'wait_for={task._fut_waiter!r}') + info.insert(3, f'wait_for={task._fut_waiter!r}') return info diff --git a/Lib/asyncio/events.py b/Lib/asyncio/events.py index e4e632206af1bc5..58a60a0b16a06a5 100644 --- a/Lib/asyncio/events.py +++ b/Lib/asyncio/events.py @@ -277,7 +277,7 @@ def create_future(self): # Method scheduling a coroutine object: create a task. - def create_task(self, coro): + def create_task(self, coro, *, name=None): raise NotImplementedError # Methods for interacting with threads. diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index 72792a25cf55acc..70dcaae72e99688 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -1,4 +1,5 @@ """Support for tasks, coroutines and the scheduler.""" +import itertools __all__ = ( 'Task', 'create_task', @@ -23,6 +24,9 @@ from . import futures from .coroutines import coroutine +# Helper to generate new task names +_counter = itertools.count(1).__next__ + def current_task(loop=None): """Return a currently executed task.""" @@ -94,7 +98,7 @@ def all_tasks(cls, loop=None): stacklevel=2) return _all_tasks_compat(loop) - def __init__(self, coro, *, loop=None): + def __init__(self, coro, *, loop=None, name=None): super().__init__(loop=loop) if self._source_traceback: del self._source_traceback[-1] @@ -104,6 +108,7 @@ def __init__(self, coro, *, loop=None): self._log_destroy_pending = False raise TypeError(f"a coroutine was expected, got {coro!r}") + self._name = str(name) if name is not None else 'Task-%s' % _counter() self._must_cancel = False self._fut_waiter = None self._coro = coro @@ -126,6 +131,14 @@ def __del__(self): def _repr_info(self): return base_tasks._task_repr_info(self) + @property + def name(self): + return self._name + + @name.setter + def name(self, value): + self._name = str(value) + def set_result(self, result): raise RuntimeError('Task does not support set_result operation') @@ -299,7 +312,6 @@ def __wakeup(self, future): self.__step() self = None # Needed to break cycles when an exception occurs. - _PyTask = Task @@ -312,13 +324,13 @@ def __wakeup(self, future): Task = _CTask = _asyncio.Task -def create_task(coro): +def create_task(coro, *, name=None): """Schedule the execution of a coroutine object in a spawn task. Return a Task object. """ loop = events.get_running_loop() - return loop.create_task(coro) + return loop.create_task(coro, name=name) # wait() and as_completed() similar to those in PEP 3148. diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py index a5442f5fdfff8fe..d882ebcb05dc077 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -87,8 +87,8 @@ class BaseTaskTests: Task = None Future = None - def new_task(self, loop, coro): - return self.__class__.Task(coro, loop=loop) + def new_task(self, loop, coro, name='TestTask'): + return self.__class__.Task(coro, loop=loop, name=name) def new_future(self, loop): return self.__class__.Future(loop=loop) @@ -295,12 +295,12 @@ def notmuch(): coro = format_coroutine(coro_qualname, 'running', src, t._source_traceback, generator=True) self.assertEqual(repr(t), - '()]>' % coro) + "()]>" % coro) # test cancelling Task t.cancel() # Does not take immediate effect! self.assertEqual(repr(t), - '()]>' % coro) + "()]>" % coro) # test cancelled Task self.assertRaises(asyncio.CancelledError, @@ -308,7 +308,7 @@ def notmuch(): coro = format_coroutine(coro_qualname, 'done', src, t._source_traceback) self.assertEqual(repr(t), - '' % coro) + "" % coro) # test finished Task t = self.new_task(self.loop, notmuch()) @@ -316,7 +316,38 @@ def notmuch(): coro = format_coroutine(coro_qualname, 'done', src, t._source_traceback) self.assertEqual(repr(t), - "" % coro) + "" % coro) + + def test_task_repr_autogenerated(self): + @asyncio.coroutine + def notmuch(): + return 123 + + t1 = self.new_task(self.loop, notmuch(), name=None) + t2 = self.new_task(self.loop, notmuch(), name=None) + self.assertNotEqual(repr(t1), repr(t2)) + + match1 = re.match("^()]>' % coro) + "()]>" % coro) self.loop.run_until_complete(t) def test_task_repr_wait_for(self): diff --git a/Misc/ACKS b/Misc/ACKS index 2cf5e10dd141db4..05a5425fec9fe67 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -573,6 +573,7 @@ Elliot Gorokhovsky Hans de Graaff Tim Graham Kim Gräsman +Alex Grönholm Nathaniel Gray Eddy De Greef Duane Griffin @@ -594,6 +595,7 @@ Michael Guravage Lars Gustäbel Thomas Güttler Jonas H. +Antti Haapala Joseph Hackman Barry Haddow Philipp Hagemeister diff --git a/Misc/NEWS.d/next/Library/2018-07-29-11-32-56.bpo-34270.aL6P-3.rst b/Misc/NEWS.d/next/Library/2018-07-29-11-32-56.bpo-34270.aL6P-3.rst new file mode 100644 index 000000000000000..a26491c6e78e359 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-07-29-11-32-56.bpo-34270.aL6P-3.rst @@ -0,0 +1,2 @@ +Asyncio tasks now have a ``name`` attribute which can also be explicitly set +using a keyword argument to ``asyncio.create_task()``. diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index ebda10404eb5dfd..2bf0229ca5ae83a 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -37,6 +37,8 @@ static PyObject *context_kwname; static PyObject *cached_running_holder; static volatile uint64_t cached_running_holder_tsid; +/* Counter for autogenerated Task names */ +static unsigned long long task_name_counter = 0; /* WeakSet containing all alive tasks. */ static PyObject *all_tasks; @@ -78,6 +80,7 @@ typedef struct { FutureObj_HEAD(task) PyObject *task_fut_waiter; PyObject *task_coro; + PyObject *task_name; PyContext *task_context; int task_must_cancel; int task_log_destroy_pending; @@ -1934,13 +1937,15 @@ _asyncio.Task.__init__ coro: object * loop: object = None + name: object = None A coroutine wrapped in a Future. [clinic start generated code]*/ static int -_asyncio_Task___init___impl(TaskObj *self, PyObject *coro, PyObject *loop) -/*[clinic end generated code: output=9f24774c2287fc2f input=8d132974b049593e]*/ +_asyncio_Task___init___impl(TaskObj *self, PyObject *coro, PyObject *loop, + PyObject *name) +/*[clinic end generated code: output=88b12b83d570df50 input=352a3137fe60091d]*/ { if (future_init((FutureObj*)self, loop)) { return -1; @@ -1969,6 +1974,17 @@ _asyncio_Task___init___impl(TaskObj *self, PyObject *coro, PyObject *loop) Py_INCREF(coro); Py_XSETREF(self->task_coro, coro); + if (name == Py_None) { + name = PyUnicode_FromFormat("Task-%llu", ++task_name_counter); + } else { + name = PyObject_Str(name); + } + + Py_XSETREF(self->task_name, name); + if (self->task_name == NULL) { + return -1; + } + if (task_call_step_soon(self, NULL)) { return -1; } @@ -1981,6 +1997,7 @@ TaskObj_clear(TaskObj *task) (void)FutureObj_clear((FutureObj*) task); Py_CLEAR(task->task_context); Py_CLEAR(task->task_coro); + Py_CLEAR(task->task_name); Py_CLEAR(task->task_fut_waiter); return 0; } @@ -1990,6 +2007,7 @@ TaskObj_traverse(TaskObj *task, visitproc visit, void *arg) { Py_VISIT(task->task_context); Py_VISIT(task->task_coro); + Py_VISIT(task->task_name); Py_VISIT(task->task_fut_waiter); (void)FutureObj_traverse((FutureObj*) task, visit, arg); return 0; @@ -2039,6 +2057,29 @@ TaskObj_get_coro(TaskObj *task) Py_RETURN_NONE; } +static PyObject * +TaskObj_get_name(TaskObj *task) +{ + if (task->task_name) { + Py_INCREF(task->task_name); + return task->task_name; + } + + Py_RETURN_NONE; +} + +static int +TaskObj_set_name(TaskObj *task, PyObject *value) +{ + PyObject *name = PyObject_Str(value); + if (name == NULL) { + return -1; + } + + Py_XSETREF(task->task_name, name); + return 0; +} + static PyObject * TaskObj_get_fut_waiter(TaskObj *task) { @@ -2391,6 +2432,8 @@ static PyGetSetDef TaskType_getsetlist[] = { (setter)TaskObj_set_log_destroy_pending, NULL}, {"_must_cancel", (getter)TaskObj_get_must_cancel, NULL, NULL}, {"_coro", (getter)TaskObj_get_coro, NULL, NULL}, + {"name", (getter)TaskObj_get_name, + (setter)TaskObj_set_name, NULL}, {"_fut_waiter", (getter)TaskObj_get_fut_waiter, NULL, NULL}, {NULL} /* Sentinel */ }; diff --git a/Modules/clinic/_asynciomodule.c.h b/Modules/clinic/_asynciomodule.c.h index d8e1b6a7987956b..cbb536ff2aa3ab2 100644 --- a/Modules/clinic/_asynciomodule.c.h +++ b/Modules/clinic/_asynciomodule.c.h @@ -253,28 +253,30 @@ _asyncio_Future__repr_info(FutureObj *self, PyObject *Py_UNUSED(ignored)) } PyDoc_STRVAR(_asyncio_Task___init____doc__, -"Task(coro, *, loop=None)\n" +"Task(coro, *, loop=None, name=None)\n" "--\n" "\n" "A coroutine wrapped in a Future."); static int -_asyncio_Task___init___impl(TaskObj *self, PyObject *coro, PyObject *loop); +_asyncio_Task___init___impl(TaskObj *self, PyObject *coro, PyObject *loop, + PyObject *name); static int _asyncio_Task___init__(PyObject *self, PyObject *args, PyObject *kwargs) { int return_value = -1; - static const char * const _keywords[] = {"coro", "loop", NULL}; - static _PyArg_Parser _parser = {"O|$O:Task", _keywords, 0}; + static const char * const _keywords[] = {"coro", "loop", "name", NULL}; + static _PyArg_Parser _parser = {"O|$OO:Task", _keywords, 0}; PyObject *coro; PyObject *loop = Py_None; + PyObject *name = Py_None; if (!_PyArg_ParseTupleAndKeywordsFast(args, kwargs, &_parser, - &coro, &loop)) { + &coro, &loop, &name)) { goto exit; } - return_value = _asyncio_Task___init___impl((TaskObj *)self, coro, loop); + return_value = _asyncio_Task___init___impl((TaskObj *)self, coro, loop, name); exit: return return_value; @@ -711,4 +713,4 @@ _asyncio__leave_task(PyObject *module, PyObject *const *args, Py_ssize_t nargs, exit: return return_value; } -/*[clinic end generated code: output=b6148b0134e7a819 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=3492ad76f8831855 input=a9049054013a1b77]*/ From 25939b9b8465560f30102c69e68aa94d8ce77669 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Mon, 30 Jul 2018 19:01:20 +0100 Subject: [PATCH 02/22] Made changes requested by the reviewers * Removed the public *name* property * Added the *Task.get_name()* method * Modified the documentation of *set_task_factory()* to include the *name* parameter * Whitespace changes --- Doc/library/asyncio-eventloop.rst | 8 ++++-- Doc/library/asyncio-task.rst | 4 +-- Lib/asyncio/base_tasks.py | 2 +- Lib/asyncio/tasks.py | 15 +++++----- Lib/test/test_asyncio/test_tasks.py | 5 +--- Modules/_asynciomodule.c | 44 ++++++++++++----------------- Modules/clinic/_asynciomodule.c.h | 19 ++++++++++++- 7 files changed, 52 insertions(+), 45 deletions(-) diff --git a/Doc/library/asyncio-eventloop.rst b/Doc/library/asyncio-eventloop.rst index 0cf97fa46e31ecd..889e3cd0cbd2df7 100644 --- a/Doc/library/asyncio-eventloop.rst +++ b/Doc/library/asyncio-eventloop.rst @@ -278,9 +278,11 @@ Tasks If *factory* is ``None`` the default task factory will be set. If *factory* is a *callable*, it should have a signature matching - ``(loop, coro)``, where *loop* will be a reference to the active - event loop, *coro* will be a coroutine object. The callable - must return an :class:`asyncio.Future` compatible object. + ``(loop, coro, name=None)``, where *loop* will be a reference to the active + event loop, *coro* will be a coroutine object and *name* will be the + descriptive name of the task. If *name* is ``None``, the factory must + provide an automatically generated name for the task. The callable must + return an :class:`asyncio.Future` compatible object. .. versionadded:: 3.4.4 diff --git a/Doc/library/asyncio-task.rst b/Doc/library/asyncio-task.rst index 1b711eb1ac811e7..40449eeaa232e6e 100644 --- a/Doc/library/asyncio-task.rst +++ b/Doc/library/asyncio-task.rst @@ -516,9 +516,9 @@ Task get_stack(). The file argument is an I/O stream to which the output is written; by default output is written to sys.stderr. - .. attribute:: name + .. method:: get_name() - Descriptive name of the task. + Return the name of the task. .. versionadded:: 3.8 diff --git a/Lib/asyncio/base_tasks.py b/Lib/asyncio/base_tasks.py index c4d91a8b63fd6ad..e2da462fde74005 100644 --- a/Lib/asyncio/base_tasks.py +++ b/Lib/asyncio/base_tasks.py @@ -12,7 +12,7 @@ def _task_repr_info(task): # replace status info[0] = 'cancelling' - info.insert(1, 'name=%r' % task.name) + info.insert(1, 'name=%r' % task.get_name()) coro = coroutines._format_coroutine(task._coro) info.insert(2, f'coro=<{coro}>') diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index 70dcaae72e99688..f6904b40e128ddb 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -25,7 +25,7 @@ from .coroutines import coroutine # Helper to generate new task names -_counter = itertools.count(1).__next__ +_task_name_counter = itertools.count(1).__next__ def current_task(loop=None): @@ -108,7 +108,11 @@ def __init__(self, coro, *, loop=None, name=None): self._log_destroy_pending = False raise TypeError(f"a coroutine was expected, got {coro!r}") - self._name = str(name) if name is not None else 'Task-%s' % _counter() + if name is not None: + self._name = str(name) + else: + self._name = 'Task-%s' % _task_name_counter() + self._must_cancel = False self._fut_waiter = None self._coro = coro @@ -131,14 +135,9 @@ def __del__(self): def _repr_info(self): return base_tasks._task_repr_info(self) - @property - def name(self): + def get_name(self): return self._name - @name.setter - def name(self, value): - self._name = str(value) - def set_result(self, result): raise RuntimeError('Task does not support set_result operation') diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py index d882ebcb05dc077..c30b648b983a747 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -343,10 +343,7 @@ def notmuch(): return 123 t = self.new_task(self.loop, notmuch(), name={6}) - self.assertEqual(t.name, '{6}') - - t.name = None - self.assertEqual(t.name, 'None') + self.assertEqual(t.get_name(), '{6}') self.loop.run_until_complete(t) def test_task_repr_coro_decorator(self): diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 2bf0229ca5ae83a..5e1f7efd23baa9d 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -1979,7 +1979,6 @@ _asyncio_Task___init___impl(TaskObj *self, PyObject *coro, PyObject *loop, } else { name = PyObject_Str(name); } - Py_XSETREF(self->task_name, name); if (self->task_name == NULL) { return -1; @@ -2057,29 +2056,6 @@ TaskObj_get_coro(TaskObj *task) Py_RETURN_NONE; } -static PyObject * -TaskObj_get_name(TaskObj *task) -{ - if (task->task_name) { - Py_INCREF(task->task_name); - return task->task_name; - } - - Py_RETURN_NONE; -} - -static int -TaskObj_set_name(TaskObj *task, PyObject *value) -{ - PyObject *name = PyObject_Str(value); - if (name == NULL) { - return -1; - } - - Py_XSETREF(task->task_name, name); - return 0; -} - static PyObject * TaskObj_get_fut_waiter(TaskObj *task) { @@ -2338,6 +2314,23 @@ _asyncio_Task_set_exception(TaskObj *self, PyObject *exception) return NULL; } +/*[clinic input] +_asyncio.Task.get_name +[clinic start generated code]*/ + +static PyObject * +_asyncio_Task_get_name_impl(TaskObj *self) +/*[clinic end generated code: output=0ecf1570c3b37a8f input=a4a6595d12f4f0f8]*/ + +{ + if (self->task_name) { + Py_INCREF(self->task_name); + return self->task_name; + } + + Py_RETURN_NONE; +} + static void TaskObj_finalize(TaskObj *task) @@ -2423,6 +2416,7 @@ static PyMethodDef TaskType_methods[] = { _ASYNCIO_TASK_GET_STACK_METHODDEF _ASYNCIO_TASK_PRINT_STACK_METHODDEF _ASYNCIO_TASK__REPR_INFO_METHODDEF + _ASYNCIO_TASK_GET_NAME_METHODDEF {NULL, NULL} /* Sentinel */ }; @@ -2432,8 +2426,6 @@ static PyGetSetDef TaskType_getsetlist[] = { (setter)TaskObj_set_log_destroy_pending, NULL}, {"_must_cancel", (getter)TaskObj_get_must_cancel, NULL, NULL}, {"_coro", (getter)TaskObj_get_coro, NULL, NULL}, - {"name", (getter)TaskObj_get_name, - (setter)TaskObj_set_name, NULL}, {"_fut_waiter", (getter)TaskObj_get_fut_waiter, NULL, NULL}, {NULL} /* Sentinel */ }; diff --git a/Modules/clinic/_asynciomodule.c.h b/Modules/clinic/_asynciomodule.c.h index cbb536ff2aa3ab2..3377f363d6e324f 100644 --- a/Modules/clinic/_asynciomodule.c.h +++ b/Modules/clinic/_asynciomodule.c.h @@ -502,6 +502,23 @@ PyDoc_STRVAR(_asyncio_Task_set_exception__doc__, #define _ASYNCIO_TASK_SET_EXCEPTION_METHODDEF \ {"set_exception", (PyCFunction)_asyncio_Task_set_exception, METH_O, _asyncio_Task_set_exception__doc__}, +PyDoc_STRVAR(_asyncio_Task_get_name__doc__, +"get_name($self, /)\n" +"--\n" +"\n"); + +#define _ASYNCIO_TASK_GET_NAME_METHODDEF \ + {"get_name", (PyCFunction)_asyncio_Task_get_name, METH_NOARGS, _asyncio_Task_get_name__doc__}, + +static PyObject * +_asyncio_Task_get_name_impl(TaskObj *self); + +static PyObject * +_asyncio_Task_get_name(TaskObj *self, PyObject *Py_UNUSED(ignored)) +{ + return _asyncio_Task_get_name_impl(self); +} + PyDoc_STRVAR(_asyncio__get_running_loop__doc__, "_get_running_loop($module, /)\n" "--\n" @@ -713,4 +730,4 @@ _asyncio__leave_task(PyObject *module, PyObject *const *args, Py_ssize_t nargs, exit: return return_value; } -/*[clinic end generated code: output=3492ad76f8831855 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=66f45b8456e9ddc2 input=a9049054013a1b77]*/ From 369eba9f1d72186a28c0bd1d223405bdeb05d99c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Mon, 30 Jul 2018 19:17:23 +0100 Subject: [PATCH 03/22] Added whatsnew entry --- Doc/whatsnew/3.8.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index 3f515357e953fa2..ef0c21ea7c2e164 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -243,6 +243,11 @@ Changes in the Python API * ``PyGC_Head`` struct is changed completely. All code touched the struct member should be rewritten. (See :issue:`33597`) +* An optional keyword-only argument, *name*, was added to + :func:`asyncio.create_task` and :meth:`asyncio.AbstractEventLoop.create_task`. + The API for task factories was also changed accordingly to allow this argument + to be passed. + CPython bytecode changes ------------------------ From ce8e014ad4c973acfb54d3d416e1a814a75c4997 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Mon, 30 Jul 2018 20:30:17 +0100 Subject: [PATCH 04/22] Changed unsigned long long to uint64_t --- Modules/_asynciomodule.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 5e1f7efd23baa9d..beff0074c4ca829 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -38,7 +38,7 @@ static PyObject *cached_running_holder; static volatile uint64_t cached_running_holder_tsid; /* Counter for autogenerated Task names */ -static unsigned long long task_name_counter = 0; +static uint64_t task_name_counter = 0; /* WeakSet containing all alive tasks. */ static PyObject *all_tasks; @@ -1975,7 +1975,7 @@ _asyncio_Task___init___impl(TaskObj *self, PyObject *coro, PyObject *loop, Py_XSETREF(self->task_coro, coro); if (name == Py_None) { - name = PyUnicode_FromFormat("Task-%llu", ++task_name_counter); + name = PyUnicode_FromFormat("Task-%" PRIu64, ++task_name_counter); } else { name = PyObject_Str(name); } From 11258ede51e14d4a654ec0fb8010356a3c70629a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Mon, 30 Jul 2018 20:50:10 +0100 Subject: [PATCH 05/22] Added explanation for the use of itertools.count() in asyncio.tasks --- Lib/asyncio/tasks.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index f6904b40e128ddb..93ef7b25b06c035 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -25,6 +25,8 @@ from .coroutines import coroutine # Helper to generate new task names +# This uses itertools.count() instead of a "+= 1" operation because the latter +# is not thread safe. See bpo-11866 for a longer explanation. _task_name_counter = itertools.count(1).__next__ From 1f3dda65ac87abcaa370c8751b820a69da6323e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Wed, 1 Aug 2018 19:30:49 +0300 Subject: [PATCH 06/22] Reverted task factory API changes and added Task.set_name() --- Doc/library/asyncio-eventloop.rst | 19 +++----------- Doc/library/asyncio-task.rst | 31 ++++++++++++----------- Lib/asyncio/base_events.py | 6 ++--- Lib/asyncio/events.py | 2 +- Lib/asyncio/tasks.py | 16 ++++++------ Lib/test/test_asyncio/test_tasks.py | 13 +++++++--- Modules/_asynciomodule.c | 39 +++++++++++++++++++---------- Modules/clinic/_asynciomodule.c.h | 24 +++++++++++------- 8 files changed, 82 insertions(+), 68 deletions(-) diff --git a/Doc/library/asyncio-eventloop.rst b/Doc/library/asyncio-eventloop.rst index 889e3cd0cbd2df7..317f3fb85c54811 100644 --- a/Doc/library/asyncio-eventloop.rst +++ b/Doc/library/asyncio-eventloop.rst @@ -250,7 +250,7 @@ Futures Tasks ----- -.. method:: AbstractEventLoop.create_task(coro, \*, name=None) +.. method:: AbstractEventLoop.create_task(coro) Schedule the execution of a :ref:`coroutine object `: wrap it in a future. Return a :class:`Task` object. @@ -259,17 +259,8 @@ Tasks interoperability. In this case, the result type is a subclass of :class:`Task`. - Parameters: - - * *name* is a descriptive name for the task. If no explicit name is - supplied, the event loop may assign the task an automatically - generated name. - .. versionadded:: 3.4.2 - .. versionchanged:: 3.8 - Added the ``name`` parameter. - .. method:: AbstractEventLoop.set_task_factory(factory) Set a task factory that will be used by @@ -278,11 +269,9 @@ Tasks If *factory* is ``None`` the default task factory will be set. If *factory* is a *callable*, it should have a signature matching - ``(loop, coro, name=None)``, where *loop* will be a reference to the active - event loop, *coro* will be a coroutine object and *name* will be the - descriptive name of the task. If *name* is ``None``, the factory must - provide an automatically generated name for the task. The callable must - return an :class:`asyncio.Future` compatible object. + ``(loop, coro)``, where *loop* will be a reference to the active + event loop, *coro* will be a coroutine object. The callable + must return an :class:`asyncio.Future` compatible object. .. versionadded:: 3.4.4 diff --git a/Doc/library/asyncio-task.rst b/Doc/library/asyncio-task.rst index 40449eeaa232e6e..1a9462c2a113613 100644 --- a/Doc/library/asyncio-task.rst +++ b/Doc/library/asyncio-task.rst @@ -387,7 +387,7 @@ with the result. Task ---- -.. function:: create_task(coro, \*, name=None) +.. function:: create_task(coro) Wrap a :ref:`coroutine ` *coro* into a task and schedule its execution. Return the task object. @@ -396,18 +396,9 @@ Task :exc:`RuntimeError` is raised if there is no running loop in current thread. - Parameters: - - * *name* is a descriptive name for the task. If no explicit name is - supplied, the event loop may assign the task an automatically - generated name. - .. versionadded:: 3.7 - .. versionchanged:: 3.8 - Added the ``name`` parameter. - -.. class:: Task(coro, \*, loop=None, name=None) +.. class:: Task(coro, \*, loop=None) A unit for concurrent running of :ref:`coroutines `, subclass of :class:`Future`. @@ -447,9 +438,6 @@ Task .. versionchanged:: 3.7 Added support for the :mod:`contextvars` module. - .. versionchanged:: 3.8 - Added the ``name`` argument. - .. classmethod:: all_tasks(loop=None) Return a set of all tasks for an event loop. @@ -520,6 +508,21 @@ Task Return the name of the task. + If no name has been explicitly assigned to the task, the default + ``Task`` implementation generates a default name during instantiation. + + .. versionadded:: 3.8 + + .. method:: set_name(value) + + Set the name of the task. + + The *value* argument can be any object, which is then converted to a + string. + + In the default Task implementation, the name will be visible in the + :func:`repr` output of a task object. + .. versionadded:: 3.8 diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py index d937aff09f981e7..dc0ca3f02b9bf63 100644 --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -381,18 +381,16 @@ def create_future(self): """Create a Future object attached to the loop.""" return futures.Future(loop=self) - def create_task(self, coro, *, name=None): + def create_task(self, coro): """Schedule a coroutine object. Return a task object. """ self._check_closed() if self._task_factory is None: - task = tasks.Task(coro, loop=self, name=name) + task = tasks.Task(coro, loop=self) if task._source_traceback: del task._source_traceback[-1] - elif name is not None: - task = self._task_factory(self, coro, name=name) else: task = self._task_factory(self, coro) return task diff --git a/Lib/asyncio/events.py b/Lib/asyncio/events.py index 58a60a0b16a06a5..e4e632206af1bc5 100644 --- a/Lib/asyncio/events.py +++ b/Lib/asyncio/events.py @@ -277,7 +277,7 @@ def create_future(self): # Method scheduling a coroutine object: create a task. - def create_task(self, coro, *, name=None): + def create_task(self, coro): raise NotImplementedError # Methods for interacting with threads. diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index 93ef7b25b06c035..370ad35d2754956 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -100,7 +100,7 @@ def all_tasks(cls, loop=None): stacklevel=2) return _all_tasks_compat(loop) - def __init__(self, coro, *, loop=None, name=None): + def __init__(self, coro, *, loop=None): super().__init__(loop=loop) if self._source_traceback: del self._source_traceback[-1] @@ -110,11 +110,7 @@ def __init__(self, coro, *, loop=None, name=None): self._log_destroy_pending = False raise TypeError(f"a coroutine was expected, got {coro!r}") - if name is not None: - self._name = str(name) - else: - self._name = 'Task-%s' % _task_name_counter() - + self._name = 'Task-%s' % _task_name_counter() self._must_cancel = False self._fut_waiter = None self._coro = coro @@ -140,6 +136,9 @@ def _repr_info(self): def get_name(self): return self._name + def set_name(self, value): + self._name = str(value) + def set_result(self, result): raise RuntimeError('Task does not support set_result operation') @@ -313,6 +312,7 @@ def __wakeup(self, future): self.__step() self = None # Needed to break cycles when an exception occurs. + _PyTask = Task @@ -325,13 +325,13 @@ def __wakeup(self, future): Task = _CTask = _asyncio.Task -def create_task(coro, *, name=None): +def create_task(coro): """Schedule the execution of a coroutine object in a spawn task. Return a Task object. """ loop = events.get_running_loop() - return loop.create_task(coro, name=name) + return loop.create_task(coro) # wait() and as_completed() similar to those in PEP 3148. diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py index c30b648b983a747..ed32f61e8b9e42e 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -88,7 +88,11 @@ class BaseTaskTests: Future = None def new_task(self, loop, coro, name='TestTask'): - return self.__class__.Task(coro, loop=loop, name=name) + t = self.__class__.Task(coro, loop=loop) + if name is not None: + t.set_name(name) + + return t def new_future(self, loop): return self.__class__.Future(loop=loop) @@ -323,8 +327,8 @@ def test_task_repr_autogenerated(self): def notmuch(): return 123 - t1 = self.new_task(self.loop, notmuch(), name=None) - t2 = self.new_task(self.loop, notmuch(), name=None) + t1 = self.new_task(self.loop, notmuch(), None) + t2 = self.new_task(self.loop, notmuch(), None) self.assertNotEqual(repr(t1), repr(t2)) match1 = re.match("^task_coro, coro); - if (name == Py_None) { - name = PyUnicode_FromFormat("Task-%" PRIu64, ++task_name_counter); - } else { - name = PyObject_Str(name); - } - Py_XSETREF(self->task_name, name); - if (self->task_name == NULL) { + PyObject *name = PyUnicode_FromFormat("Task-%" PRIu64, ++task_name_counter); + if (name == NULL) { return -1; } + Py_XSETREF(self->task_name, name); if (task_call_step_soon(self, NULL)) { return -1; @@ -2319,9 +2313,8 @@ _asyncio.Task.get_name [clinic start generated code]*/ static PyObject * -_asyncio_Task_get_name_impl(TaskObj *self) +_asyncio_Task_get_name(TaskObj *self) /*[clinic end generated code: output=0ecf1570c3b37a8f input=a4a6595d12f4f0f8]*/ - { if (self->task_name) { Py_INCREF(self->task_name); @@ -2331,6 +2324,25 @@ _asyncio_Task_get_name_impl(TaskObj *self) Py_RETURN_NONE; } +/*[clinic input] +_asyncio.Task.set_name + + value: object + / +[clinic start generated code]*/ + +static PyObject * +_asyncio_Task_set_name(TaskObj *self, PyObject *value) +/*[clinic end generated code: output=138a8d51e32057d6 input=a8359b6e65f8fd31]*/ +{ + PyObject *name = PyObject_Str(value); + if (name == NULL) { + return -1; + } + + Py_XSETREF(self->task_name, name); + Py_RETURN_NONE; +} static void TaskObj_finalize(TaskObj *task) @@ -2417,6 +2429,7 @@ static PyMethodDef TaskType_methods[] = { _ASYNCIO_TASK_PRINT_STACK_METHODDEF _ASYNCIO_TASK__REPR_INFO_METHODDEF _ASYNCIO_TASK_GET_NAME_METHODDEF + _ASYNCIO_TASK_SET_NAME_METHODDEF {NULL, NULL} /* Sentinel */ }; diff --git a/Modules/clinic/_asynciomodule.c.h b/Modules/clinic/_asynciomodule.c.h index 3377f363d6e324f..b5d1f6f529ed23d 100644 --- a/Modules/clinic/_asynciomodule.c.h +++ b/Modules/clinic/_asynciomodule.c.h @@ -253,30 +253,28 @@ _asyncio_Future__repr_info(FutureObj *self, PyObject *Py_UNUSED(ignored)) } PyDoc_STRVAR(_asyncio_Task___init____doc__, -"Task(coro, *, loop=None, name=None)\n" +"Task(coro, *, loop=None)\n" "--\n" "\n" "A coroutine wrapped in a Future."); static int -_asyncio_Task___init___impl(TaskObj *self, PyObject *coro, PyObject *loop, - PyObject *name); +_asyncio_Task___init___impl(TaskObj *self, PyObject *coro, PyObject *loop); static int _asyncio_Task___init__(PyObject *self, PyObject *args, PyObject *kwargs) { int return_value = -1; - static const char * const _keywords[] = {"coro", "loop", "name", NULL}; - static _PyArg_Parser _parser = {"O|$OO:Task", _keywords, 0}; + static const char * const _keywords[] = {"coro", "loop", NULL}; + static _PyArg_Parser _parser = {"O|$O:Task", _keywords, 0}; PyObject *coro; PyObject *loop = Py_None; - PyObject *name = Py_None; if (!_PyArg_ParseTupleAndKeywordsFast(args, kwargs, &_parser, - &coro, &loop, &name)) { + &coro, &loop)) { goto exit; } - return_value = _asyncio_Task___init___impl((TaskObj *)self, coro, loop, name); + return_value = _asyncio_Task___init___impl((TaskObj *)self, coro, loop); exit: return return_value; @@ -519,6 +517,14 @@ _asyncio_Task_get_name(TaskObj *self, PyObject *Py_UNUSED(ignored)) return _asyncio_Task_get_name_impl(self); } +PyDoc_STRVAR(_asyncio_Task_set_name__doc__, +"set_name($self, value, /)\n" +"--\n" +"\n"); + +#define _ASYNCIO_TASK_SET_NAME_METHODDEF \ + {"set_name", (PyCFunction)_asyncio_Task_set_name, METH_O, _asyncio_Task_set_name__doc__}, + PyDoc_STRVAR(_asyncio__get_running_loop__doc__, "_get_running_loop($module, /)\n" "--\n" @@ -730,4 +736,4 @@ _asyncio__leave_task(PyObject *module, PyObject *const *args, Py_ssize_t nargs, exit: return return_value; } -/*[clinic end generated code: output=66f45b8456e9ddc2 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=e92096eacaa22d64 input=a9049054013a1b77]*/ From de125ce94af3e659c74909b519c6a8208f4cd25b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Wed, 1 Aug 2018 19:40:51 +0300 Subject: [PATCH 07/22] Updated the changelog entries to match the latest changes --- Doc/whatsnew/3.8.rst | 7 +++---- .../next/Library/2018-07-29-11-32-56.bpo-34270.aL6P-3.rst | 5 +++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index ef0c21ea7c2e164..b6de2ee71d10891 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -243,10 +243,9 @@ Changes in the Python API * ``PyGC_Head`` struct is changed completely. All code touched the struct member should be rewritten. (See :issue:`33597`) -* An optional keyword-only argument, *name*, was added to - :func:`asyncio.create_task` and :meth:`asyncio.AbstractEventLoop.create_task`. - The API for task factories was also changed accordingly to allow this argument - to be passed. +* The :meth:`asyncio.Task.get_name` and :meth:`asyncio.Task.set_name` methods + were added to the :class:`asyncio.Task` class. The name of the task was made + visible in the ``repr()`` output of :class:`asyncio.Task` instances. CPython bytecode changes diff --git a/Misc/NEWS.d/next/Library/2018-07-29-11-32-56.bpo-34270.aL6P-3.rst b/Misc/NEWS.d/next/Library/2018-07-29-11-32-56.bpo-34270.aL6P-3.rst index a26491c6e78e359..accc770511614f2 100644 --- a/Misc/NEWS.d/next/Library/2018-07-29-11-32-56.bpo-34270.aL6P-3.rst +++ b/Misc/NEWS.d/next/Library/2018-07-29-11-32-56.bpo-34270.aL6P-3.rst @@ -1,2 +1,3 @@ -Asyncio tasks now have a ``name`` attribute which can also be explicitly set -using a keyword argument to ``asyncio.create_task()``. +The default asyncio task class now always has a name which can be get or set using two new methods +(``get_name()`` and ``set_name``). An initial name (like ``Task-1``) is always generated in the +default implementation using a monotonic counter and is visible in the ``repr()`` output. From 98ebde12649d63bf4038b3179c0e10129b24b55d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Wed, 1 Aug 2018 19:45:38 +0300 Subject: [PATCH 08/22] Fixed PEP 8 issue --- Lib/asyncio/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index 370ad35d2754956..190771f18e801f5 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -1,5 +1,4 @@ """Support for tasks, coroutines and the scheduler.""" -import itertools __all__ = ( 'Task', 'create_task', @@ -14,6 +13,7 @@ import contextvars import functools import inspect +import itertools import types import warnings import weakref From 5aadb368af0b022f12b7ef506d90546c713771a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Wed, 1 Aug 2018 19:53:31 +0300 Subject: [PATCH 09/22] Fixed C function name for Task.get_name() --- Modules/_asynciomodule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index a8e4bfc3b9b97e2..d0850fc9b5b790a 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -2313,7 +2313,7 @@ _asyncio.Task.get_name [clinic start generated code]*/ static PyObject * -_asyncio_Task_get_name(TaskObj *self) +_asyncio_Task_get_name_impl(TaskObj *self) /*[clinic end generated code: output=0ecf1570c3b37a8f input=a4a6595d12f4f0f8]*/ { if (self->task_name) { From ad5c572f901ac40f4921c1d562faeed46a305a1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Sat, 4 Aug 2018 13:17:11 +0300 Subject: [PATCH 10/22] Added back the "name" parameter to create_task() and asyncio.Task() As suggested in the code review, this will allow us to add task names in a fashion that is backwards compatible with existing task factories and event loops. --- Lib/asyncio/base_events.py | 12 ++++++++++-- Lib/asyncio/tasks.py | 8 ++++++-- Lib/test/test_asyncio/test_tasks.py | 6 +----- Modules/_asynciomodule.c | 16 +++++++++++----- Modules/clinic/_asynciomodule.c.h | 16 +++++++++------- 5 files changed, 37 insertions(+), 21 deletions(-) diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py index dc0ca3f02b9bf63..3ea5409dcca214c 100644 --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -381,18 +381,26 @@ def create_future(self): """Create a Future object attached to the loop.""" return futures.Future(loop=self) - def create_task(self, coro): + def create_task(self, coro, *, name=None): """Schedule a coroutine object. Return a task object. """ self._check_closed() if self._task_factory is None: - task = tasks.Task(coro, loop=self) + task = tasks.Task(coro, loop=self, name=name) if task._source_traceback: del task._source_traceback[-1] else: task = self._task_factory(self, coro) + if name is not None: + try: + set_name = task.set_name + except AttributeError: + pass + else: + set_name(name) + return task def set_task_factory(self, factory): diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index 190771f18e801f5..fdedc5cea544b37 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -100,7 +100,7 @@ def all_tasks(cls, loop=None): stacklevel=2) return _all_tasks_compat(loop) - def __init__(self, coro, *, loop=None): + def __init__(self, coro, *, loop=None, name=None): super().__init__(loop=loop) if self._source_traceback: del self._source_traceback[-1] @@ -110,7 +110,11 @@ def __init__(self, coro, *, loop=None): self._log_destroy_pending = False raise TypeError(f"a coroutine was expected, got {coro!r}") - self._name = 'Task-%s' % _task_name_counter() + if name is None: + self._name = 'Task-%s' % _task_name_counter() + else: + self._name = str(name) + self._must_cancel = False self._fut_waiter = None self._coro = coro diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py index ed32f61e8b9e42e..77b4ecf42323d92 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -88,11 +88,7 @@ class BaseTaskTests: Future = None def new_task(self, loop, coro, name='TestTask'): - t = self.__class__.Task(coro, loop=loop) - if name is not None: - t.set_name(name) - - return t + return self.__class__.Task(coro, loop=loop, name=name) def new_future(self, loop): return self.__class__.Future(loop=loop) diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index d0850fc9b5b790a..ab129bb304b4d3a 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -1937,13 +1937,15 @@ _asyncio.Task.__init__ coro: object * loop: object = None + name: object = None A coroutine wrapped in a Future. [clinic start generated code]*/ static int -_asyncio_Task___init___impl(TaskObj *self, PyObject *coro, PyObject *loop) -/*[clinic end generated code: output=9f24774c2287fc2f input=8d132974b049593e]*/ +_asyncio_Task___init___impl(TaskObj *self, PyObject *coro, PyObject *loop, + PyObject *name) +/*[clinic end generated code: output=88b12b83d570df50 input=352a3137fe60091d]*/ { if (future_init((FutureObj*)self, loop)) { return -1; @@ -1972,11 +1974,15 @@ _asyncio_Task___init___impl(TaskObj *self, PyObject *coro, PyObject *loop) Py_INCREF(coro); Py_XSETREF(self->task_coro, coro); - PyObject *name = PyUnicode_FromFormat("Task-%" PRIu64, ++task_name_counter); - if (name == NULL) { - return -1; + if (name == Py_None) { + name = PyUnicode_FromFormat("Task-%" PRIu64, ++task_name_counter); + } else { + name = PyObject_Str(name); } Py_XSETREF(self->task_name, name); + if (self->task_name == NULL) { + return -1; + } if (task_call_step_soon(self, NULL)) { return -1; diff --git a/Modules/clinic/_asynciomodule.c.h b/Modules/clinic/_asynciomodule.c.h index b5d1f6f529ed23d..4f5326fd9e7cd83 100644 --- a/Modules/clinic/_asynciomodule.c.h +++ b/Modules/clinic/_asynciomodule.c.h @@ -253,28 +253,30 @@ _asyncio_Future__repr_info(FutureObj *self, PyObject *Py_UNUSED(ignored)) } PyDoc_STRVAR(_asyncio_Task___init____doc__, -"Task(coro, *, loop=None)\n" +"Task(coro, *, loop=None, name=None)\n" "--\n" "\n" "A coroutine wrapped in a Future."); static int -_asyncio_Task___init___impl(TaskObj *self, PyObject *coro, PyObject *loop); +_asyncio_Task___init___impl(TaskObj *self, PyObject *coro, PyObject *loop, + PyObject *name); static int _asyncio_Task___init__(PyObject *self, PyObject *args, PyObject *kwargs) { int return_value = -1; - static const char * const _keywords[] = {"coro", "loop", NULL}; - static _PyArg_Parser _parser = {"O|$O:Task", _keywords, 0}; + static const char * const _keywords[] = {"coro", "loop", "name", NULL}; + static _PyArg_Parser _parser = {"O|$OO:Task", _keywords, 0}; PyObject *coro; PyObject *loop = Py_None; + PyObject *name = Py_None; if (!_PyArg_ParseTupleAndKeywordsFast(args, kwargs, &_parser, - &coro, &loop)) { + &coro, &loop, &name)) { goto exit; } - return_value = _asyncio_Task___init___impl((TaskObj *)self, coro, loop); + return_value = _asyncio_Task___init___impl((TaskObj *)self, coro, loop, name); exit: return return_value; @@ -736,4 +738,4 @@ _asyncio__leave_task(PyObject *module, PyObject *const *args, Py_ssize_t nargs, exit: return return_value; } -/*[clinic end generated code: output=e92096eacaa22d64 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=67da879c9f841505 input=a9049054013a1b77]*/ From 5fba00978570c738a18b288a6101b4c1ee92f1a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Sat, 4 Aug 2018 13:46:08 +0300 Subject: [PATCH 11/22] Added back the "name" parameter to create_task() The previous commit lacked this particular change for the bare asyncio.create_task(). --- Lib/asyncio/tasks.py | 13 +++++++++++-- Lib/test/test_asyncio/test_tasks.py | 12 ++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index fdedc5cea544b37..ed700a8549bb862 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -329,13 +329,22 @@ def __wakeup(self, future): Task = _CTask = _asyncio.Task -def create_task(coro): +def create_task(coro, *, name=None): """Schedule the execution of a coroutine object in a spawn task. Return a Task object. """ loop = events.get_running_loop() - return loop.create_task(coro) + task = loop.create_task(coro) + if name is not None: + try: + set_name = task.set_name + except AttributeError: + pass + else: + set_name(name) + + return task # wait() and as_completed() similar to those in PEP 3148. diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py index 77b4ecf42323d92..c9305936de5529f 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -2289,6 +2289,18 @@ async def coro(): self.loop.run_until_complete(coro()) + def test_bare_create_named_task(self): + + async def coro_noop(): + pass + + async def coro(): + task = asyncio.create_task(coro_noop(), name='No-op') + self.assertEqual(task.get_name(), 'No-op') + await task + + self.loop.run_until_complete(coro()) + def test_context_1(self): cvar = contextvars.ContextVar('cvar', default='nope') From fc02fd448e38119f40d8d5652d4e2d624f07c65b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Sat, 4 Aug 2018 15:48:37 +0300 Subject: [PATCH 12/22] Updated the news items --- Doc/whatsnew/3.8.rst | 8 +++++--- .../Library/2018-07-29-11-32-56.bpo-34270.aL6P-3.rst | 9 ++++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index b6de2ee71d10891..aae5de6b0371a61 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -243,9 +243,11 @@ Changes in the Python API * ``PyGC_Head`` struct is changed completely. All code touched the struct member should be rewritten. (See :issue:`33597`) -* The :meth:`asyncio.Task.get_name` and :meth:`asyncio.Task.set_name` methods - were added to the :class:`asyncio.Task` class. The name of the task was made - visible in the ``repr()`` output of :class:`asyncio.Task` instances. +* Asyncio task can now be named, either by passing the ``name`` keyword + argument to :func:`asyncio.create_task` or by calling + :meth:`asyncio.Task.set_name` method on the task object. The task name + is visible in the ``repr()`` output of :class:`asyncio.Task` and can also be + retrieved using the :meth:`asyncio.Task.get_name` method. CPython bytecode changes diff --git a/Misc/NEWS.d/next/Library/2018-07-29-11-32-56.bpo-34270.aL6P-3.rst b/Misc/NEWS.d/next/Library/2018-07-29-11-32-56.bpo-34270.aL6P-3.rst index accc770511614f2..407eaae5480cced 100644 --- a/Misc/NEWS.d/next/Library/2018-07-29-11-32-56.bpo-34270.aL6P-3.rst +++ b/Misc/NEWS.d/next/Library/2018-07-29-11-32-56.bpo-34270.aL6P-3.rst @@ -1,3 +1,6 @@ -The default asyncio task class now always has a name which can be get or set using two new methods -(``get_name()`` and ``set_name``). An initial name (like ``Task-1``) is always generated in the -default implementation using a monotonic counter and is visible in the ``repr()`` output. +The default asyncio task class now always has a name which can be get or set +using two new methods (``get_name()`` and ``set_name``) and is visible in the +``repr()`` output. An initial name can also be set using the new ``name`` +keyword argument to :func:`asyncio.create_task`. If no initial name is set, +the default Task implementation generates a name like ``Task-1`` using a +monotonic counter. From 77f2147ad980ed8e9b61767d2915cba4db107f81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Sat, 4 Aug 2018 15:58:15 +0300 Subject: [PATCH 13/22] Added the "name" argument back to AbstractEventLoop.create_task() --- Lib/asyncio/events.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/asyncio/events.py b/Lib/asyncio/events.py index e4e632206af1bc5..58a60a0b16a06a5 100644 --- a/Lib/asyncio/events.py +++ b/Lib/asyncio/events.py @@ -277,7 +277,7 @@ def create_future(self): # Method scheduling a coroutine object: create a task. - def create_task(self, coro): + def create_task(self, coro, *, name=None): raise NotImplementedError # Methods for interacting with threads. From 645b6384b2e485bf32134982b45eda0397f8d09f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Sat, 4 Aug 2018 16:13:13 +0300 Subject: [PATCH 14/22] Updated the news items --- Doc/whatsnew/3.8.rst | 9 +++++---- .../Library/2018-07-29-11-32-56.bpo-34270.aL6P-3.rst | 12 +++++++----- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index aae5de6b0371a61..19aa257e42fc063 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -244,10 +244,11 @@ Changes in the Python API struct member should be rewritten. (See :issue:`33597`) * Asyncio task can now be named, either by passing the ``name`` keyword - argument to :func:`asyncio.create_task` or by calling - :meth:`asyncio.Task.set_name` method on the task object. The task name - is visible in the ``repr()`` output of :class:`asyncio.Task` and can also be - retrieved using the :meth:`asyncio.Task.get_name` method. + argument to :func:`asyncio.create_task` or + the :meth:`asyncio.AbstractEventLoop.create_task` event loop method, or by + calling the :meth:`~asyncio.Task.set_name` method on the task object. The + task name is visible in the ``repr()`` output of :class:`asyncio.Task` and + can also be retrieved using the :meth:`~asyncio.Task.get_name` method. CPython bytecode changes diff --git a/Misc/NEWS.d/next/Library/2018-07-29-11-32-56.bpo-34270.aL6P-3.rst b/Misc/NEWS.d/next/Library/2018-07-29-11-32-56.bpo-34270.aL6P-3.rst index 407eaae5480cced..cfdef1337635ae2 100644 --- a/Misc/NEWS.d/next/Library/2018-07-29-11-32-56.bpo-34270.aL6P-3.rst +++ b/Misc/NEWS.d/next/Library/2018-07-29-11-32-56.bpo-34270.aL6P-3.rst @@ -1,6 +1,8 @@ The default asyncio task class now always has a name which can be get or set -using two new methods (``get_name()`` and ``set_name``) and is visible in the -``repr()`` output. An initial name can also be set using the new ``name`` -keyword argument to :func:`asyncio.create_task`. If no initial name is set, -the default Task implementation generates a name like ``Task-1`` using a -monotonic counter. +using two new methods (:meth:`~asyncio.Task.get_name()` and +:meth:`~asyncio.Task.set_name`) and is visible in the ``repr()`` output. An +initial name can also be set using the new ``name`` keyword argument to +:func:`asyncio.create_task` or the +:meth:`~asyncio.AbstractEventLoop.create_task` method of the event loop. +If no initial name is set, the default Task implementation generates a name +like ``Task-1`` using a monotonic counter. From b352983bc393debaa90e255f3c274cbb5bbfb984 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Sat, 4 Aug 2018 16:47:43 +0300 Subject: [PATCH 15/22] More documentation updates --- Doc/library/asyncio-eventloop.rst | 5 ++++- Doc/library/asyncio-task.rst | 16 ++++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/Doc/library/asyncio-eventloop.rst b/Doc/library/asyncio-eventloop.rst index 317f3fb85c54811..11edcd5ab1bcb5a 100644 --- a/Doc/library/asyncio-eventloop.rst +++ b/Doc/library/asyncio-eventloop.rst @@ -250,7 +250,7 @@ Futures Tasks ----- -.. method:: AbstractEventLoop.create_task(coro) +.. method:: AbstractEventLoop.create_task(coro, \*, name=None) Schedule the execution of a :ref:`coroutine object `: wrap it in a future. Return a :class:`Task` object. @@ -261,6 +261,9 @@ Tasks .. versionadded:: 3.4.2 + .. versionchanged:: 3.8 + Added the ``name`` parameter. + .. method:: AbstractEventLoop.set_task_factory(factory) Set a task factory that will be used by diff --git a/Doc/library/asyncio-task.rst b/Doc/library/asyncio-task.rst index 1a9462c2a113613..421662c41c8de24 100644 --- a/Doc/library/asyncio-task.rst +++ b/Doc/library/asyncio-task.rst @@ -387,7 +387,7 @@ with the result. Task ---- -.. function:: create_task(coro) +.. function:: create_task(coro, \*, name=None) Wrap a :ref:`coroutine ` *coro* into a task and schedule its execution. Return the task object. @@ -396,9 +396,18 @@ Task :exc:`RuntimeError` is raised if there is no running loop in current thread. + Parameters: + + * *name* is a descriptive name for the task. If no explicit name is + supplied, the event loop may assign the task an automatically + generated name. + .. versionadded:: 3.7 -.. class:: Task(coro, \*, loop=None) + .. versionchanged:: 3.8 + Added the ``name`` parameter. + +.. class:: Task(coro, \*, loop=None, name=None) A unit for concurrent running of :ref:`coroutines `, subclass of :class:`Future`. @@ -438,6 +447,9 @@ Task .. versionchanged:: 3.7 Added support for the :mod:`contextvars` module. + .. versionchanged:: 3.8 + Added the ``name`` parameter. + .. classmethod:: all_tasks(loop=None) Return a set of all tasks for an event loop. From 56e2883d21bf60548dc6e9203e94fcf552906220 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Sat, 4 Aug 2018 19:04:26 +0300 Subject: [PATCH 16/22] Refactored the task name setting logic into a function --- Lib/asyncio/base_events.py | 10 ++-------- Lib/asyncio/tasks.py | 19 +++++++++++-------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py index 3ea5409dcca214c..15c97aca4086b6c 100644 --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -388,18 +388,12 @@ def create_task(self, coro, *, name=None): """ self._check_closed() if self._task_factory is None: - task = tasks.Task(coro, loop=self, name=name) + task = tasks.Task(coro, loop=self) if task._source_traceback: del task._source_traceback[-1] else: task = self._task_factory(self, coro) - if name is not None: - try: - set_name = task.set_name - except AttributeError: - pass - else: - set_name(name) + tasks._set_task_name(task, name) return task diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index ed700a8549bb862..59e23034b5ab7b2 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -54,6 +54,16 @@ def _all_tasks_compat(loop=None): return {t for t in _all_tasks if futures._get_loop(t) is loop} +def _set_task_name(task, name): + if name is not None: + try: + set_name = task.set_name + except AttributeError: + pass + else: + set_name(name) + + class Task(futures._PyFuture): # Inherit Python Task implementation # from a Python Future implementation. @@ -336,14 +346,7 @@ def create_task(coro, *, name=None): """ loop = events.get_running_loop() task = loop.create_task(coro) - if name is not None: - try: - set_name = task.set_name - except AttributeError: - pass - else: - set_name(name) - + _set_task_name(task, name) return task From d655f24579bfd50488c78c40068bb734b414b148 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Sat, 4 Aug 2018 19:36:42 +0300 Subject: [PATCH 17/22] Documentation tweaks --- Doc/library/asyncio-eventloop.rst | 3 +++ Doc/library/asyncio-task.rst | 11 ++++------- Doc/whatsnew/3.8.rst | 4 ++-- .../Library/2018-07-29-11-32-56.bpo-34270.aL6P-3.rst | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Doc/library/asyncio-eventloop.rst b/Doc/library/asyncio-eventloop.rst index 11edcd5ab1bcb5a..bfecff304abcd0a 100644 --- a/Doc/library/asyncio-eventloop.rst +++ b/Doc/library/asyncio-eventloop.rst @@ -259,6 +259,9 @@ Tasks interoperability. In this case, the result type is a subclass of :class:`Task`. + If *name* is not ``None``, it is set as the name of the task using + :meth:`Task.set_name`. + .. versionadded:: 3.4.2 .. versionchanged:: 3.8 diff --git a/Doc/library/asyncio-task.rst b/Doc/library/asyncio-task.rst index 421662c41c8de24..c73f36b1adb9a9b 100644 --- a/Doc/library/asyncio-task.rst +++ b/Doc/library/asyncio-task.rst @@ -392,16 +392,13 @@ Task Wrap a :ref:`coroutine ` *coro* into a task and schedule its execution. Return the task object. + If *name* is not ``None``, it is set as the name of the task using + :meth:`Task.set_name`. + The task is executed in :func:`get_running_loop` context, :exc:`RuntimeError` is raised if there is no running loop in current thread. - Parameters: - - * *name* is a descriptive name for the task. If no explicit name is - supplied, the event loop may assign the task an automatically - generated name. - .. versionadded:: 3.7 .. versionchanged:: 3.8 @@ -532,7 +529,7 @@ Task The *value* argument can be any object, which is then converted to a string. - In the default Task implementation, the name will be visible in the + In the default ``Task`` implementation, the name will be visible in the :func:`repr` output of a task object. .. versionadded:: 3.8 diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index 19aa257e42fc063..41cc282794e3467 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -243,9 +243,9 @@ Changes in the Python API * ``PyGC_Head`` struct is changed completely. All code touched the struct member should be rewritten. (See :issue:`33597`) -* Asyncio task can now be named, either by passing the ``name`` keyword +* Asyncio tasks can now be named, either by passing the ``name`` keyword argument to :func:`asyncio.create_task` or - the :meth:`asyncio.AbstractEventLoop.create_task` event loop method, or by + the :meth:`~asyncio.AbstractEventLoop.create_task` event loop method, or by calling the :meth:`~asyncio.Task.set_name` method on the task object. The task name is visible in the ``repr()`` output of :class:`asyncio.Task` and can also be retrieved using the :meth:`~asyncio.Task.get_name` method. diff --git a/Misc/NEWS.d/next/Library/2018-07-29-11-32-56.bpo-34270.aL6P-3.rst b/Misc/NEWS.d/next/Library/2018-07-29-11-32-56.bpo-34270.aL6P-3.rst index cfdef1337635ae2..a66e110315ae766 100644 --- a/Misc/NEWS.d/next/Library/2018-07-29-11-32-56.bpo-34270.aL6P-3.rst +++ b/Misc/NEWS.d/next/Library/2018-07-29-11-32-56.bpo-34270.aL6P-3.rst @@ -1,6 +1,6 @@ The default asyncio task class now always has a name which can be get or set using two new methods (:meth:`~asyncio.Task.get_name()` and -:meth:`~asyncio.Task.set_name`) and is visible in the ``repr()`` output. An +:meth:`~asyncio.Task.set_name`) and is visible in the :func:`repr` output. An initial name can also be set using the new ``name`` keyword argument to :func:`asyncio.create_task` or the :meth:`~asyncio.AbstractEventLoop.create_task` method of the event loop. From dd4d4a5cc8d0196fbee37d37686797711960c907 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Wed, 8 Aug 2018 21:33:38 +0300 Subject: [PATCH 18/22] Made the changes requested by the reviewer --- Doc/library/asyncio-eventloop.rst | 4 ++-- Lib/asyncio/base_events.py | 2 +- Lib/test/test_asyncio/test_base_events.py | 12 ++++++++++++ Modules/_asynciomodule.c | 2 +- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/Doc/library/asyncio-eventloop.rst b/Doc/library/asyncio-eventloop.rst index bfecff304abcd0a..c3b8dbb664b2c83 100644 --- a/Doc/library/asyncio-eventloop.rst +++ b/Doc/library/asyncio-eventloop.rst @@ -259,8 +259,8 @@ Tasks interoperability. In this case, the result type is a subclass of :class:`Task`. - If *name* is not ``None``, it is set as the name of the task using - :meth:`Task.set_name`. + If the *name* argument is provided and not ``None``, it is set as the name + of the task using :meth:`Task.set_name`. .. versionadded:: 3.4.2 diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py index 15c97aca4086b6c..120fc71af3c3175 100644 --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -388,7 +388,7 @@ def create_task(self, coro, *, name=None): """ self._check_closed() if self._task_factory is None: - task = tasks.Task(coro, loop=self) + task = tasks.Task(coro, loop=self, name=name) if task._source_traceback: del task._source_traceback[-1] else: diff --git a/Lib/test/test_asyncio/test_base_events.py b/Lib/test/test_asyncio/test_base_events.py index bda8cc6e3a82107..367d8d0b96ad92f 100644 --- a/Lib/test/test_asyncio/test_base_events.py +++ b/Lib/test/test_asyncio/test_base_events.py @@ -813,6 +813,18 @@ def create_task(self, coro): task._log_destroy_pending = False coro.close() + def test_create_named_task(self): + async def test(): + pass + + loop = asyncio.new_event_loop() + task = loop.create_task(test(), name='test_task') + try: + self.assertEqual(task.get_name(), 'test_task') + finally: + loop.run_until_complete(task) + loop.close() + def test_run_forever_keyboard_interrupt(self): # Python issue #22601: ensure that the temporary task created by # run_forever() consumes the KeyboardInterrupt and so don't log diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index ab129bb304b4d3a..12a37dc17f79bde 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -1976,7 +1976,7 @@ _asyncio_Task___init___impl(TaskObj *self, PyObject *coro, PyObject *loop, if (name == Py_None) { name = PyUnicode_FromFormat("Task-%" PRIu64, ++task_name_counter); - } else { + } else if (!PyUnicode_Check(name)) { name = PyObject_Str(name); } Py_XSETREF(self->task_name, name); From 860d67a07cadca39393a0fa57019f74a57c40875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Wed, 8 Aug 2018 21:49:50 +0300 Subject: [PATCH 19/22] Fixed segfault if "name" is already a string --- Modules/_asynciomodule.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 12a37dc17f79bde..32597cb82e1ae1b 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -1978,6 +1978,8 @@ _asyncio_Task___init___impl(TaskObj *self, PyObject *coro, PyObject *loop, name = PyUnicode_FromFormat("Task-%" PRIu64, ++task_name_counter); } else if (!PyUnicode_Check(name)) { name = PyObject_Str(name); + } else { + Py_INCREF(name); } Py_XSETREF(self->task_name, name); if (self->task_name == NULL) { From 2cca5e7c9b13703a321583adca00df9cb9919769 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Wed, 8 Aug 2018 22:07:18 +0300 Subject: [PATCH 20/22] Fixed the new test to use a custom factory --- Lib/test/test_asyncio/test_base_events.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_asyncio/test_base_events.py b/Lib/test/test_asyncio/test_base_events.py index 367d8d0b96ad92f..f9de1c83a69a31f 100644 --- a/Lib/test/test_asyncio/test_base_events.py +++ b/Lib/test/test_asyncio/test_base_events.py @@ -813,11 +813,15 @@ def create_task(self, coro): task._log_destroy_pending = False coro.close() - def test_create_named_task(self): + def test_create_named_task_with_custom_factory(self): + def task_factory(loop, coro): + return asyncio.Task(coro, loop=loop) + async def test(): pass loop = asyncio.new_event_loop() + loop.set_task_factory(task_factory) task = loop.create_task(test(), name='test_task') try: self.assertEqual(task.get_name(), 'test_task') From a1be2e00753053212f50e86c516926aee8d03b61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Wed, 8 Aug 2018 22:43:31 +0300 Subject: [PATCH 21/22] Fixed last two nits --- Lib/asyncio/tasks.py | 2 +- Lib/test/test_asyncio/test_base_events.py | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index 59e23034b5ab7b2..03d71d37f01afd1 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -121,7 +121,7 @@ def __init__(self, coro, *, loop=None, name=None): raise TypeError(f"a coroutine was expected, got {coro!r}") if name is None: - self._name = 'Task-%s' % _task_name_counter() + self._name = f'Task-{_task_name_counter()}' else: self._name = str(name) diff --git a/Lib/test/test_asyncio/test_base_events.py b/Lib/test/test_asyncio/test_base_events.py index f9de1c83a69a31f..36b5878bce7502b 100644 --- a/Lib/test/test_asyncio/test_base_events.py +++ b/Lib/test/test_asyncio/test_base_events.py @@ -813,6 +813,18 @@ def create_task(self, coro): task._log_destroy_pending = False coro.close() + def test_create_named_task_with_default_factory(self): + async def test(): + pass + + loop = asyncio.new_event_loop() + task = loop.create_task(test(), name='test_task') + try: + self.assertEqual(task.get_name(), 'test_task') + finally: + loop.run_until_complete(task) + loop.close() + def test_create_named_task_with_custom_factory(self): def task_factory(loop, coro): return asyncio.Task(coro, loop=loop) From 2ee39721175efb80815fe2487233e8f7a1745ffe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20Gr=C3=B6nholm?= Date: Wed, 8 Aug 2018 22:56:45 +0300 Subject: [PATCH 22/22] Fixed incompatible return value from set_name() --- Modules/_asynciomodule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 32597cb82e1ae1b..3c94848e33189a5 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -2345,7 +2345,7 @@ _asyncio_Task_set_name(TaskObj *self, PyObject *value) { PyObject *name = PyObject_Str(value); if (name == NULL) { - return -1; + return NULL; } Py_XSETREF(self->task_name, name);