diff --git a/Objects/genobject.c b/Objects/genobject.c index 4038cdd8dce42f..452e2c4528315d 100644 --- a/Objects/genobject.c +++ b/Objects/genobject.c @@ -173,6 +173,15 @@ gen_send_ex(PyGenObject *gen, PyObject *arg, int exc, int closing) PyFrameObject *f = gen->gi_frame; PyObject *result; +#ifdef STACKLESS + if (gen->gi_running && exc && closing) { + /* + * Do not close a running generator. + * See Stackless issue 190. + */ + return NULL; + } +#endif if (gen->gi_running) { const char *msg = "generator already executing"; if (PyCoro_CheckExact(gen)) { diff --git a/Stackless/changelog.txt b/Stackless/changelog.txt index 862586825a555f..f4cc772bfabd30 100644 --- a/Stackless/changelog.txt +++ b/Stackless/changelog.txt @@ -9,6 +9,11 @@ What's New in Stackless 3.X.X? *Release date: 20XX-XX-XX* +- https://github.com/stackless-dev/stackless/issues/190 + Silently ignore attempts to close a running generator, coroutine or + asynchronous generator. This avoids spurious error messages, if such an + object is deallocated as part of a paused, restorable tasklet. + - https://github.com/stackless-dev/stackless/issues/193 Fix the macros PyTasklet_CheckExact(op) and PyChannel_CheckExact(op). Previously they contained a reference to a non existing variable. diff --git a/Stackless/unittests/test_generator.py b/Stackless/unittests/test_generator.py index 90ec6feaeb6453..79d5cb40ccbfac 100644 --- a/Stackless/unittests/test_generator.py +++ b/Stackless/unittests/test_generator.py @@ -8,7 +8,7 @@ import sys from support import test_main # @UnusedImport -from support import StacklessTestCase +from support import StacklessTestCase, captured_stderr def f(): @@ -37,6 +37,74 @@ def testSimpleLeakage(self): if len(leakage): self.failUnless(len(leakage) == 0, "Leaked %s" % repr(leakage)) + def run_GC_test(self, task, arg): + tasklet = stackless.tasklet(task)(arg) + tasklet.run() + if False: # To test the generator / coroutine / async gen + tasklet.run() + self.assertFalse(tasklet.alive) + return + self.assertTrue(tasklet.paused) + tasklet.tempval = None + with captured_stderr() as stringio: + # must not throw or output + if tasklet.restorable: + tasklet.bind(None) + # make sure, that t=None kills the last reference + self.assertEqual(sys.getrefcount(tasklet), 2) + tasklet = None + self.assertEqual(stringio.getvalue(), "") + + def testGCRunningGenerator(self): + def gen(): + try: + # print("gen nesting level: ", stackless.current.nesting_level) + stackless.schedule_remove() + yield 1 + except: # @IgnorePep8 + # print("exception in gen:", sys.exc_info()) + raise + + def task(generator): + l = [i for i in generator] + self.assertListEqual(l, [1]) + + self.run_GC_test(task,gen()) + + def testGCRunningCoroutine(self): + async def coro(): + try: + # print("coro nesting level: ", stackless.current.nesting_level) + stackless.schedule_remove() + except: # @IgnorePep8 + # print("exception in coro:", sys.exc_info()) + raise + + def task(c): + self.assertRaises(StopIteration, c.send, None) + + self.run_GC_test(task, coro()) + + def testGCRunningAsyncGen(self): + async def asyncgen(): + try: + # print("asyncgen nesting level: ", stackless.current.nesting_level) + stackless.schedule_remove() + except: # @IgnorePep8 + # print("exception in asyncgen:", sys.exc_info()) + raise + yield 100 + + def task(ag): + c = ag.__anext__() + with self.assertRaises(StopIteration) as cm: + c.send(None) + self.assertEqual(cm.exception.value, 100) + c = ag.__anext__() + self.assertRaises(StopAsyncIteration, c.send, None) + + self.run_GC_test(task, asyncgen()) + class TestGeneratorWrapper(StacklessTestCase): def test_run_wrap_generator(self):