Skip to content

Commit 712763a

Browse files
author
Anselm Kruis
committed
Issue python#107: disable pickling of frames
This is part 2 of issue python#107. Second step. Stackless no longer pretends to be able to pickle frame objects in general. It still pickles frames as part of tasklets or traceback objects. This way Stackless adheres closely to the pickling protocol. https://bitbucket.org/stackless-dev/stackless/issues/107
1 parent 056de5a commit 712763a

File tree

9 files changed

+132
-13
lines changed

9 files changed

+132
-13
lines changed

Lib/stackless.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,48 @@ def transmogrify():
7979
from copyreg import pickle
8080
for name in dir(_wrap):
8181
cls = getattr(_wrap, name, None)
82-
if isinstance(cls, type):
82+
if isinstance(cls, type) and cls.__name__ != "frame":
8383
pickle(cls.__bases__[0], cls.__reduce__)
8484

85+
try:
86+
# in case of reload(stackless)
87+
reduce_frame = _wrap.reduce_frame
88+
except AttributeError:
89+
from weakref import WeakValueDictionary
90+
91+
wrap_set_reduce_frame = _wrap.set_reduce_frame
92+
93+
def set_reduce_frame(func):
94+
wrap_set_reduce_frame(func)
95+
_wrap.reduce_frame = func
96+
97+
_wrap.set_reduce_frame = set_reduce_frame
98+
99+
wrap_frame__reduce = _wrap.frame.__reduce__
100+
cache = WeakValueDictionary()
101+
102+
class _Frame_Wrapper(object):
103+
"""Wrapper for frames to be pickled"""
104+
__slots__ = ('__weakref__', 'frame')
105+
106+
@classmethod
107+
def reduce_frame(cls, frame):
108+
oid = id(frame)
109+
try:
110+
return cache[oid]
111+
except KeyError:
112+
cache[oid] = reducer = cls(frame)
113+
return reducer
114+
115+
def __init__(self, frame):
116+
self.frame = frame
117+
118+
def __reduce__(self):
119+
return wrap_frame__reduce(self.frame)
120+
reduce_frame = _Frame_Wrapper.reduce_frame
121+
_wrap.set_reduce_frame(reduce_frame)
122+
123+
85124
class StacklessModuleType(types.ModuleType):
86125

87126
def current(self):

Stackless/core/stackless_impl.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,8 @@ PyObject * slp_restore_exception(PyFrameObject *f, int exc, PyObject *retval);
388388
PyObject * slp_restore_tracing(PyFrameObject *f, int exc, PyObject *retval);
389389
/* other eval_frame functions from Objects/typeobject.c */
390390
PyObject * slp_tp_init_callback(PyFrameObject *f, int exc, PyObject *retval);
391+
/* functions related to pickling */
392+
PyObject * slp_reduce_frame(PyFrameObject * frame);
391393

392394
/* rebirth of software stack avoidance */
393395

Stackless/module/taskletobject.c

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,14 @@ tasklet_reduce(PyTaskletObject * t)
442442
goto err_exit;
443443
}
444444
if (append_frame) {
445-
if (PyList_Append(lis, (PyObject *) f)) goto err_exit;
445+
int ret;
446+
PyObject * frame_reducer = slp_reduce_frame(f);
447+
if (frame_reducer == NULL)
448+
goto err_exit;
449+
ret = PyList_Append(lis, frame_reducer);
450+
Py_DECREF(frame_reducer);
451+
if (ret)
452+
goto err_exit;
446453
}
447454
f = f->f_back;
448455
}
@@ -1486,8 +1493,8 @@ tasklet_get_frame(PyTaskletObject *task)
14861493
{
14871494
PyObject *ret = (PyObject*) PyTasklet_GetFrame(task);
14881495
if (ret)
1489-
return ret;
1490-
Py_RETURN_NONE;
1496+
return ret;
1497+
Py_RETURN_NONE;
14911498
}
14921499

14931500
PyObject *

Stackless/pickling/prickelpit.c

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,36 @@ static struct _typeobject wrap_##type = { \
179179
};
180180

