From d1ea1d3bbe72f135f1623ee81bd70c87d83c1e8b Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Sun, 13 Feb 2022 12:35:32 +0200 Subject: [PATCH 1/2] Implement task.__cancel_requested__ property --- Lib/asyncio/tasks.py | 7 ++++++- Modules/_asynciomodule.c | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index 2bee5c050ded7d..faee0cec6b668d 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -109,6 +109,7 @@ def __init__(self, coro, *, loop=None, name=None): self._fut_waiter = None self._coro = coro self._context = contextvars.copy_context() + self.__cancel_requested__ = False self._loop.call_soon(self.__step, context=self._context) _register_task(self) @@ -201,6 +202,10 @@ def cancel(self, msg=None): self._log_traceback = False if self.done(): return False + if self.__cancel_requested__: + # Cancel was requested by previous task.cancel() call + return False + self.__cancel_requested__ = True if self._fut_waiter is not None: if self._fut_waiter.cancel(msg=msg): # Leave self._fut_waiter; it may be a Task that @@ -634,7 +639,7 @@ def _ensure_future(coro_or_future, *, loop=None): loop = events._get_event_loop(stacklevel=4) try: return loop.create_task(coro_or_future) - except RuntimeError: + except RuntimeError: if not called_wrap_awaitable: coro_or_future.close() raise diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 72dbdb8902f121..95faa179f64ddd 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -91,6 +91,7 @@ typedef struct { PyObject *task_context; int task_must_cancel; int task_log_destroy_pending; + int task_cancel_requested; } TaskObj; typedef struct { @@ -2039,6 +2040,7 @@ _asyncio_Task___init___impl(TaskObj *self, PyObject *coro, PyObject *loop, Py_CLEAR(self->task_fut_waiter); self->task_must_cancel = 0; self->task_log_destroy_pending = 1; + self->task_cancel_requested = 0; Py_INCREF(coro); Py_XSETREF(self->task_coro, coro); @@ -2141,6 +2143,32 @@ TaskObj_get_fut_waiter(TaskObj *task, void *Py_UNUSED(ignored)) Py_RETURN_NONE; } +static PyObject * +TaskObj_get_cancel_requested(TaskObj *task, void *Py_UNUSED(ignored)) +{ + if (task->task_cancel_requested) { + Py_RETURN_TRUE; + } + else { + Py_RETURN_FALSE; + } +} + +static int +TaskObj_set_cancel_requested(TaskObj *task, PyObject *val, void *Py_UNUSED(ignored)) +{ + if (val == NULL) { + PyErr_SetString(PyExc_AttributeError, "cannot delete attribute"); + return -1; + } + int is_true = PyObject_IsTrue(val); + if (is_true < 0) { + return -1; + } + task->task_cancel_requested = is_true; + return 0; +} + /*[clinic input] _asyncio.Task._make_cancelled_error @@ -2205,6 +2233,11 @@ _asyncio_Task_cancel_impl(TaskObj *self, PyObject *msg) Py_RETURN_FALSE; } + if (self->task_cancel_requested) { + Py_RETURN_FALSE; + } + self->task_cancel_requested = 1; + if (self->task_fut_waiter) { PyObject *res; int is_true; @@ -2473,6 +2506,8 @@ static PyGetSetDef TaskType_getsetlist[] = { {"_must_cancel", (getter)TaskObj_get_must_cancel, NULL, NULL}, {"_coro", (getter)TaskObj_get_coro, NULL, NULL}, {"_fut_waiter", (getter)TaskObj_get_fut_waiter, NULL, NULL}, + {"__task_cancel_requested__", (getter)TaskObj_get_cancel_requested, + (setter)TaskObj_set_cancel_requested, NULL}, {NULL} /* Sentinel */ }; From 26a0f1056a738c2ec7a9ed8f06239c3d9894d30d Mon Sep 17 00:00:00 2001 From: Andrew Svetlov Date: Sun, 13 Feb 2022 13:33:24 +0200 Subject: [PATCH 2/2] Add test --- Lib/test/test_asyncio/test_tasks.py | 19 +++++++++++++++++++ Modules/_asynciomodule.c | 4 ++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py index 8c4dceacdeec96..b14240327d60c7 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -496,6 +496,25 @@ async def run(): # This also distinguishes from the initial has_cycle=None. self.assertEqual(has_cycle, False) + def test___cancel_requested__(self): + loop = asyncio.new_event_loop() + + async def task(): + await asyncio.sleep(10) + return 12 + + try: + t = self.new_task(loop, task()) + self.assertFalse(t.__cancel_requested__) + self.assertTrue(t.cancel()) + self.assertTrue(t.__cancel_requested__) + self.assertFalse(t.cancel()) + + with self.assertRaises(asyncio.CancelledError): + loop.run_until_complete(t) + finally: + loop.close() + def test_cancel(self): def gen(): diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 95faa179f64ddd..ef176b6bc596a9 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -2506,8 +2506,8 @@ static PyGetSetDef TaskType_getsetlist[] = { {"_must_cancel", (getter)TaskObj_get_must_cancel, NULL, NULL}, {"_coro", (getter)TaskObj_get_coro, NULL, NULL}, {"_fut_waiter", (getter)TaskObj_get_fut_waiter, NULL, NULL}, - {"__task_cancel_requested__", (getter)TaskObj_get_cancel_requested, - (setter)TaskObj_set_cancel_requested, NULL}, + {"__cancel_requested__", (getter)TaskObj_get_cancel_requested, + (setter)TaskObj_set_cancel_requested, NULL}, {NULL} /* Sentinel */ }; pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy