Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion Lib/asyncio/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,11 +103,20 @@ def __init__(self, coro, *, loop=None, name=None, context=None,
self._coro = coro
if context is None:
self._context = contextvars.copy_context()
elif not isinstance(context, contextvars.Context):
# gh-157301: the passed value must be a contextvars.Context
self._log_destroy_pending = False
raise TypeError('a contextvars.Context was expected, '
f'got {type(context).__name__}')
else:
self._context = context

if eager_start and self._loop.is_running():
self.__eager_start()
try:
self.__eager_start()
except:
self._log_destroy_pending = False
raise
else:
self._loop.call_soon(self.__step, context=self._context)
_py_register_task(self)
Expand Down
62 changes: 62 additions & 0 deletions Lib/test/test_asyncio/test_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -2635,6 +2635,68 @@ async def main():
finally:
loop.close()

def test_context_not_a_context(self):
# gh-157301
async def coro():
pass

loop = asyncio.new_event_loop()
c = coro()
try:
with self.assertRaises(TypeError):
self.new_task(loop, c, context='not a context')
finally:
c.close()
loop.close()

def test_context_not_a_context_leaves_loop_usable(self):
# gh-157301
async def coro():
pass

async def main():
c = coro()
try:
with self.assertRaises(TypeError):
self.new_task(loop, c, context='not a context',
eager_start=True)
finally:
c.close()
await asyncio.sleep(0)

loop = asyncio.new_event_loop()
loop.call_later(support.SHORT_TIMEOUT, loop.stop)
try:
loop.run_until_complete(self.new_task(loop, main()))
finally:
loop.close()

def test_context_already_entered_leaves_loop_usable(self):
# gh-157301
async def coro():
pass

async def main():
ctx = contextvars.copy_context()

def inside():
c = coro()
try:
with self.assertRaises(RuntimeError):
self.new_task(loop, c, context=ctx, eager_start=True)
finally:
c.close()

ctx.run(inside)
await asyncio.sleep(0)

loop = asyncio.new_event_loop()
loop.call_later(support.SHORT_TIMEOUT, loop.stop)
try:
loop.run_until_complete(self.new_task(loop, main()))
finally:
loop.close()

def test_context_2(self):
cvar = contextvars.ContextVar('cvar', default='nope')

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix :class:`asyncio.Task` hanging the event loop when an eager start fails
to enter the task's context.
13 changes: 13 additions & 0 deletions Modules/_asynciomodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -2312,6 +2312,13 @@ _asyncio_Task___init___impl(TaskObj *self, PyObject *coro, PyObject *loop,
if (self->task_context == NULL) {
return -1;
}
} else if (!PyContext_CheckExact(context)) {
// gh-157301: the passed value must be a contextvars.Context
self->task_log_destroy_pending = 0;
PyErr_Format(PyExc_TypeError,
"a contextvars.Context was expected, got %T",
context);
return -1;
} else {
Py_XSETREF(self->task_context, Py_NewRef(context));
}
Expand Down Expand Up @@ -3458,7 +3465,13 @@ task_eager_start(_PyThreadStateImpl *ts, asyncio_state *state, TaskObj *task)
// it will continue as a regular (non-eager) asyncio task
register_task(ts, task);

assert(PyContext_CheckExact(task->task_context));
if (_PyContext_Enter(&ts->base, task->task_context) == -1) {
// gh-157301: a failed enter must not leave the task current and registered
task->task_log_destroy_pending = 0;
PyObject *curtask = swap_current_task(ts, task->task_loop, prevtask);
Py_XDECREF(curtask);
unregister_task(task);
Py_DECREF(prevtask);
return -1;
}
Expand Down
4 changes: 2 additions & 2 deletions Objects/floatobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1895,7 +1895,7 @@ int
PyFloat_Pack2(double x, char *data, int le)
{
unsigned char *p = (unsigned char *)data;
#if HAVE_FLOAT16
#if _Py_HAVE_FLOAT16
/* Conversion can change NaNs type or alter payload. Here we
just fallback to the generic code, instead of providing
workarounds as for single/double precision. */
Expand Down Expand Up @@ -2115,7 +2115,7 @@ double
PyFloat_Unpack2(const char *data, int le)
{
unsigned char *p = (unsigned char *)data;
#if HAVE_FLOAT16
#if _Py_HAVE_FLOAT16
_Float16 x16;

if ((_PY_FLOAT_LITTLE_ENDIAN && !le) || (_PY_FLOAT_BIG_ENDIAN && le)) {
Expand Down
2 changes: 1 addition & 1 deletion configure

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -4486,7 +4486,7 @@ int main(void)
[ac_cv_float16_supported=no],
[ac_cv_float16_supported=no])]))
AS_VAR_IF([ac_cv_float16_supported], [yes],
[AC_DEFINE([HAVE_FLOAT16], [1],
[AC_DEFINE([_Py_HAVE_FLOAT16], [1],
[Defined if _Float16 C type is supported])])

dnl Check for libmpdec >= 2.5.0
Expand Down
6 changes: 3 additions & 3 deletions pyconfig.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -497,9 +497,6 @@
/* Define if you have the 'ffi_prep_closure_loc' function. */
#undef HAVE_FFI_PREP_CLOSURE_LOC

/* Defined if _Float16 C type is supported */
#undef HAVE_FLOAT16

/* Define to 1 if you have the 'flock' function. */
#undef HAVE_FLOCK

Expand Down Expand Up @@ -2210,6 +2207,9 @@
AND the target architecture has native SIMD (not just API availability) */
#undef _Py_HAVE_EFFICIENT_BUILTIN_SHUFFLEVECTOR

/* Defined if _Float16 C type is supported */
#undef _Py_HAVE_FLOAT16

/* Define to 1 if libgcc __register_frame and __deregister_frame are linkable.
*/
#undef _Py_HAVE_LIBGCC_EH_FRAME_REGISTRATION
Expand Down
Loading