Skip to content

Commit 04e70d1

Browse files
committed
Issue #17807: Generators can now be finalized even when they are part of a reference cycle.
1 parent 070cb3c commit 04e70d1

File tree

8 files changed

+334
-244
lines changed

8 files changed

+334
-244
lines changed

Include/frameobject.h

+9
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ typedef struct _frame {
3636
non-generator frames. See the save_exc_state and swap_exc_state
3737
functions in ceval.c for details of their use. */
3838
PyObject *f_exc_type, *f_exc_value, *f_exc_traceback;
39+
/* Borrowed referenced to a generator, or NULL */
40+
PyObject *f_gen;
3941

4042
PyThreadState *f_tstate;
4143
int f_lasti; /* Last instruction if called */
@@ -84,6 +86,13 @@ PyAPI_FUNC(void) _PyFrame_DebugMallocStats(FILE *out);
8486
/* Return the line of code the frame is currently executing. */
8587
PyAPI_FUNC(int) PyFrame_GetLineNumber(PyFrameObject *);
8688

89+
/* Generator support */
90+
PyAPI_FUNC(PyObject *) _PyFrame_YieldingFrom(PyFrameObject *);
91+
PyAPI_FUNC(PyObject *) _PyFrame_GeneratorSend(PyFrameObject *, PyObject *, int exc);
92+
PyAPI_FUNC(PyObject *) _PyFrame_Finalize(PyFrameObject *);
93+
PyAPI_FUNC(int) _PyFrame_CloseIterator(PyObject *);
94+
95+
8796
#ifdef __cplusplus
8897
}
8998
#endif

Include/genobject.h

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ PyAPI_DATA(PyTypeObject) PyGen_Type;
3333
#define PyGen_CheckExact(op) (Py_TYPE(op) == &PyGen_Type)
3434

3535
PyAPI_FUNC(PyObject *) PyGen_New(struct _frame *);
36+
/* Deprecated, kept for backwards compatibility. */
3637
PyAPI_FUNC(int) PyGen_NeedsFinalizing(PyGenObject *);
3738
PyAPI_FUNC(int) _PyGen_FetchStopIterationValue(PyObject **);
3839
PyObject *_PyGen_Send(PyGenObject *, PyObject *);

Lib/test/test_generators.py

+53
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,55 @@
1+
import gc
2+
import sys
3+
import unittest
4+
import weakref
5+
6+
from test import support
7+
8+
9+
class FinalizationTest(unittest.TestCase):
10+
11+
def test_frame_resurrect(self):
12+
# A generator frame can be resurrected by a generator's finalization.
13+
def gen():
14+
nonlocal frame
15+
try:
16+
yield
17+
finally:
18+
frame = sys._getframe()
19+
20+
g = gen()
21+
wr = weakref.ref(g)
22+
next(g)
23+
del g
24+
support.gc_collect()
25+
self.assertIs(wr(), None)
26+
self.assertTrue(frame)
27+
del frame
28+
support.gc_collect()
29+
30+
def test_refcycle(self):
31+
# A generator caught in a refcycle gets finalized anyway.
32+
old_garbage = gc.garbage[:]
33+
finalized = False
34+
def gen():
35+
nonlocal finalized
36+
try:
37+
g = yield
38+
yield 1
39+
finally:
40+
finalized = True
41+
42+
g = gen()
43+
next(g)
44+
g.send(g)
45+
self.assertGreater(sys.getrefcount(g), 2)
46+
self.assertFalse(finalized)
47+
del g
48+
support.gc_collect()
49+
self.assertTrue(finalized)
50+
self.assertEqual(gc.garbage, old_garbage)
51+
52+
153
tutorial_tests = """
254
Let's try a simple generator:
355
@@ -1880,6 +1932,7 @@ def printsolution(self, x):
18801932
# so this works as expected in both ways of running regrtest.
18811933
def test_main(verbose=None):
18821934
from test import support, test_generators
1935+
support.run_unittest(__name__)
18831936
support.run_doctest(test_generators, verbose)
18841937

18851938
# This part isn't needed for regrtest, but for running the test directly.

Lib/test/test_sys.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -764,7 +764,7 @@ class C(object): pass
764764
nfrees = len(x.f_code.co_freevars)
765765
extras = x.f_code.co_stacksize + x.f_code.co_nlocals +\
766766
ncells + nfrees - 1
767-
check(x, vsize('12P3i' + CO_MAXBLOCKS*'3i' + 'P' + extras*'P'))
767+
check(x, vsize('13P3i' + CO_MAXBLOCKS*'3i' + 'P' + extras*'P'))
768768
# function
769769
def func(): pass
770770
check(func, size('12P'))

Misc/NEWS

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ What's New in Python 3.4.0 Alpha 1?
1010
Core and Builtins
1111
-----------------
1212

13+
- Issue #17807: Generators can now be finalized even when they are part of
14+
a reference cycle.
15+
1316
- Issue #1545463: At shutdown, defer finalization of codec modules so
1417
that stderr remains usable.
1518

Modules/gcmodule.c

+1-4
Original file line numberDiff line numberDiff line change
@@ -524,10 +524,7 @@ untrack_dicts(PyGC_Head *head)
524524
static int
525525
has_finalizer(PyObject *op)
526526
{
527-
if (PyGen_CheckExact(op))
528-
return PyGen_NeedsFinalizing((PyGenObject *)op);
529-
else
530-
return op->ob_type->tp_del != NULL;
527+
return op->ob_type->tp_del != NULL;
531528
}
532529

533530
/* Move the objects in unreachable with __del__ methods into `finalizers`.

0 commit comments

Comments
 (0)