Skip to content

Commit

Permalink
Add recursion guard when cancelling tasks
Browse files Browse the repository at this point in the history
Summary: As in title, fixes stack overflow when cancellation is triggered on tasks with mutual dependencies

Reviewed By: carljm

Differential Revision: D44685444

fbshipit-source-id: 966426a5eb5c7cd86ad79b2ef4f94f3baf091411
  • Loading branch information
Vladimir Matveev authored and facebook-github-bot committed Apr 5, 2023
1 parent 8915330 commit 157ccc7
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 0 deletions.
31 changes: 31 additions & 0 deletions Lib/test/test_cinder.py
Original file line number Diff line number Diff line change
Expand Up @@ -2653,7 +2653,38 @@ async def test_suspended_duplicate_success(self):
await self.run_duplicate_success_test("test_suspended_duplicate_success", True)


@cinder_support.skipUnderJIT("Recursion checks are not consistent in JIT")
class TestTaskCancel(unittest.TestCase):
def test_recursive_cancel(self):
def run(TaskConstructor):
async def main():
# create first task
fut = asyncio.Future()
t1 = TaskConstructor(f1(fut))
t2 = TaskConstructor(f2(t1))
fut.set_result(t2)
await asyncio.sleep(1)
t1.cancel()

async def f1(fut):
task = await fut
await task

async def f2(task):
await task

with self.assertRaises(RecursionError):
asyncio.run(main())

with self.subTest("PyTask"):
run(asyncio.tasks._PyTask)

with self.subTest("asyncio.Task"):
run(asyncio.Task)


class GeneralRegressionTests(unittest.TestCase):

# This tests a fix for an issue reported in T130047792
@async_test
async def test_skip_duplicated_coro_on_earlier_failure(self):
Expand Down
6 changes: 6 additions & 0 deletions Modules/_asynciomodule.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "Python.h"
#include "pycore_list.h" // _PyList_ITEMS()
#include "pycore_pyerrors.h" // _PyErr_ClearExcState()
#include "pycore_ceval.h" // _Py_EnterRecursiveCall()
#include "pycore_pystate.h"
#include "pycore_call.h"
#include <stddef.h> // offsetof()
Expand Down Expand Up @@ -3780,7 +3781,12 @@ task_cancel_impl(TaskObj *self, PyObject *msg) {
if (tableref == NULL) {
return -1;
}
PyThreadState *tstate = PyThreadState_GET();
if (_Py_EnterRecursiveCall(tstate, " while calling 'cancel' on the task")) {
return -1;
}
is_true = tableref->cancel(self->task_fut_waiter, msg);
_Py_LeaveRecursiveCall(tstate);

if (is_true < 0) {
return -1;
Expand Down

0 comments on commit 157ccc7

Please sign in to comment.