diff -r 48e2f5915d49 Lib/test/test_coroutines.py --- a/Lib/test/test_coroutines.py Tue Jan 12 00:09:43 2016 +0200 +++ b/Lib/test/test_coroutines.py Mon Jan 11 18:48:50 2016 -0500 @@ -569,6 +569,135 @@ "coroutine ignored GeneratorExit"): c.close() + def test_func_15(self): + # See http://bugs.python.org/issue25887 for details + + async def send(): + return 'spam' + async def reader(coro): + return await coro + + spammer = send() + + with self.assertRaisesRegex(StopIteration, 'spam'): + reader(spammer).send(None) + + with self.assertRaisesRegex(RuntimeError, + 'coroutine was already awaited'): + reader(spammer).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, + 'coroutine was already awaited'): + reader.send(None) + + with self.assertRaisesRegex(RuntimeError, + 'coroutine was already awaited'): + 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, + 'coroutine was already awaited'): + coro.send(None) + + with self.assertRaisesRegex(RuntimeError, + 'coroutine was already awaited'): + coro.throw(Exception('wat')) + + def test_func_18(self): + # See http://bugs.python.org/issue25887 for details + + async def coroutine(): + return 'spam' + + coro = coroutine() + awaitable = coro.__await__() + it = iter(awaitable) + + with self.assertRaisesRegex(StopIteration, 'spam'): + it.send(None) + + with self.assertRaisesRegex(RuntimeError, + 'coroutine was already awaited'): + it.send(None) + + with self.assertRaisesRegex(RuntimeError, + 'coroutine was already awaited'): + next(it) + + with self.assertRaisesRegex(RuntimeError, + 'coroutine was already awaited'): + 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() + it.close() + 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(): diff -r 48e2f5915d49 Objects/genobject.c --- a/Objects/genobject.c Tue Jan 12 00:09:43 2016 +0200 +++ b/Objects/genobject.c Mon Jan 11 18:48:50 2016 -0500 @@ -78,7 +78,7 @@ } 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 @@ 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 + aloways be a silent method. */ + PyErr_SetString( + PyExc_RuntimeError, + "coroutine was already awaited"); + } 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 @@ 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 @@ } 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->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 @@ /* 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 @@ } 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 @@ static PyObject * gen_iternext(PyGenObject *gen) { - return gen_send_ex(gen, NULL, 0); + return gen_send_ex(gen, NULL, 0, 0); } /* @@ -893,13 +902,13 @@ 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 *