Skip to content

Commit be5b7fb

Browse files
author
Anselm Kruis
committed
merge 3.5-slp (Stackless issue python#112: enable pickling of coroutines)
2 parents c615848 + bd76867 commit be5b7fb

File tree

4 files changed

+106
-7
lines changed

4 files changed

+106
-7
lines changed

Lib/test/test_coroutines.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1905,6 +1905,7 @@ async def f():
19051905
run_async(f()),
19061906
([], {1: 1, 2: 2, 3: 3}))
19071907

1908+
@unittest.skipIf(support.stackless, "Stackless can copy coroutines")
19081909
def test_copy(self):
19091910
async def func(): pass
19101911
coro = func()
@@ -1918,6 +1919,7 @@ async def func(): pass
19181919
finally:
19191920
aw.close()
19201921

1922+
@unittest.skipIf(support.stackless, "Stackless can pickle coroutines")
19211923
def test_pickle(self):
19221924
async def func(): pass
19231925
coro = func()

Stackless/changelog.txt

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

112113
- https://bitbucket.org/stackless-dev/stackless/issues/111
113114
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
@@ -1694,16 +1694,20 @@ static int init_methodwrappertype(void)
16941694

16951695
/******************************************************
16961696
1697-
pickling of generators
1697+
pickling of generators and coroutines
16981698
16991699
******************************************************/
17001700

17011701
static PyTypeObject wrap_PyGen_Type;
1702-
/* Used to initialize a generator created by gen_new. */
1703-
static PyFrameObject *gen_exhausted_frame;
1702+
static PyTypeObject wrap_PyCoro_Type;
1703+
1704+
/* Used to initialize a generator created by gen_new.
1705+
Also assert, that the size of generator and coroutines is equal. */
1706+
static PyFrameObject *gen_exhausted_frame = \
1707+
Py_BUILD_ASSERT_EXPR(sizeof(PyGenObject) == sizeof(PyCoroObject)); /* value is 0 */
17041708

17051709
static PyObject *
1706-
gen_reduce(PyGenObject *gen)
1710+
_gen_reduce(PyTypeObject *type, PyGenObject *gen)
17071711
{
17081712
PyObject *tup;
17091713
PyObject *frame_reducer = (PyObject *)gen->gi_frame;
@@ -1718,7 +1722,7 @@ gen_reduce(PyGenObject *gen)
17181722
if (frame_reducer == NULL)
17191723
return NULL;
17201724
tup = Py_BuildValue("(O()(OiOO))",
1721-
&wrap_PyGen_Type,
1725+
type,
17221726
frame_reducer,
17231727
gen->gi_running,
17241728
gen->gi_name,
@@ -1728,6 +1732,12 @@ gen_reduce(PyGenObject *gen)
17281732
return tup;
17291733
}
17301734

1735+
static PyObject *
1736+
gen_reduce(PyGenObject *gen)
1737+
{
1738+
return _gen_reduce(&wrap_PyGen_Type, gen);
1739+
}
1740+
17311741
static PyObject *
17321742
gen_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
17331743
{
@@ -1737,7 +1747,13 @@ gen_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
17371747
/* A reference to frame is stolen by PyGen_New. */
17381748
assert(gen_exhausted_frame != NULL);
17391749
assert(PyFrame_Check(gen_exhausted_frame));
1740-
gen = (PyGenObject *) PyGen_NewWithQualName(slp_ensure_new_frame(gen_exhausted_frame), NULL, NULL);
1750+
if (type == &wrap_PyGen_Type) {
1751+
gen = (PyGenObject *)PyGen_NewWithQualName(slp_ensure_new_frame(gen_exhausted_frame), NULL, NULL);
1752+
}
1753+
else {
1754+
assert(type == &wrap_PyCoro_Type);
1755+
gen = (PyGenObject *)PyCoro_New(slp_ensure_new_frame(gen_exhausted_frame), NULL, NULL);
1756+
}
17411757
if (gen == NULL)
17421758
return NULL;
17431759
Py_TYPE(gen) = type;
@@ -1903,6 +1919,22 @@ static int init_generatortype(void)
19031919
#undef initchain
19041920
#define initchain init_generatortype
19051921

1922+
static PyObject *
1923+
coro_reduce(PyGenObject *gen)
1924+
{
1925+
return _gen_reduce(&wrap_PyCoro_Type, gen);
1926+
}
1927+
1928+
MAKE_WRAPPERTYPE(PyCoro_Type, coro, "coroutine", coro_reduce,
1929+
gen_new, gen_setstate)
1930+
1931+
static int init_coroutinetype(void)
1932+
{
1933+
return init_type(&wrap_PyCoro_Type, initchain);
1934+
}
1935+
#undef initchain
1936+
#define initchain init_coroutinetype
1937+
19061938

19071939
/******************************************************
19081940

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)