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

Commit 9fbbf8b

Browse files
author
hg
committed
Issue #130: fix pickling of exhausted generator objects
Pickling of an exhausted generator no longer causes a segmentation fault. https://bitbucket.org/stackless-dev/stackless/issues/130 (grafted from b86bda31f4e3372bbde61179cc890af24d83ae71)
1 parent 3a45703 commit 9fbbf8b

File tree

3 files changed

+63
-3
lines changed

3 files changed

+63
-3
lines changed

Stackless/changelog.txt

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

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

12+
- https://bitbucket.org/stackless-dev/stackless/issues/130
13+
Pickling exhausted generators no longer crashes.
14+
1215
- https://bitbucket.org/stackless-dev/stackless/issues/129
1316
C-API: Calling PyTasklet_New( NULL, ...) no longer crashes.
1417

Stackless/pickling/prickelpit.c

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1707,9 +1707,15 @@ static PyObject *
17071707
gen_reduce(PyGenObject *gen)
17081708
{
17091709
PyObject *tup;
1710+
PyObject *gi_frame = (PyObject *)gen->gi_frame;
1711+
if (gi_frame == NULL) {
1712+
/* Pickle NULL as None. See gen_setstate() for the corresponding
1713+
* unpickling code. */
1714+
gi_frame = Py_None;
1715+
}
17101716
tup = Py_BuildValue("(O()(Oi))",
17111717
&wrap_PyGen_Type,
1712-
gen->gi_frame,
1718+
gi_frame,
17131719
gen->gi_running
17141720
);
17151721
return tup;
@@ -1736,12 +1742,39 @@ gen_setstate(PyObject *self, PyObject *args)
17361742
{
17371743
PyGenObject *gen = (PyGenObject *) self;
17381744
PyFrameObject *f;
1745+
PyObject *obj;
17391746
int gi_running;
17401747

17411748
if (is_wrong_type(Py_TYPE(self))) return NULL;
1742-
if (!PyArg_ParseTuple(args, "O!i:generator",
1743-
&PyFrame_Type, &f, &gi_running))
1749+
if (!PyArg_ParseTuple(args, "Oi:generator",
1750+
&obj, &gi_running))
1751+
return NULL;
1752+
1753+
if (obj == Py_None) {
1754+
/* No frame, generator is exhausted */
1755+
Py_CLEAR(gen->gi_frame);
1756+
1757+
/* Even if gi_frame is NULL, gi_code is still valid. Therefore
1758+
* I set it to the code of the exhausted frame singleton.
1759+
*/
1760+
assert(gen_exhausted_frame != NULL);
1761+
assert(PyFrame_Check(gen_exhausted_frame));
1762+
obj = (PyObject *)gen_exhausted_frame->f_code;
1763+
Py_INCREF(obj);
1764+
Py_SETREF(gen->gi_code, obj);
1765+
1766+
gen->gi_running = gi_running;
1767+
Py_TYPE(gen) = Py_TYPE(gen)->tp_base;
1768+
Py_INCREF(gen);
1769+
return (PyObject *)gen;
1770+
}
1771+
if (!PyFrame_Check(obj)) {
1772+
PyErr_SetString(PyExc_TypeError,
1773+
"invalid frame object for gen_setstate");
17441774
return NULL;
1775+
}
1776+
f = (PyFrameObject *)obj;
1777+
obj = NULL;
17451778

17461779
if (!gi_running) {
17471780
if ((f = slp_ensure_new_frame(f)) != NULL) {

Stackless/unittests/test_generator.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import gc
44
import stackless
55
import types
6+
import pickle
67

78
from support import test_main # @UnusedImport
89
from support import StacklessTestCase
@@ -51,5 +52,28 @@ def test_wrap_generator_frame_code(self):
5152
self.assertIsNot(g0.gi_frame, g1.gi_frame)
5253
self.assertEqual(g0.__name__, "exhausted_generator")
5354

55+
56+
class TestGeneratorPickling(StacklessTestCase):
57+
def test_name(self):
58+
def g():
59+
yield 1
60+
gen_orig = g()
61+
p = pickle.dumps(gen_orig)
62+
gen_new = pickle.loads(p)
63+
64+
self.assertEqual(gen_new.__name__, gen_orig.__name__)
65+
66+
def test_exhausted(self):
67+
def g():
68+
yield 1
69+
gen_orig = g()
70+
self.assertEqual(gen_orig.__next__(), 1)
71+
self.assertRaises(StopIteration, gen_orig.__next__)
72+
p = pickle.dumps(gen_orig)
73+
self.assertRaises(StopIteration, gen_orig.__next__)
74+
gen_new = pickle.loads(p)
75+
self.assertRaises(StopIteration, gen_new.__next__)
76+
77+
5478
if __name__ == '__main__':
5579
unittest.main()

0 commit comments

Comments
 (0)