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

Commit f2fa3aa

Browse files
Anselm KruisAnselm Kruis
Anselm Kruis
authored and
Anselm Kruis
committed
Stackless issue #190: Do not close running (async) generators or coroutines (#195)
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. (cherry picked from commit 905d0ef)
1 parent 427c5a0 commit f2fa3aa

File tree

3 files changed

+84
-1
lines changed

3 files changed

+84
-1
lines changed

Objects/genobject.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,15 @@ gen_send_ex(PyGenObject *gen, PyObject *arg, int exc, int closing)
143143
PyFrameObject *f = gen->gi_frame;
144144
PyObject *result;
145145

146+
#ifdef STACKLESS
147+
if (gen->gi_running && exc && closing) {
148+
/*
149+
* Do not close a running generator.
150+
* See Stackless issue 190.
151+
*/
152+
return NULL;
153+
}
154+
#endif
146155
if (gen->gi_running) {
147156
char *msg = "generator already executing";
148157
if (PyCoro_CheckExact(gen)) {

Stackless/changelog.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ What's New in Stackless 3.X.X?
99

1010
*Release date: 20XX-XX-XX*
1111

12+
- https://github.com/stackless-dev/stackless/issues/190
13+
Silently ignore attempts to close a running generator, coroutine or
14+
asynchronous generator. This avoids spurious error messages, if such an
15+
object is deallocated as part of a paused, restorable tasklet.
16+
1217
- https://github.com/stackless-dev/stackless/issues/206
1318
Fix the error handling of the Python implementation of function-object
1419
pickling.

Stackless/unittests/test_generator.py

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
import types
66
import pickle
77
import copyreg
8+
import sys
89

910
from support import test_main # @UnusedImport
10-
from support import StacklessTestCase
11+
from support import StacklessTestCase, captured_stderr
1112

1213

1314
def f():
@@ -36,6 +37,74 @@ def testSimpleLeakage(self):
3637
if len(leakage):
3738
self.failUnless(len(leakage) == 0, "Leaked %s" % repr(leakage))
3839

40+
def run_GC_test(self, task, arg):
41+
tasklet = stackless.tasklet(task)(arg)
42+
tasklet.run()
43+
if False: # To test the generator / coroutine / async gen
44+
tasklet.run()
45+
self.assertFalse(tasklet.alive)
46+
return
47+
self.assertTrue(tasklet.paused)
48+
tasklet.tempval = None
49+
with captured_stderr() as stringio:
50+
# must not throw or output
51+
if tasklet.restorable:
52+
tasklet.bind(None)
53+
# make sure, that t=None kills the last reference
54+
self.assertEqual(sys.getrefcount(tasklet), 2)
55+
tasklet = None
56+
self.assertEqual(stringio.getvalue(), "")
57+
58+
def testGCRunningGenerator(self):
59+
def gen():
60+
try:
61+
# print("gen nesting level: ", stackless.current.nesting_level)
62+
stackless.schedule_remove()
63+
yield 1
64+
except: # @IgnorePep8
65+
# print("exception in gen:", sys.exc_info())
66+
raise
67+
68+
def task(generator):
69+
l = [i for i in generator]
70+
self.assertListEqual(l, [1])
71+
72+
self.run_GC_test(task,gen())
73+
74+
def testGCRunningCoroutine(self):
75+
async def coro():
76+
try:
77+
# print("coro nesting level: ", stackless.current.nesting_level)
78+
stackless.schedule_remove()
79+
except: # @IgnorePep8
80+
# print("exception in coro:", sys.exc_info())
81+
raise
82+
83+
def task(c):
84+
self.assertRaises(StopIteration, c.send, None)
85+
86+
self.run_GC_test(task, coro())
87+
88+
def testGCRunningAsyncGen(self):
89+
async def asyncgen():
90+
try:
91+
# print("asyncgen nesting level: ", stackless.current.nesting_level)
92+
stackless.schedule_remove()
93+
except: # @IgnorePep8
94+
# print("exception in asyncgen:", sys.exc_info())
95+
raise
96+
yield 100
97+
98+
def task(ag):
99+
c = ag.__anext__()
100+
with self.assertRaises(StopIteration) as cm:
101+
c.send(None)
102+
self.assertEqual(cm.exception.value, 100)
103+
c = ag.__anext__()
104+
self.assertRaises(StopAsyncIteration, c.send, None)
105+
106+
self.run_GC_test(task, asyncgen())
107+
39108

40109
class TestGeneratorWrapper(StacklessTestCase):
41110
def test_run_wrap_generator(self):

0 commit comments

Comments
 (0)