181181
static PyObject *types_mod = NULL;
182+
static PyObject *reduce_frame_func = NULL;
183+
184+
PyDoc_STRVAR(set_reduce_frame__doc__,
185+
"set_reduce_frame(func) -- set the function used to reduce frames during pickling.\n"
186+
"The function takes a frame as its sole argument and must return a pickleable object.\n");
187+
188+
static PyObject *
189+
set_reduce_frame(PyObject *self, PyObject *func)
190+
{
191+
if (func == Py_None) {
192+
Py_CLEAR(reduce_frame_func);
193+
} else {
194+
if (!PyCallable_Check(func)) {
195+
TYPE_ERROR("func must be callable", NULL);
196+
}
197+
Py_INCREF(func);
198+
Py_XSETREF(reduce_frame_func, func);
199+
}
200+
Py_RETURN_NONE;
201+
}
202+
203+
PyObject *
204+
slp_reduce_frame(PyFrameObject * frame) {
205+
if (!PyFrame_Check(frame) || reduce_frame_func == NULL) {
206+
Py_INCREF(frame);
207+
return (PyObject *)frame;
208+
}
209+
return PyObject_CallFunctionObjArgs(reduce_frame_func, (PyObject *)frame, NULL);
210+
}
211+
182212

183213
static struct PyMethodDef _new_methoddef[] = {
184214
{"__new__", (PyCFunction)_new_wrapper, METH_VARARGS | METH_KEYWORDS,
@@ -1156,13 +1186,19 @@ static PyObject *
11561186
tb_reduce(tracebackobject * tb)
11571187
{
11581188
PyObject *tup = NULL;
1159-
char *fmt = "(O()(OiiO))";
1189+
PyObject *frame_reducer;
1190+
const char *fmt = "(O()(OiiO))";
11601191

11611192
if (tb->tb_next == NULL)
11621193
fmt = "(O()(Oii))";
1194+
frame_reducer = slp_reduce_frame(tb->tb_frame);
1195+
if (frame_reducer == NULL)
1196+
return NULL;
1197+
11631198
tup = Py_BuildValue(fmt,
11641199
&wrap_PyTraceBack_Type,
1165-
tb->tb_frame, tb->tb_lasti, tb->tb_lineno, tb->tb_next);
1200+
frame_reducer, tb->tb_lasti, tb->tb_lineno, tb->tb_next);
1201+
Py_DECREF(frame_reducer);
11661202
return tup;
11671203
}
11681204

@@ -2242,11 +2278,16 @@ static PyObject *
22422278
gen_reduce(PyGenObject *gen)
22432279
{
22442280
PyObject *tup;
2281+
PyObject *frame_reducer;
2282+
frame_reducer = slp_reduce_frame(gen->gi_frame);
2283+
if (frame_reducer == NULL)
2284+
return NULL;
22452285
tup = Py_BuildValue("(O()(Oi))",
22462286
&wrap_PyGen_Type,
2247-
gen->gi_frame,
2287+
frame_reducer,
22482288
gen->gi_running
22492289
);
2290+
Py_DECREF(frame_reducer);
22502291
return tup;
22512292
}
22522293

@@ -2458,22 +2499,29 @@ static int
24582499
_wrapmodule_traverse(PyObject *self, visitproc visit, void *arg)
24592500
{
24602501
Py_VISIT(gen_exhausted_frame);
2502+
Py_VISIT(reduce_frame_func);
24612503
return 0;
24622504
}
24632505

24642506
static int
24652507
_wrapmodule_clear(PyObject *self)
24662508
{
24672509
Py_CLEAR(gen_exhausted_frame);
2510+
Py_CLEAR(reduce_frame_func);
24682511
return 0;
24692512
}
24702513

2514+
static PyMethodDef _wrapmodule_methods[] = {
2515+
{"set_reduce_frame", set_reduce_frame, METH_O, set_reduce_frame__doc__},
2516+
{NULL, NULL} /* sentinel */
2517+
};
2518+
24712519
static struct PyModuleDef _wrapmodule = {
24722520
PyModuleDef_HEAD_INIT,
24732521
"_stackless._wrap",
24742522
NULL,
24752523
-1,
2476-
NULL,
2524+
_wrapmodule_methods,
24772525
NULL,
24782526
_wrapmodule_traverse,
24792527
_wrapmodule_clear,

Stackless/unittests/support.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,14 @@ def helper():
747747
return result
748748

749749

750+
def get_reduce_frame():
751+
"""counterpart to stackless._wrap.set_reduce_frame()
752+
753+
Only for testing!
754+
"""
755+
return getattr(stackless._wrap, "reduce_frame", None)
756+
757+
750758
def test_main():
751759
"""Main function for the CPython :mod:`test.regrtest` test driver.
752760

Stackless/unittests/test_defects.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414

1515
from stackless import _test_nostacklesscall as apply_not_stackless
1616
from support import test_main # @UnusedImport
17-
from support import StacklessTestCase, captured_stderr, require_one_thread
17+
from support import (StacklessTestCase, captured_stderr, require_one_thread,
18+
get_reduce_frame)
1819

1920

2021
"""
@@ -236,6 +237,7 @@ def testCrasher(self):
236237
frameType = type(frame)
237238
while frame and frame.f_back:
238239
frame = frame.f_back
240+
frame = get_reduce_frame()(frame)
239241
p = pickle.dumps(frame, -1)
240242
frame = None
241243
frame = pickle.loads(p)

Stackless/unittests/test_pickle.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from stackless import schedule, tasklet, stackless
88

99
from support import test_main # @UnusedImport
10-
from support import StacklessTestCase, StacklessPickleTestCase
10+
from support import StacklessTestCase, StacklessPickleTestCase, get_reduce_frame
1111

1212

1313
# because test runner instances in the testsuite contain copies of the old stdin/stdout thingies,
@@ -494,6 +494,14 @@ def testFunctionModulePreservation(self):
494494

495495

496496
class TestFramePickling(StacklessTestCase):
497+
def test_get_set_reduce_frame(self):
498+
# test setting / getting the reduce frame function
499+
rf = get_reduce_frame()
500+
self.assertTrue(callable(rf))
501+
stackless._wrap.set_reduce_frame(None)
502+
self.assertIsNone(get_reduce_frame())
503+
stackless._wrap.set_reduce_frame(rf)
504+
self.assertIs(get_reduce_frame(), rf)
497505

498506
def testLocalplus(self):
499507
result = []
@@ -607,6 +615,7 @@ def d():
607615
p = self.dumps(tb)
608616
tb2 = self.loads(p)
609617
# basics
618+
innerframes_orig = inspect.getinnerframes(tb)
610619
self.assertIs(type(tb), type(tb2))
611620
self.assertIsNot(tb, tb2)
612621
innerframes = inspect.getinnerframes(tb2)

Stackless/unittests/test_thread.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,9 @@ def to_current_thread(self, task):
235235
frameList[i] = newFrame
236236
# rebind the task
237237
task = reducedTask[0](*reducedTask[1])
238+
for i in range(len(reducedTask[2][3])):
239+
if not isinstance(reducedTask[2][3][i], stackless.cframe):
240+
reducedTask[2][3][i] = reducedTask[2][3][i].frame
238241
task.__setstate__(reducedTask[2])
239242
return task
240243

Stackless/unittests/test_tstate.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from stackless import *
77

88
from support import test_main # @UnusedImport
9-
from support import StacklessTestCase
9+
from support import StacklessTestCase, get_reduce_frame
1010

1111
# import os
1212
# def debug():
@@ -332,9 +332,10 @@ def testReduceOfTracingState(self):
332332
return
333333

334334
# test if the tracing cframe is present / not present
335-
self.assertListEqual(reduced_tasklet1[2][3], [frame])
335+
reduce_frame = get_reduce_frame()
336+
self.assertListEqual(reduced_tasklet1[2][3], [reduce_frame(frame)])
336337
self.assertEquals(len(reduced_tasklet2[2][3]), 2)
337-
self.assertIs(reduced_tasklet2[2][3][0], frame)
338+
self.assertIs(reduced_tasklet2[2][3][0], reduce_frame(frame))
338339
self.assertIsInstance(reduced_tasklet2[2][3][1], stackless.cframe)
339340

340341
cf = reduced_tasklet2[2][3][1]

0 commit comments

Comments
 (0)