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

Stackless issue #190: Do not close running (async) generators or coroutines #195

Merged
merged 2 commits into from
Dec 30, 2018
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
9 changes: 9 additions & 0 deletions Objects/genobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down
5 changes: 5 additions & 0 deletions Stackless/changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
70 changes: 69 additions & 1 deletion Stackless/unittests/test_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import sys

from support import test_main # @UnusedImport
from support import StacklessTestCase
from support import StacklessTestCase, captured_stderr


def f():
Expand Down Expand Up @@ -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):
Expand Down