Skip to content
This repository was archived by the owner on Feb 13, 2025. It is now read-only.

Commit e50dea2

Browse files
author
Anselm Kruis
committed
merge branch 3.5
2 parents 38ad290 + aab3c4a commit e50dea2

File tree

6 files changed

+68
-10
lines changed

6 files changed

+68
-10
lines changed

Doc/library/sys.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1085,6 +1085,20 @@ always available.
10851085
If called twice, the new wrapper replaces the previous one. The function
10861086
is thread-specific.
10871087

1088+
The *wrapper* callable cannot define new coroutines directly or indirectly::
1089+
1090+
def wrapper(coro):
1091+
async def wrap(coro):
1092+
return await coro
1093+
return wrap(coro)
1094+
sys.set_coroutine_wrapper(wrapper)
1095+
1096+
async def foo(): pass
1097+
1098+
# The following line will fail with a RuntimeError, because
1099+
# `wrapper` creates a `wrap(coro)` coroutine:
1100+
foo()
1101+
10881102
See also :func:`get_coroutine_wrapper`.
10891103

10901104
.. versionadded:: 3.5

Include/ceval.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ PyAPI_FUNC(PyObject *) PyEval_CallMethod(PyObject *obj,
2323
#ifndef Py_LIMITED_API
2424
PyAPI_FUNC(void) PyEval_SetProfile(Py_tracefunc, PyObject *);
2525
PyAPI_FUNC(void) PyEval_SetTrace(Py_tracefunc, PyObject *);
26-
PyAPI_FUNC(void) _PyEval_SetCoroutineWrapper(PyObject *wrapper);
26+
PyAPI_FUNC(void) _PyEval_SetCoroutineWrapper(PyObject *);
2727
PyAPI_FUNC(PyObject *) _PyEval_GetCoroutineWrapper(void);
28+
PyAPI_FUNC(PyObject *) _PyEval_ApplyCoroutineWrapper(PyObject *);
2829
#endif
2930

3031
struct _frame; /* Avoid including frameobject.h */

Include/pystate.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ PyStacklessState st;
142142
void *on_delete_data;
143143

144144
PyObject *coroutine_wrapper;
145+
int in_coroutine_wrapper;
145146

146147
/* XXX signal handlers should also be here */
147148

Lib/test/test_coroutines.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -995,6 +995,26 @@ def test_set_wrapper_2(self):
995995
sys.set_coroutine_wrapper(1)
996996
self.assertIsNone(sys.get_coroutine_wrapper())
997997

998+
def test_set_wrapper_3(self):
999+
async def foo():
1000+
return 'spam'
1001+
1002+
def wrapper(coro):
1003+
async def wrap(coro):
1004+
return await coro
1005+
return wrap(coro)
1006+
1007+
sys.set_coroutine_wrapper(wrapper)
1008+
try:
1009+
with self.assertRaisesRegex(
1010+
RuntimeError,
1011+
"coroutine wrapper.*\.wrapper at 0x.*attempted to "
1012+
"recursively wrap <coroutine.*\.wrap"):
1013+
1014+
foo()
1015+
finally:
1016+
sys.set_coroutine_wrapper(None)
1017+
9981018

9991019
class CAPITest(unittest.TestCase):
10001020

Python/ceval.c

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4667,7 +4667,6 @@ _PyEval_EvalCodeWithName(PyObject *_co, PyObject *globals, PyObject *locals,
46674667

46684668
if (co->co_flags & CO_GENERATOR) {
46694669
PyObject *gen;
4670-
PyObject *coroutine_wrapper;
46714670

46724671
/* Don't need to keep the reference to f_back, it will be set
46734672
* when the generator is resumed. */
@@ -4681,14 +4680,9 @@ _PyEval_EvalCodeWithName(PyObject *_co, PyObject *globals, PyObject *locals,
46814680
if (gen == NULL)
46824681
return NULL;
46834682

4684-
if (co->co_flags & (CO_COROUTINE | CO_ITERABLE_COROUTINE)) {
4685-
coroutine_wrapper = _PyEval_GetCoroutineWrapper();
4686-
if (coroutine_wrapper != NULL) {
4687-
PyObject *wrapped =
4688-
PyObject_CallFunction(coroutine_wrapper, "N", gen);
4689-
gen = wrapped;
4690-
}
4691-
}
4683+
if (co->co_flags & (CO_COROUTINE | CO_ITERABLE_COROUTINE))
4684+
return _PyEval_ApplyCoroutineWrapper(gen);
4685+
46924686
return gen;
46934687
}
46944688

@@ -5178,6 +5172,33 @@ _PyEval_GetCoroutineWrapper(void)
51785172
return tstate->coroutine_wrapper;
51795173
}
51805174

5175+
PyObject *
5176+
_PyEval_ApplyCoroutineWrapper(PyObject *gen)
5177+
{
5178+
PyObject *wrapped;
5179+
PyThreadState *tstate = PyThreadState_GET();
5180+
PyObject *wrapper = tstate->coroutine_wrapper;
5181+
5182+
if (tstate->in_coroutine_wrapper) {
5183+
assert(wrapper != NULL);
5184+
PyErr_Format(PyExc_RuntimeError,
5185+
"coroutine wrapper %.150R attempted "
5186+
"to recursively wrap %.150R",
5187+
wrapper,
5188+
gen);
5189+
return NULL;
5190+
}
5191+
5192+
if (wrapper == NULL) {
5193+
return gen;
5194+
}
5195+
5196+
tstate->in_coroutine_wrapper = 1;
5197+
wrapped = PyObject_CallFunction(wrapper, "N", gen);
5198+
tstate->in_coroutine_wrapper = 0;
5199+
return wrapped;
5200+
}
5201+
51815202
PyObject *
51825203
PyEval_GetBuiltins(void)
51835204
{

Python/pystate.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,7 @@ new_threadstate(PyInterpreterState *interp, int init)
239239
tstate->on_delete_data = NULL;
240240

241241
tstate->coroutine_wrapper = NULL;
242+
tstate->in_coroutine_wrapper = 0;
242243
#ifdef STACKLESS
243244
STACKLESS_PYSTATE_NEW;
244245
#endif

0 commit comments

Comments
 (0)