Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
cpython
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
cpython
Commits
77c96813
Commit
77c96813
authored
Feb 13, 2016
by
Yury Selivanov
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Issue #25887: Raise a RuntimeError when a coroutine is awaited more than once.
parent
b2a2aa76
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
169 additions
and
12 deletions
+169
-12
Doc/reference/datamodel.rst
Doc/reference/datamodel.rst
+4
-0
Lib/test/test_coroutines.py
Lib/test/test_coroutines.py
+141
-0
Misc/NEWS
Misc/NEWS
+3
-0
Objects/genobject.c
Objects/genobject.c
+21
-12
No files found.
Doc/reference/datamodel.rst
View file @
77c96813
...
...
@@ -2316,6 +2316,10 @@ Coroutines also have the methods listed below, which are analogous to
those of generators (see :ref:`generator-methods`). However, unlike
generators, coroutines do not directly support iteration.
.. versionchanged:: 3.5.2
It is a :exc:`RuntimeError` to await on a coroutine more than once.
.. method:: coroutine.send(value)
Starts or resumes execution of the coroutine. If *value* is ``None``,
...
...
Lib/test/test_coroutines.py
View file @
77c96813
...
...
@@ -569,6 +569,147 @@ class CoroutineTest(unittest.TestCase):
"coroutine ignored GeneratorExit"
):
c
.
close
()
def
test_func_15
(
self
):
# See http://bugs.python.org/issue25887 for details
async
def
spammer
():
return
'spam'
async
def
reader
(
coro
):
return
await
coro
spammer_coro
=
spammer
()
with
self
.
assertRaisesRegex
(
StopIteration
,
'spam'
):
reader
(
spammer_coro
).
send
(
None
)
with
self
.
assertRaisesRegex
(
RuntimeError
,
'cannot reuse already awaited coroutine'
):
reader
(
spammer_coro
).
send
(
None
)
def
test_func_16
(
self
):
# See http://bugs.python.org/issue25887 for details
@
types
.
coroutine
def
nop
():
yield
async
def
send
():
await
nop
()
return
'spam'
async
def
read
(
coro
):
await
nop
()
return
await
coro
spammer
=
send
()
reader
=
read
(
spammer
)
reader
.
send
(
None
)
reader
.
send
(
None
)
with
self
.
assertRaisesRegex
(
Exception
,
'ham'
):
reader
.
throw
(
Exception
(
'ham'
))
reader
=
read
(
spammer
)
reader
.
send
(
None
)
with
self
.
assertRaisesRegex
(
RuntimeError
,
'cannot reuse already awaited coroutine'
):
reader
.
send
(
None
)
with
self
.
assertRaisesRegex
(
RuntimeError
,
'cannot reuse already awaited coroutine'
):
reader
.
throw
(
Exception
(
'wat'
))
def
test_func_17
(
self
):
# See http://bugs.python.org/issue25887 for details
async
def
coroutine
():
return
'spam'
coro
=
coroutine
()
with
self
.
assertRaisesRegex
(
StopIteration
,
'spam'
):
coro
.
send
(
None
)
with
self
.
assertRaisesRegex
(
RuntimeError
,
'cannot reuse already awaited coroutine'
):
coro
.
send
(
None
)
with
self
.
assertRaisesRegex
(
RuntimeError
,
'cannot reuse already awaited coroutine'
):
coro
.
throw
(
Exception
(
'wat'
))
# Closing a coroutine shouldn't raise any exception even if it's
# already closed/exhausted (similar to generators)
coro
.
close
()
coro
.
close
()
def
test_func_18
(
self
):
# See http://bugs.python.org/issue25887 for details
async
def
coroutine
():
return
'spam'
coro
=
coroutine
()
await_iter
=
coro
.
__await__
()
it
=
iter
(
await_iter
)
with
self
.
assertRaisesRegex
(
StopIteration
,
'spam'
):
it
.
send
(
None
)
with
self
.
assertRaisesRegex
(
RuntimeError
,
'cannot reuse already awaited coroutine'
):
it
.
send
(
None
)
with
self
.
assertRaisesRegex
(
RuntimeError
,
'cannot reuse already awaited coroutine'
):
# Although the iterator protocol requires iterators to
# raise another StopIteration here, we don't want to do
# that. In this particular case, the iterator will raise
# a RuntimeError, so that 'yield from' and 'await'
# expressions will trigger the error, instead of silently
# ignoring the call.
next
(
it
)
with
self
.
assertRaisesRegex
(
RuntimeError
,
'cannot reuse already awaited coroutine'
):
it
.
throw
(
Exception
(
'wat'
))
with
self
.
assertRaisesRegex
(
RuntimeError
,
'cannot reuse already awaited coroutine'
):
it
.
throw
(
Exception
(
'wat'
))
# Closing a coroutine shouldn't raise any exception even if it's
# already closed/exhausted (similar to generators)
it
.
close
()
it
.
close
()
def
test_func_19
(
self
):
CHK
=
0
@
types
.
coroutine
def
foo
():
nonlocal
CHK
yield
try
:
yield
except
GeneratorExit
:
CHK
+=
1
async
def
coroutine
():
await
foo
()
coro
=
coroutine
()
coro
.
send
(
None
)
coro
.
send
(
None
)
self
.
assertEqual
(
CHK
,
0
)
coro
.
close
()
self
.
assertEqual
(
CHK
,
1
)
for
_
in
range
(
3
):
# Closing a coroutine shouldn't raise any exception even if it's
# already closed/exhausted (similar to generators)
coro
.
close
()
self
.
assertEqual
(
CHK
,
1
)
def
test_cr_await
(
self
):
@
types
.
coroutine
def
a
():
...
...
Misc/NEWS
View file @
77c96813
...
...
@@ -69,6 +69,9 @@ Core and Builtins
- Issue #25660: Fix TAB key behaviour in REPL with readline.
- Issue #25887: Raise a RuntimeError when a coroutine object is awaited
more than once.
Library
-------
...
...
Objects/genobject.c
View file @
77c96813
...
...
@@ -78,7 +78,7 @@ gen_dealloc(PyGenObject *gen)
}
static
PyObject
*
gen_send_ex
(
PyGenObject
*
gen
,
PyObject
*
arg
,
int
exc
)
gen_send_ex
(
PyGenObject
*
gen
,
PyObject
*
arg
,
int
exc
,
int
closing
)
{
PyThreadState
*
tstate
=
PyThreadState_GET
();
PyFrameObject
*
f
=
gen
->
gi_frame
;
...
...
@@ -92,9 +92,18 @@ gen_send_ex(PyGenObject *gen, PyObject *arg, int exc)
return
NULL
;
}
if
(
f
==
NULL
||
f
->
f_stacktop
==
NULL
)
{
/* Only set exception if called from send() */
if
(
arg
&&
!
exc
)
if
(
PyCoro_CheckExact
(
gen
)
&&
!
closing
)
{
/* `gen` is an exhausted coroutine: raise an error,
except when called from gen_close(), which should
always be a silent method. */
PyErr_SetString
(
PyExc_RuntimeError
,
"cannot reuse already awaited coroutine"
);
}
else
if
(
arg
&&
!
exc
)
{
/* `gen` is an exhausted generator:
only set exception if called from send(). */
PyErr_SetNone
(
PyExc_StopIteration
);
}
return
NULL
;
}
...
...
@@ -220,7 +229,7 @@ return next yielded value or raise StopIteration.");
PyObject
*
_PyGen_Send
(
PyGenObject
*
gen
,
PyObject
*
arg
)
{
return
gen_send_ex
(
gen
,
arg
,
0
);
return
gen_send_ex
(
gen
,
arg
,
0
,
0
);
}
PyDoc_STRVAR
(
close_doc
,
...
...
@@ -292,7 +301,7 @@ gen_close(PyGenObject *gen, PyObject *args)
}
if
(
err
==
0
)
PyErr_SetNone
(
PyExc_GeneratorExit
);
retval
=
gen_send_ex
(
gen
,
Py_None
,
1
);
retval
=
gen_send_ex
(
gen
,
Py_None
,
1
,
1
);
if
(
retval
)
{
char
*
msg
=
"generator ignored GeneratorExit"
;
if
(
PyCoro_CheckExact
(
gen
))
...
...
@@ -336,7 +345,7 @@ gen_throw(PyGenObject *gen, PyObject *args)
gen
->
gi_running
=
0
;
Py_DECREF
(
yf
);
if
(
err
<
0
)
return
gen_send_ex
(
gen
,
Py_None
,
1
);
return
gen_send_ex
(
gen
,
Py_None
,
1
,
0
);
goto
throw_here
;
}
if
(
PyGen_CheckExact
(
yf
))
{
...
...
@@ -369,10 +378,10 @@ gen_throw(PyGenObject *gen, PyObject *args)
/* Termination repetition of YIELD_FROM */
gen
->
gi_frame
->
f_lasti
++
;
if
(
_PyGen_FetchStopIterationValue
(
&
val
)
==
0
)
{
ret
=
gen_send_ex
(
gen
,
val
,
0
);
ret
=
gen_send_ex
(
gen
,
val
,
0
,
0
);
Py_DECREF
(
val
);
}
else
{
ret
=
gen_send_ex
(
gen
,
Py_None
,
1
);
ret
=
gen_send_ex
(
gen
,
Py_None
,
1
,
0
);
}
}
return
ret
;
...
...
@@ -426,7 +435,7 @@ throw_here:
}
PyErr_Restore
(
typ
,
val
,
tb
);
return
gen_send_ex
(
gen
,
Py_None
,
1
);
return
gen_send_ex
(
gen
,
Py_None
,
1
,
0
);
failed_throw:
/* Didn't use our arguments, so restore their original refcounts */
...
...
@@ -440,7 +449,7 @@ failed_throw:
static
PyObject
*
gen_iternext
(
PyGenObject
*
gen
)
{
return
gen_send_ex
(
gen
,
NULL
,
0
);
return
gen_send_ex
(
gen
,
NULL
,
0
,
0
);
}
/*
...
...
@@ -901,13 +910,13 @@ coro_wrapper_dealloc(PyCoroWrapper *cw)
static
PyObject
*
coro_wrapper_iternext
(
PyCoroWrapper
*
cw
)
{
return
gen_send_ex
((
PyGenObject
*
)
cw
->
cw_coroutine
,
NULL
,
0
);
return
gen_send_ex
((
PyGenObject
*
)
cw
->
cw_coroutine
,
NULL
,
0
,
0
);
}
static
PyObject
*
coro_wrapper_send
(
PyCoroWrapper
*
cw
,
PyObject
*
arg
)
{
return
gen_send_ex
((
PyGenObject
*
)
cw
->
cw_coroutine
,
arg
,
0
);
return
gen_send_ex
((
PyGenObject
*
)
cw
->
cw_coroutine
,
arg
,
0
,
0
);
}
static
PyObject
*
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment