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

Commit 60bbffd

Browse files
author
Anselm Kruis
committed
Issue #112: Prepare Stackless 3.5, enable pickling of coroutines
Coroutines are a new feature of Python 3.5. Because their implementation is based on generators, we can pickle and unpickle them easily. https://bitbucket.org/stackless-dev/stackless/issues/112
1 parent 3269354 commit 60bbffd

File tree

3 files changed

+104
-7
lines changed

3 files changed

+104
-7
lines changed

Stackless/changelog.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ What's New in Stackless 3.X.X?
8787
Adapt Stackless to Python 3.5.
8888
- PyGenObject got two additional fields
8989
- Skip the CPython test case test.test_pickle.*.test_local_lookup_error
90+
- Enable the pickling of coroutine objects
9091

9192
- https://bitbucket.org/stackless-dev/stackless/issues/111
9293
Restore the Python ABI function PyGen_New(). Previously Stackless named this

Stackless/pickling/prickelpit.c

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1688,16 +1688,20 @@ static int init_methodwrappertype(void)
16881688

16891689
/******************************************************
16901690
1691-
pickling of generators
1691+
pickling of generators and coroutines
16921692
16931693
******************************************************/
16941694

16951695
static PyTypeObject wrap_PyGen_Type;
1696-
/* Used to initialize a generator created by gen_new. */
1697-
static PyFrameObject *gen_exhausted_frame;
1696+
static PyTypeObject wrap_PyCoro_Type;
1697+
1698+
/* Used to initialize a generator created by gen_new.
1699+
Also assert, that the size of generator and coroutines is equal. */
1700+
static PyFrameObject *gen_exhausted_frame = \
1701+
Py_BUILD_ASSERT_EXPR(sizeof(PyGenObject) == sizeof(PyCoroObject)); /* value is 0 */
16981702

16991703
static PyObject *
1700-
gen_reduce(PyGenObject *gen)
1704+
_gen_reduce(PyTypeObject *type, PyGenObject *gen)
17011705
{
17021706
PyObject *tup;
17031707
PyObject *frame_reducer = (PyObject *)gen->gi_frame;
@@ -1712,7 +1716,7 @@ gen_reduce(PyGenObject *gen)
17121716
if (frame_reducer == NULL)
17131717
return NULL;
17141718
tup = Py_BuildValue("(O()(OiOO))",
1715-
&wrap_PyGen_Type,
1719+
type,
17161720
frame_reducer,
17171721
gen->gi_running,
17181722
gen->gi_name,
@@ -1722,6 +1726,12 @@ gen_reduce(PyGenObject *gen)
17221726
return tup;
17231727
}
17241728

1729+
static PyObject *
1730+
gen_reduce(PyGenObject *gen)
1731+
{
1732+
return _gen_reduce(&wrap_PyGen_Type, gen);
1733+
}
1734+
17251735
static PyObject *
17261736
gen_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
17271737
{
@@ -1731,7 +1741,13 @@ gen_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
17311741
/* A reference to frame is stolen by PyGen_New. */
17321742
assert(gen_exhausted_frame != NULL);
17331743
assert(PyFrame_Check(gen_exhausted_frame));
1734-
gen = (PyGenObject *) PyGen_NewWithQualName(slp_ensure_new_frame(gen_exhausted_frame), NULL, NULL);
1744+
if (type == &wrap_PyGen_Type) {
1745+
gen = (PyGenObject *)PyGen_NewWithQualName(slp_ensure_new_frame(gen_exhausted_frame), NULL, NULL);
1746+
}
1747+
else {
1748+
assert(type == &wrap_PyCoro_Type);
1749+
gen = (PyGenObject *)PyCoro_New(slp_ensure_new_frame(gen_exhausted_frame), NULL, NULL);
1750+
}
17351751
if (gen == NULL)
17361752
return NULL;
17371753
Py_TYPE(gen) = type;
@@ -1897,6 +1913,22 @@ static int init_generatortype(void)
18971913
#undef initchain
18981914
#define initchain init_generatortype
18991915

1916+
static PyObject *
1917+
coro_reduce(PyGenObject *gen)
1918+
{
1919+
return _gen_reduce(&wrap_PyCoro_Type, gen);
1920+
}
1921+
1922+
MAKE_WRAPPERTYPE(PyCoro_Type, coro, "coroutine", coro_reduce,
1923+
gen_new, gen_setstate)
1924+
1925+
static int init_coroutinetype(void)
1926+
{
1927+
return init_type(&wrap_PyCoro_Type, initchain);
1928+
}
1929+
#undef initchain
1930+
#define initchain init_coroutinetype
1931+
19001932

19011933
/******************************************************
19021934

Stackless/unittests/test_pickle.py

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,60 @@ def d():
632632
self.assertListEqual([i[1:] for i in all_outerframes_orig[:l - 1]], [i[1:] for i in all_outerframes[:l - 1]])
633633

634634

635+
class TestCoroutinePickling(StacklessPickleTestCase):
636+
@types.coroutine
637+
def yield1(self, value):
638+
yield value
639+
640+
async def c(self):
641+
await self.yield1(1)
642+
643+
def test_without_pickling(self):
644+
c = self.c()
645+
self.assertIsInstance(c, types.CoroutineType)
646+
self.assertIsNone(c.cr_await)
647+
self.assertIsNotNone(c.cr_frame)
648+
self.assertEqual(c.send(None), 1)
649+
self.assertIsNotNone(c.cr_await)
650+
self.assertIsNotNone(c.cr_frame)
651+
self.assertRaises(StopIteration, c.send, None)
652+
self.assertIsNone(c.cr_await)
653+
self.assertIsNone(c.cr_frame)
654+
self.assertRaisesRegex(RuntimeError, "cannot reuse already awaited coroutine", c.send, None)
655+
656+
def test_pickling1(self):
657+
c = self.c()
658+
p = self.dumps(c)
659+
c.send(None)
660+
c = self.loads(p)
661+
self.assertIsInstance(c, types.CoroutineType)
662+
self.assertIsNone(c.cr_await)
663+
self.assertIsNotNone(c.cr_frame)
664+
self.assertEqual(c.send(None), 1)
665+
self.assertRaises(StopIteration, c.send, None)
666+
667+
def test_pickling2(self):
668+
c = self.c()
669+
self.assertEqual(c.send(None), 1)
670+
p = self.dumps(c)
671+
c = self.loads(p)
672+
self.assertIsInstance(c, types.CoroutineType)
673+
self.assertIsNotNone(c.cr_await)
674+
self.assertIsNotNone(c.cr_frame)
675+
self.assertRaises(StopIteration, c.send, None)
676+
677+
def test_pickling3(self):
678+
c = self.c()
679+
self.assertEqual(c.send(None), 1)
680+
self.assertRaises(StopIteration, c.send, None)
681+
p = self.dumps(c)
682+
c = self.loads(p)
683+
self.assertIsInstance(c, types.CoroutineType)
684+
self.assertIsNone(c.cr_await)
685+
self.assertIsNone(c.cr_frame)
686+
self.assertRaisesRegex(RuntimeError, "cannot reuse already awaited coroutine", c.send, None)
687+
688+
635689
class TestCopy(StacklessTestCase):
636690
ITERATOR_TYPE = type(iter("abc"))
637691

@@ -653,6 +707,7 @@ def _test(self, obj, *attributes, **kw):
653707
# it is a shallow copy, therefore the attributes should
654708
# refer to the same objects
655709
self.assertIs(value_c, value_obj)
710+
return c
656711

657712
def test_module_stackless(self):
658713
# test for issue 128
@@ -706,7 +761,7 @@ def test_generator(self):
706761
def g():
707762
yield 1
708763
obj = g()
709-
self._test(obj, 'gi_running', 'gi_code')
764+
self._test(obj, 'gi_running', 'gi_code', '__name__', '__qualname__')
710765

711766
def test_dict_keys(self):
712767
d = {1: 10, "a": "ABC"}
@@ -723,6 +778,15 @@ def test_dict_items(self):
723778
obj = d.items()
724779
self._test(obj)
725780

781+
def test_coroutine(self):
782+
async def c():
783+
return 1
784+
obj = c()
785+
c = self._test(obj, 'cr_running', 'cr_code', '__name__', '__qualname__')
786+
self.assertRaises(StopIteration, obj.send, None)
787+
self.assertRaises(StopIteration, c.send, None)
788+
789+
726790
if __name__ == '__main__':
727791
if not sys.argv[1:]:
728792
sys.argv.append('-v')

0 commit comments

Comments
 (0)