Skip to content

Commit 0cf16f9

Browse files
authored
bpo-32363: Disable Task.set_exception() and Task.set_result() (#4923)
1 parent 3dfbaf5 commit 0cf16f9

File tree

7 files changed

+158
-44
lines changed

7 files changed

+158
-44
lines changed

Lib/asyncio/futures.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -239,14 +239,15 @@ def set_exception(self, exception):
239239
self._schedule_callbacks()
240240
self._log_traceback = True
241241

242-
def __iter__(self):
242+
def __await__(self):
243243
if not self.done():
244244
self._asyncio_future_blocking = True
245245
yield self # This tells Task to wait for completion.
246-
assert self.done(), "await wasn't used with future"
246+
if not self.done():
247+
raise RuntimeError("await wasn't used with future")
247248
return self.result() # May raise too.
248249

249-
__await__ = __iter__ # make compatible with 'await' expression
250+
__iter__ = __await__ # make compatible with 'yield from'.
250251

251252

252253
# Needed for testing purposes.

Lib/asyncio/tasks.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ def all_tasks(loop=None):
3737
return {t for t in _all_tasks if futures._get_loop(t) is loop}
3838

3939

40-
class Task(futures.Future):
40+
class Task(futures._PyFuture): # Inherit Python Task implementation
41+
# from a Python Future implementation.
42+
4143
"""A coroutine wrapped in a Future."""
4244

4345
# An important invariant maintained while a Task not done:
@@ -107,11 +109,17 @@ def __del__(self):
107109
if self._source_traceback:
108110
context['source_traceback'] = self._source_traceback
109111
self._loop.call_exception_handler(context)
110-
futures.Future.__del__(self)
112+
super().__del__()
111113

112114
def _repr_info(self):
113115
return base_tasks._task_repr_info(self)
114116

117+
def set_result(self, result):
118+
raise RuntimeError('Task does not support set_result operation')
119+
120+
def set_exception(self, exception):
121+
raise RuntimeError('Task does not support set_exception operation')
122+
115123
def get_stack(self, *, limit=None):
116124
"""Return the list of stack frames for this task's coroutine.
117125
@@ -180,7 +188,9 @@ def cancel(self):
180188
return True
181189

182190
def _step(self, exc=None):
183-
assert not self.done(), f'_step(): already done: {self!r}, {exc!r}'
191+
if self.done():
192+
raise futures.InvalidStateError(
193+
f'_step(): already done: {self!r}, {exc!r}')
184194
if self._must_cancel:
185195
if not isinstance(exc, futures.CancelledError):
186196
exc = futures.CancelledError()
@@ -201,15 +211,15 @@ def _step(self, exc=None):
201211
if self._must_cancel:
202212
# Task is cancelled right before coro stops.
203213
self._must_cancel = False
204-
self.set_exception(futures.CancelledError())
214+
super().set_exception(futures.CancelledError())
205215
else:
206-
self.set_result(exc.value)
216+
super().set_result(exc.value)
207217
except futures.CancelledError:
208218
super().cancel() # I.e., Future.cancel(self).
209219
except Exception as exc:
210-
self.set_exception(exc)
220+
super().set_exception(exc)
211221
except BaseException as exc:
212-
self.set_exception(exc)
222+
super().set_exception(exc)
213223
raise
214224
else:
215225
blocking = getattr(result, '_asyncio_future_blocking', None)

Lib/test/test_asyncio/test_futures.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,8 @@ def coro():
370370
def test():
371371
arg1, arg2 = coro()
372372

373-
self.assertRaises(AssertionError, test)
373+
with self.assertRaisesRegex(RuntimeError, "await wasn't used"):
374+
test()
374375
fut.cancel()
375376

376377
@mock.patch('asyncio.base_events.logger')

Lib/test/test_asyncio/test_tasks.py

Lines changed: 71 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1332,17 +1332,23 @@ def coro():
13321332
self.assertIsNone(task._fut_waiter)
13331333
self.assertTrue(fut.cancelled())
13341334

1335-
def test_step_in_completed_task(self):
1335+
def test_task_set_methods(self):
13361336
@asyncio.coroutine
13371337
def notmuch():
13381338
return 'ko'
13391339

13401340
gen = notmuch()
13411341
task = self.new_task(self.loop, gen)
1342-
task.set_result('ok')
13431342

1344-
self.assertRaises(AssertionError, task._step)
1345-
gen.close()
1343+
with self.assertRaisesRegex(RuntimeError, 'not support set_result'):
1344+
task.set_result('ok')
1345+
1346+
with self.assertRaisesRegex(RuntimeError, 'not support set_exception'):
1347+
task.set_exception(ValueError())
1348+
1349+
self.assertEqual(
1350+
self.loop.run_until_complete(task),
1351+
'ko')
13461352

13471353
def test_step_result(self):
13481354
@asyncio.coroutine
@@ -2231,10 +2237,59 @@ async def func():
22312237
return cls
22322238

22332239

2240+
class SetMethodsTest:
2241+
2242+
def test_set_result_causes_invalid_state(self):
2243+
Future = type(self).Future
2244+
self.loop.call_exception_handler = exc_handler = mock.Mock()
2245+
2246+
async def foo():
2247+
await asyncio.sleep(0.1, loop=self.loop)
2248+
return 10
2249+
2250+
task = self.new_task(self.loop, foo())
2251+
Future.set_result(task, 'spam')
2252+
2253+
self.assertEqual(
2254+
self.loop.run_until_complete(task),
2255+
'spam')
2256+
2257+
exc_handler.assert_called_once()
2258+
exc = exc_handler.call_args[0][0]['exception']
2259+
with self.assertRaisesRegex(asyncio.InvalidStateError,
2260+
r'step\(\): already done'):
2261+
raise exc
2262+
2263+
def test_set_exception_causes_invalid_state(self):
2264+
class MyExc(Exception):
2265+
pass
2266+
2267+
Future = type(self).Future
2268+
self.loop.call_exception_handler = exc_handler = mock.Mock()
2269+
2270+
async def foo():
2271+
await asyncio.sleep(0.1, loop=self.loop)
2272+
return 10
2273+
2274+
task = self.new_task(self.loop, foo())
2275+
Future.set_exception(task, MyExc())
2276+
2277+
with self.assertRaises(MyExc):
2278+
self.loop.run_until_complete(task)
2279+
2280+
exc_handler.assert_called_once()
2281+
exc = exc_handler.call_args[0][0]['exception']
2282+
with self.assertRaisesRegex(asyncio.InvalidStateError,
2283+
r'step\(\): already done'):
2284+
raise exc
2285+
2286+
22342287
@unittest.skipUnless(hasattr(futures, '_CFuture') and
22352288
hasattr(tasks, '_CTask'),
22362289
'requires the C _asyncio module')
2237-
class CTask_CFuture_Tests(BaseTaskTests, test_utils.TestCase):
2290+
class CTask_CFuture_Tests(BaseTaskTests, SetMethodsTest,
2291+
test_utils.TestCase):
2292+
22382293
Task = getattr(tasks, '_CTask', None)
22392294
Future = getattr(futures, '_CFuture', None)
22402295

@@ -2245,21 +2300,16 @@ class CTask_CFuture_Tests(BaseTaskTests, test_utils.TestCase):
22452300
@add_subclass_tests
22462301
class CTask_CFuture_SubclassTests(BaseTaskTests, test_utils.TestCase):
22472302

2248-
class Task(tasks._CTask):
2249-
pass
2250-
2251-
class Future(futures._CFuture):
2252-
pass
2303+
Task = getattr(tasks, '_CTask', None)
2304+
Future = getattr(futures, '_CFuture', None)
22532305

22542306

22552307
@unittest.skipUnless(hasattr(tasks, '_CTask'),
22562308
'requires the C _asyncio module')
22572309
@add_subclass_tests
22582310
class CTaskSubclass_PyFuture_Tests(BaseTaskTests, test_utils.TestCase):
22592311

2260-
class Task(tasks._CTask):
2261-
pass
2262-
2312+
Task = getattr(tasks, '_CTask', None)
22632313
Future = futures._PyFuture
22642314

22652315

@@ -2268,38 +2318,37 @@ class Task(tasks._CTask):
22682318
@add_subclass_tests
22692319
class PyTask_CFutureSubclass_Tests(BaseTaskTests, test_utils.TestCase):
22702320

2271-
class Future(futures._CFuture):
2272-
pass
2273-
2321+
Future = getattr(futures, '_CFuture', None)
22742322
Task = tasks._PyTask
22752323

22762324

22772325
@unittest.skipUnless(hasattr(tasks, '_CTask'),
22782326
'requires the C _asyncio module')
22792327
class CTask_PyFuture_Tests(BaseTaskTests, test_utils.TestCase):
2328+
22802329
Task = getattr(tasks, '_CTask', None)
22812330
Future = futures._PyFuture
22822331

22832332

22842333
@unittest.skipUnless(hasattr(futures, '_CFuture'),
22852334
'requires the C _asyncio module')
22862335
class PyTask_CFuture_Tests(BaseTaskTests, test_utils.TestCase):
2336+
22872337
Task = tasks._PyTask
22882338
Future = getattr(futures, '_CFuture', None)
22892339

22902340

2291-
class PyTask_PyFuture_Tests(BaseTaskTests, test_utils.TestCase):
2341+
class PyTask_PyFuture_Tests(BaseTaskTests, SetMethodsTest,
2342+
test_utils.TestCase):
2343+
22922344
Task = tasks._PyTask
22932345
Future = futures._PyFuture
22942346

22952347

22962348
@add_subclass_tests
22972349
class PyTask_PyFuture_SubclassTests(BaseTaskTests, test_utils.TestCase):
2298-
class Task(tasks._PyTask):
2299-
pass
2300-
2301-
class Future(futures._PyFuture):
2302-
pass
2350+
Task = tasks._PyTask
2351+
Future = futures._PyFuture
23032352

23042353

23052354
@unittest.skipUnless(hasattr(tasks, '_CTask'),
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Make asyncio.Task.set_exception() and set_result() raise
2+
NotImplementedError. Task._step() and Future.__await__() raise proper
3+
exceptions when they are in an invalid state, instead of raising an
4+
AssertionError.

Modules/_asynciomodule.c

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -779,7 +779,7 @@ _asyncio_Future_exception_impl(FutureObj *self)
779779
/*[clinic input]
780780
_asyncio.Future.set_result
781781
782-
res: object
782+
result: object
783783
/
784784
785785
Mark the future done and set its result.
@@ -789,11 +789,11 @@ InvalidStateError.
789789
[clinic start generated code]*/
790790

791791
static PyObject *
792-
_asyncio_Future_set_result(FutureObj *self, PyObject *res)
793-
/*[clinic end generated code: output=a620abfc2796bfb6 input=5b9dc180f1baa56d]*/
792+
_asyncio_Future_set_result(FutureObj *self, PyObject *result)
793+
/*[clinic end generated code: output=1ec2e6bcccd6f2ce input=8b75172c2a7b05f1]*/
794794
{
795795
ENSURE_FUTURE_ALIVE(self)
796-
return future_set_result(self, res);
796+
return future_set_result(self, result);
797797
}
798798

799799
/*[clinic input]
@@ -1468,8 +1468,8 @@ FutureIter_iternext(futureiterobject *it)
14681468
Py_INCREF(fut);
14691469
return (PyObject *)fut;
14701470
}
1471-
PyErr_SetString(PyExc_AssertionError,
1472-
"yield from wasn't used with future");
1471+
PyErr_SetString(PyExc_RuntimeError,
1472+
"await wasn't used with future");
14731473
return NULL;
14741474
}
14751475

@@ -2232,6 +2232,39 @@ _asyncio_Task__wakeup_impl(TaskObj *self, PyObject *fut)
22322232
return task_wakeup(self, fut);
22332233
}
22342234

2235+
/*[clinic input]
2236+
_asyncio.Task.set_result
2237+
2238+
result: object
2239+
/
2240+
[clinic start generated code]*/
2241+
2242+
static PyObject *
2243+
_asyncio_Task_set_result(TaskObj *self, PyObject *result)
2244+
/*[clinic end generated code: output=1dcae308bfcba318 input=9d1a00c07be41bab]*/
2245+
{
2246+
PyErr_SetString(PyExc_RuntimeError,
2247+
"Task does not support set_result operation");
2248+
return NULL;
2249+
}
2250+
2251+
/*[clinic input]
2252+
_asyncio.Task.set_exception
2253+
2254+
exception: object
2255+
/
2256+
[clinic start generated code]*/
2257+
2258+
static PyObject *
2259+
_asyncio_Task_set_exception(TaskObj *self, PyObject *exception)
2260+
/*[clinic end generated code: output=bc377fc28067303d input=9a8f65c83dcf893a]*/
2261+
{
2262+
PyErr_SetString(PyExc_RuntimeError,
2263+
"Task doed not support set_exception operation");
2264+
return NULL;
2265+
}
2266+
2267+
22352268
static void
22362269
TaskObj_finalize(TaskObj *task)
22372270
{
@@ -2304,12 +2337,12 @@ static void TaskObj_dealloc(PyObject *); /* Needs Task_CheckExact */
23042337
static PyMethodDef TaskType_methods[] = {
23052338
_ASYNCIO_FUTURE_RESULT_METHODDEF
23062339
_ASYNCIO_FUTURE_EXCEPTION_METHODDEF
2307-
_ASYNCIO_FUTURE_SET_RESULT_METHODDEF
2308-
_ASYNCIO_FUTURE_SET_EXCEPTION_METHODDEF
23092340
_ASYNCIO_FUTURE_ADD_DONE_CALLBACK_METHODDEF
23102341
_ASYNCIO_FUTURE_REMOVE_DONE_CALLBACK_METHODDEF
23112342
_ASYNCIO_FUTURE_CANCELLED_METHODDEF
23122343
_ASYNCIO_FUTURE_DONE_METHODDEF
2344+
_ASYNCIO_TASK_SET_RESULT_METHODDEF
2345+
_ASYNCIO_TASK_SET_EXCEPTION_METHODDEF
23132346
_ASYNCIO_TASK_CURRENT_TASK_METHODDEF
23142347
_ASYNCIO_TASK_ALL_TASKS_METHODDEF
23152348
_ASYNCIO_TASK_CANCEL_METHODDEF
@@ -2461,7 +2494,7 @@ task_step_impl(TaskObj *task, PyObject *exc)
24612494
PyObject *o;
24622495

24632496
if (task->task_state != STATE_PENDING) {
2464-
PyErr_Format(PyExc_AssertionError,
2497+
PyErr_Format(asyncio_InvalidStateError,
24652498
"_step(): already done: %R %R",
24662499
task,
24672500
exc ? exc : Py_None);

Modules/clinic/_asynciomodule.c.h

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ _asyncio_Future_exception(FutureObj *self, PyObject *Py_UNUSED(ignored))
8686
}
8787

8888
PyDoc_STRVAR(_asyncio_Future_set_result__doc__,
89-
"set_result($self, res, /)\n"
89+
"set_result($self, result, /)\n"
9090
"--\n"
9191
"\n"
9292
"Mark the future done and set its result.\n"
@@ -536,6 +536,22 @@ _asyncio_Task__wakeup(TaskObj *self, PyObject *const *args, Py_ssize_t nargs, Py
536536
return return_value;
537537
}
538538

539+
PyDoc_STRVAR(_asyncio_Task_set_result__doc__,
540+
"set_result($self, result, /)\n"
541+
"--\n"
542+
"\n");
543+
544+
#define _ASYNCIO_TASK_SET_RESULT_METHODDEF \
545+
{"set_result", (PyCFunction)_asyncio_Task_set_result, METH_O, _asyncio_Task_set_result__doc__},
546+
547+
PyDoc_STRVAR(_asyncio_Task_set_exception__doc__,
548+
"set_exception($self, exception, /)\n"
549+
"--\n"
550+
"\n");
551+
552+
#define _ASYNCIO_TASK_SET_EXCEPTION_METHODDEF \
553+
{"set_exception", (PyCFunction)_asyncio_Task_set_exception, METH_O, _asyncio_Task_set_exception__doc__},
554+
539555
PyDoc_STRVAR(_asyncio__get_running_loop__doc__,
540556
"_get_running_loop($module, /)\n"
541557
"--\n"
@@ -747,4 +763,4 @@ _asyncio__leave_task(PyObject *module, PyObject *const *args, Py_ssize_t nargs,
747763
exit:
748764
return return_value;
749765
}
750-
/*[clinic end generated code: output=5d100b3d74f2a0f4 input=a9049054013a1b77]*/
766+
/*[clinic end generated code: output=616e814431893dcc input=a9049054013a1b77]*/

0 commit comments

Comments
 (0)