Skip to content

Commit 3272cdf

Browse files
author
Anselm Kruis
committed
merge 3.4-slp (Stackless python#117, fix frame reference leaks)
2 parents a3d95de + 405a9cd commit 3272cdf

16 files changed

+705
-208
lines changed

Objects/object.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33

44
#include "Python.h"
55
#include "frameobject.h"
6+
#ifdef STACKLESS
7+
#include "core/stackless_impl.h"
8+
#endif
69

710
#ifdef __cplusplus
811
extern "C" {
@@ -1762,7 +1765,11 @@ _Py_Dealloc(PyObject *op)
17621765
{
17631766
destructor dealloc = Py_TYPE(op)->tp_dealloc;
17641767
_Py_ForgetReference(op);
1768+
#ifdef STACKLESS
1769+
SLP_WITH_VALID_CURRENT_FRAME((*dealloc)(op));
1770+
#else
17651771
(*dealloc)(op);
1772+
#endif
17661773
}
17671774

17681775
/* Print all live objects. Because PyObject_Print is called, the

Objects/typeobject.c

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6356,8 +6356,7 @@ slp_tp_init_callback(PyFrameObject *f, int exc, PyObject *retval)
63566356
cf->ob1 = NULL;
63576357
}
63586358
}
6359-
ts->frame = f;
6360-
Py_DECREF(cf);
6359+
SLP_STORE_NEXT_FRAME(ts, f);
63616360
return STACKLESS_PACK(ts, retval);
63626361
}
63636362
#endif
@@ -6369,17 +6368,23 @@ slot_tp_init(PyObject *self, PyObject *args, PyObject *kwds)
63696368
_Py_IDENTIFIER(__init__);
63706369
PyObject *meth = lookup_method(self, &PyId___init__);
63716370
PyObject *res;
6371+
#ifdef STACKLESS
6372+
PyCFrameObject *f = NULL;
6373+
#endif
63726374

63736375
if (meth == NULL)
63746376
return -1;
63756377
#ifdef STACKLESS
63766378
if (stackless) {
6377-
PyCFrameObject *f = slp_cframe_new(slp_tp_init_callback, 1);
6379+
f = slp_cframe_new(slp_tp_init_callback, 1);
63786380
if (f == NULL)
63796381
return -1;
63806382
Py_INCREF(self);
63816383
f->ob1 = self;
6382-
PyThreadState_GET()->frame = (PyFrameObject *) f;
6384+
SLP_SET_CURRENT_FRAME(PyThreadState_GET(), (PyFrameObject *) f);
6385+
/* f contains the only counted reference to current frame. This reference
6386+
* keeps the fame alive during the following PyObject_Call().
6387+
*/
63836388
}
63846389
#endif
63856390
STACKLESS_PROMOTE_ALL();
@@ -6388,10 +6393,16 @@ slot_tp_init(PyObject *self, PyObject *args, PyObject *kwds)
63886393
Py_DECREF(meth);
63896394
#ifdef STACKLESS
63906395
if (stackless && !STACKLESS_UNWINDING(res)) {
6391-
/* required, because added a C-frame */
6392-
STACKLESS_PACK(PyThreadState_GET(), res);
6396+
/* required, because we added a C-frame */
6397+
PyThreadState *ts = PyThreadState_GET();
6398+
STACKLESS_PACK(ts, res);
6399+
assert(f);
6400+
assert((PyFrameObject *)f == SLP_CURRENT_FRAME(ts));
6401+
SLP_STORE_NEXT_FRAME(ts, (PyFrameObject *)f);
6402+
Py_DECREF(f);
63936403
return STACKLESS_UNWINDING_MAGIC;
63946404
}
6405+
Py_XDECREF(f);
63956406
if (STACKLESS_UNWINDING(res)) {
63966407
return STACKLESS_UNWINDING_MAGIC;
63976408
}

Python/ceval.c

Lines changed: 43 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1167,8 +1167,7 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
11671167
/* push frame */
11681168
if (Py_EnterRecursiveCall("")) {
11691169
Py_XDECREF(retval);
1170-
tstate->frame = f->f_back;
1171-
Py_DECREF(f);
1170+
SLP_STORE_NEXT_FRAME(tstate, f->f_back);
11721171
return NULL;
11731172
}
11741173
#else
@@ -1221,7 +1220,7 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
12211220
Py_XDECREF(retval);
12221221
Py_LeaveRecursiveCall();
12231222
f->f_executing = 0;
1224-
tstate->frame = f->f_back;
1223+
SLP_STORE_NEXT_FRAME(tstate, f->f_back);
12251224
return NULL;
12261225
}
12271226

@@ -4080,9 +4079,8 @@ slp_eval_frame_value(PyFrameObject *f, int throwflag, PyObject *retval)
40804079
#else
40814080
Py_LeaveRecursiveCall();
40824081
f->f_executing = 0;
4083-
tstate->frame = f->f_back;
4082+
SLP_STORE_NEXT_FRAME(tstate, f->f_back);
40844083

4085-
Py_DECREF(f);
40864084
return _Py_CheckFunctionResult(NULL, retval, "PyEval_EvalFrameEx");
40874085

40884086
stackless_setup_with:
@@ -4108,16 +4106,23 @@ slp_eval_frame_value(PyFrameObject *f, int throwflag, PyObject *retval)
41084106
/* the -1 is to adjust for the f_lasti change.
41094107
(look for the word 'Promise' above) */
41104108
f->f_lasti = INSTR_OFFSET() - 1;
4111-
if (tstate->frame->f_back != f)
4109+
if (SLP_PEEK_NEXT_FRAME(tstate)->f_back != f)
41124110
return retval;
41134111
STACKLESS_UNPACK(tstate, retval);
4114-
retval = tstate->frame->f_execute(tstate->frame, 0, retval);
4115-
if (tstate->frame != f) {
4116-
assert(f->f_execute == slp_eval_frame_value || f->f_execute == slp_eval_frame_noval ||
4117-
f->f_execute == slp_eval_frame_setup_with || f->f_execute == slp_eval_frame_with_cleanup);
4118-
if (f->f_execute == slp_eval_frame_noval)
4119-
f->f_execute = slp_eval_frame_value;
4120-
return retval;
4112+
{
4113+
PyFrameObject *f2 = SLP_CLAIM_NEXT_FRAME(tstate);
4114+
retval = CALL_FRAME_FUNCTION(f2, 0, retval);
4115+
Py_DECREF(f2);
4116+
if (SLP_PEEK_NEXT_FRAME(tstate) != f) {
4117+
assert(f->f_execute == slp_eval_frame_value || f->f_execute == slp_eval_frame_noval ||
4118+
f->f_execute == slp_eval_frame_setup_with || f->f_execute == slp_eval_frame_with_cleanup);
4119+
if (f->f_execute == slp_eval_frame_noval)
4120+
f->f_execute = slp_eval_frame_value;
4121+
return retval;
4122+
}
4123+
f2 = SLP_CLAIM_NEXT_FRAME(tstate);
4124+
assert(f == f2);
4125+
Py_DECREF(f2);
41214126
}
41224127
if (STACKLESS_UNWINDING(retval))
41234128
STACKLESS_UNPACK(tstate, retval);
@@ -4141,14 +4146,14 @@ slp_eval_frame_value(PyFrameObject *f, int throwflag, PyObject *retval)
41414146
goto stackless_call_return;
41424147

41434148
stackless_interrupt_call:
4149+
/* interrupted during unwinding */
41444150

41454151
f->f_execute = slp_eval_frame_noval;
41464152
f->f_stacktop = stack_pointer;
41474153

41484154
/* the -1 is to adjust for the f_lasti change.
41494155
(look for the word 'Promise' above) */
41504156
f->f_lasti = INSTR_OFFSET() - 1;
4151-
f = tstate->frame;
41524157
return (PyObject *) Py_UnwindToken;
41534158
#endif
41544159
}
@@ -4514,17 +4519,23 @@ _PyEval_EvalCodeWithName(PyObject *_co, PyObject *globals, PyObject *locals,
45144519
Py_INCREF(Py_None);
45154520
retval = Py_None;
45164521
if (stackless) {
4517-
tstate->frame = f;
4522+
SLP_STORE_NEXT_FRAME(tstate, f);
4523+
Py_DECREF(f);
45184524
return STACKLESS_PACK(tstate, retval);
45194525
}
45204526
else {
4521-
if (f->f_back != NULL)
4527+
if (f->f_back != NULL) {
45224528
/* use the faster path */
4523-
retval = slp_frame_dispatch(f, f->f_back, 0, retval);
4524-
else {
4529+
PyFrameObject *back = f->f_back;
4530+
Py_INCREF(back);
4531+
retval = slp_frame_dispatch(f, back, 0, retval);
4532+
Py_DECREF(back);
4533+
}
4534+
else {
45254535
Py_DECREF(retval);
45264536
retval = slp_eval_frame(f);
45274537
}
4538+
Py_DECREF(f);
45284539
return retval;
45294540
}
45304541
#else
@@ -5292,7 +5303,7 @@ fast_function(PyObject *func, PyObject ***pp_stack, int n, int na, int nk)
52925303
PCALL(PCALL_FAST_FUNCTION);
52935304
if (argdefs == NULL && co->co_argcount == n &&
52945305
co->co_kwonlyargcount == 0 && nk==0 &&
5295-
co->co_flags == (CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE)) {
5306+
(co->co_flags & (~PyCF_MASK)) == (CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE)) {
52965307
PyFrameObject *f;
52975308
PyObject *retval = NULL;
52985309
PyThreadState *tstate = PyThreadState_GET();
@@ -5322,10 +5333,21 @@ fast_function(PyObject *func, PyObject ***pp_stack, int n, int na, int nk)
53225333
if (STACKLESS_POSSIBLE()) {
53235334
Py_INCREF(Py_None);
53245335
retval = Py_None;
5325-
tstate->frame = f;
5336+
SLP_STORE_NEXT_FRAME(tstate, f);
5337+
Py_DECREF(f);
53265338
return STACKLESS_PACK(tstate, retval);
53275339
}
5328-
return slp_eval_frame(f);
5340+
if (f->f_back != NULL) {
5341+
/* use the faster path */
5342+
PyFrameObject *back = f->f_back;
5343+
Py_INCREF(Py_None);
5344+
Py_INCREF(back);
5345+
retval = slp_frame_dispatch(f, back, 0, Py_None);
5346+
Py_DECREF(back);
5347+
}
5348+
else {
5349+
retval = slp_eval_frame(f);
5350+
}
53295351
#else
53305352
retval = PyEval_EvalFrameEx(f,0);
53315353
#endif

Python/pystate.c

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@
33

44
#include "Python.h"
55
#ifdef STACKLESS
6-
/* XXX this should vanish! */
7-
#include "compile.h"
86
#include "frameobject.h"
97
#endif
8+
#include "core/stackless_impl.h"
109

1110
/* --------------------------------------------------------------------------
1211
CAUTION
@@ -171,7 +170,7 @@ threadstate_getframe(PyThreadState *self)
171170
{
172171
#ifdef STACKLESS
173172
/* make sure to return a real frame */
174-
struct _frame *f = self->frame;
173+
struct _frame *f = SLP_CURRENT_FRAME(self);
175174
while (f != NULL && !PyFrame_Check(f))
176175
f = f->f_back;
177176
return f;
@@ -645,7 +644,7 @@ _PyThread_CurrentFrames(void)
645644
for (t = i->tstate_head; t != NULL; t = t->next) {
646645
PyObject *id;
647646
int stat;
648-
struct _frame *frame = t->frame;
647+
struct _frame *frame = SLP_PEEK_NEXT_FRAME(t);
649648
if (frame == NULL)
650649
continue;
651650
id = PyLong_FromLong(t->thread_id);

Stackless/changelog.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,12 @@ What's New in Stackless 3.X.X?
3030
- Leak to an object, if a Python profile or trace function fails.
3131
- Various leaks, if stackless._wrap.frame.__setstate__() fails, because its
3232
state argument is invalid.
33+
- Leak of references to Python stack frames or Stackless C-frames, if
34+
a tasklet didn't run to its end or various other conditions. This part
35+
of the fix changes the Stackless reference counting for frames to follow
36+
the general rules for Python objects.
3337

34-
Additionally this change brings the handling of caught exceptions more in
38+
Additionally, this change brings the handling of caught exceptions more in
3539
line with C-Python.
3640

3741
- https://bitbucket.org/stackless-dev/stackless/issues/112

Stackless/core/cframeobject.c

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,23 @@ cframe_traverse(PyCFrameObject *cf, visitproc visit, void *arg)
6666
static void
6767
cframe_clear(PyCFrameObject *cf)
6868
{
69-
Py_CLEAR(cf->f_back);
70-
Py_CLEAR(cf->ob1);
71-
Py_CLEAR(cf->ob2);
72-
Py_CLEAR(cf->ob3);
69+
/* The Python C-API documentation recomends to use Py_CLEAR() to release
70+
* references held by container objects. The following code is an unrolled
71+
* version of four Py_CLEAR() macros. It is more robust at no additional
72+
* costs.
73+
*/
74+
PyFrameObject *tmp_f_back;
75+
PyObject *tmp_ob1, *tmp_ob2, *tmp_ob3;
76+
tmp_f_back = cf->f_back;
77+
tmp_ob1 = cf->ob1;
78+
tmp_ob2 = cf->ob2;
79+
tmp_ob3 = cf->ob3;
80+
cf->f_back = NULL;
81+
cf->ob1 = cf->ob2 = cf->ob3 = NULL;
82+
Py_XDECREF(tmp_f_back);
83+
Py_XDECREF(tmp_ob1);
84+
Py_XDECREF(tmp_ob2);
85+
Py_XDECREF(tmp_ob3);
7386
}
7487

7588

@@ -93,8 +106,9 @@ slp_cframe_new(PyFrame_ExecFunc *exec, unsigned int linked)
93106
_Py_NewReference((PyObject *) cf);
94107
}
95108

96-
if (linked)
97-
back = ts->frame;
109+
if (linked) {
110+
back = SLP_CURRENT_FRAME(ts);
111+
}
98112
else
99113
back = NULL;
100114
Py_XINCREF(back);
@@ -203,7 +217,7 @@ static PyObject * run_cframe(PyFrameObject *f, int exc, PyObject *retval)
203217
PyTaskletObject *task = ts->st.current;
204218
int done = cf->i;
205219

206-
ts->frame = f;
220+
SLP_SET_CURRENT_FRAME(ts, f);
207221

208222
if (retval == NULL || done)
209223
goto exit_run_cframe;
@@ -218,18 +232,17 @@ static PyObject * run_cframe(PyFrameObject *f, int exc, PyObject *retval)
218232

219233
if (STACKLESS_UNWINDING(retval)) {
220234
/* try to shortcut */
221-
if (ts->st.current == task && ts->frame != NULL &&
222-
ts->frame->f_back == (PyFrameObject *) cf) {
223-
Py_CLEAR(ts->frame->f_back);
224-
ts->frame->f_back = cf->f_back;
225-
Py_DECREF(cf); /* the exec reference */
235+
PyFrameObject *f; /* a borrowed ref */
236+
if (ts->st.current == task && (f = SLP_PEEK_NEXT_FRAME(ts)) != NULL &&
237+
f->f_back == (PyFrameObject *) cf) {
238+
Py_XINCREF(cf->f_back);
239+
Py_SETREF(f->f_back, cf->f_back);
226240
}
227241
return retval;
228242
}
229243
/* pop frame */
230244
exit_run_cframe:
231-
ts->frame = cf->f_back;
232-
Py_DECREF(cf);
245+
SLP_STORE_NEXT_FRAME(ts, cf->f_back);
233246
return retval;
234247
}
235248

Stackless/core/slp_transfer.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ slp_transfer(PyCStackObject **cstprev, PyCStackObject *cst,
121121

122122
/* since we change the stack we must assure that the protocol was met */
123123
STACKLESS_ASSERT();
124+
SLP_ASSERT_FRAME_IN_TRANSFER(ts);
124125

125126
if ((intptr_t *) &ts > ts->st.cstack_base)
126127
return climb_stack_and_transfer(cstprev, cst, prev);
@@ -149,6 +150,7 @@ slp_transfer(PyCStackObject **cstprev, PyCStackObject *cst,
149150
_cst = cst;
150151
_prev = prev;
151152
result = slp_switch();
153+
SLP_ASSERT_FRAME_IN_TRANSFER(ts);
152154
if (!result) {
153155
if (_cst) {
154156
/* record the context of the target stack. Can't do it before the switch because

0 commit comments

Comments
 (0)