Skip to content

Commit d0ff48c

Browse files
author
Anselm Kruis
committed
Issue python#117: fix various reference leaks to frames.
Stackless used to leak references to frames / C-frames, if a tasklet didn't run to its end, or various other conditions. This part of the fix changes the Stackless reference counting for frames to follow the general rules for Python objects. The concept of an "execute reference" no longer exists. Frames no longer eat their reference. See Stackless/core/stackless_impl.h for a detailed explanation. This patch adds a special frame reference counting debug mode, which can be enabled in stackless_tstate.h. https://bitbucket.org/stackless-dev/stackless/issues/117 (grafted from 9e9a2cea45059e87d025a1867beb8b0e3369262d)
1 parent 8450bb9 commit d0ff48c

16 files changed

+708
-210
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" {
@@ -1785,7 +1788,11 @@ _Py_Dealloc(PyObject *op)
17851788
{
17861789
destructor dealloc = Py_TYPE(op)->tp_dealloc;
17871790
_Py_ForgetReference(op);
1791+
#ifdef STACKLESS
1792+
SLP_WITH_VALID_CURRENT_FRAME((*dealloc)(op));
1793+
#else
17881794
(*dealloc)(op);
1795+
#endif
17891796
}
17901797

17911798
/* 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
@@ -5742,8 +5742,7 @@ slp_tp_init_callback(PyFrameObject *f, int exc, PyObject *retval)
57425742
cf->ob1 = NULL;
57435743
}
57445744
}
5745-
ts->frame = f;
5746-
Py_DECREF(cf);
5745+
SLP_STORE_NEXT_FRAME(ts, f);
57475746
return STACKLESS_PACK(ts, retval);
57485747
}
57495748
#endif
@@ -5755,17 +5754,23 @@ slot_tp_init(PyObject *self, PyObject *args, PyObject *kwds)
57555754
_Py_IDENTIFIER(__init__);
57565755
PyObject *meth = lookup_method(self, &PyId___init__);
57575756
PyObject *res;
5757+
#ifdef STACKLESS
5758+
PyCFrameObject *f = NULL;
5759+
#endif
57585760

57595761
if (meth == NULL)
57605762
return -1;
57615763
#ifdef STACKLESS
57625764
if (stackless) {
5763-
PyCFrameObject *f = slp_cframe_new(slp_tp_init_callback, 1);
5765+
f = slp_cframe_new(slp_tp_init_callback, 1);
57645766
if (f == NULL)
57655767
return -1;
57665768
Py_INCREF(self);
57675769
f->ob1 = self;
5768-
PyThreadState_GET()->frame = (PyFrameObject *) f;
5770+
SLP_SET_CURRENT_FRAME(PyThreadState_GET(), (PyFrameObject *) f);
5771+
/* f contains the only counted reference to current frame. This reference
5772+
* keeps the fame alive during the following PyObject_Call().
5773+
*/
57695774
}
57705775
#endif
57715776
STACKLESS_PROMOTE_ALL();
@@ -5774,10 +5779,16 @@ slot_tp_init(PyObject *self, PyObject *args, PyObject *kwds)
57745779
Py_DECREF(meth);
57755780
#ifdef STACKLESS
57765781
if (stackless && !STACKLESS_UNWINDING(res)) {
5777-
/* required, because added a C-frame */
5778-
STACKLESS_PACK(PyThreadState_GET(), res);
5782+
/* required, because we added a C-frame */
5783+
PyThreadState *ts = PyThreadState_GET();
5784+
STACKLESS_PACK(ts, res);
5785+
assert(f);
5786+
assert((PyFrameObject *)f == SLP_CURRENT_FRAME(ts));
5787+
SLP_STORE_NEXT_FRAME(ts, (PyFrameObject *)f);
5788+
Py_DECREF(f);
57795789
return STACKLESS_UNWINDING_MAGIC;
57805790
}
5791+
Py_XDECREF(f);
57815792
if (STACKLESS_UNWINDING(res)) {
57825793
return STACKLESS_UNWINDING_MAGIC;
57835794
}

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
@@ -1220,7 +1219,7 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
12201219
exit_eval_frame:
12211220
Py_XDECREF(retval);
12221221
Py_LeaveRecursiveCall();
1223-
tstate->frame = f->f_back;
1222+
SLP_STORE_NEXT_FRAME(tstate, f->f_back);
12241223
return NULL;
12251224
}
12261225

@@ -3738,8 +3737,7 @@ slp_eval_frame_value(PyFrameObject *f, int throwflag, PyObject *retval)
37383737

37393738
#else
37403739
Py_LeaveRecursiveCall();
3741-
tstate->frame = f->f_back;
3742-
Py_DECREF(f);
3740+
SLP_STORE_NEXT_FRAME(tstate, f->f_back);
37433741
return retval;
37443742

37453743
stackless_setup_with:
@@ -3766,16 +3764,23 @@ slp_eval_frame_value(PyFrameObject *f, int throwflag, PyObject *retval)
37663764
/* the -1 is to adjust for the f_lasti change.
37673765
(look for the word 'Promise' above) */
37683766
f->f_lasti = INSTR_OFFSET() - 1;
3769-
if (tstate->frame->f_back != f)
3767+
if (SLP_PEEK_NEXT_FRAME(tstate)->f_back != f)
37703768
return retval;
37713769
STACKLESS_UNPACK(tstate, retval);
3772-
retval = tstate->frame->f_execute(tstate->frame, 0, retval);
3773-
if (tstate->frame != f) {
3774-
assert(f->f_execute == slp_eval_frame_value || f->f_execute == slp_eval_frame_noval ||
3775-
f->f_execute == slp_eval_frame_setup_with || f->f_execute == slp_eval_frame_with_cleanup);
3776-
if (f->f_execute == slp_eval_frame_noval)
3777-
f->f_execute = slp_eval_frame_value;
3778-
return retval;
3770+
{
3771+
PyFrameObject *f2 = SLP_CLAIM_NEXT_FRAME(tstate);
3772+
retval = CALL_FRAME_FUNCTION(f2, 0, retval);
3773+
Py_DECREF(f2);
3774+
if (SLP_PEEK_NEXT_FRAME(tstate) != f) {
3775+
assert(f->f_execute == slp_eval_frame_value || f->f_execute == slp_eval_frame_noval ||
3776+
f->f_execute == slp_eval_frame_setup_with || f->f_execute == slp_eval_frame_with_cleanup);
3777+
if (f->f_execute == slp_eval_frame_noval)
3778+
f->f_execute = slp_eval_frame_value;
3779+
return retval;
3780+
}
3781+
f2 = SLP_CLAIM_NEXT_FRAME(tstate);
3782+
assert(f == f2);
3783+
Py_DECREF(f2);
37793784
}
37803785
if (STACKLESS_UNWINDING(retval))
37813786
STACKLESS_UNPACK(tstate, retval);
@@ -3800,14 +3805,14 @@ slp_eval_frame_value(PyFrameObject *f, int throwflag, PyObject *retval)
38003805
goto stackless_call_return;
38013806

38023807
stackless_interrupt_call:
3808+
/* interrupted during unwinding */
38033809

38043810
f->f_execute = slp_eval_frame_noval;
38053811
f->f_stacktop = stack_pointer;
38063812

38073813
/* the -1 is to adjust for the f_lasti change.
38083814
(look for the word 'Promise' above) */
38093815
f->f_lasti = INSTR_OFFSET() - 1;
3810-
f = tstate->frame;
38113816
return (PyObject *) Py_UnwindToken;
38123817
#endif
38133818
}
@@ -4172,17 +4177,23 @@ PyEval_EvalCodeEx(PyObject *_co, PyObject *globals, PyObject *locals,
41724177
Py_INCREF(Py_None);
41734178
retval = Py_None;
41744179
if (stackless) {
4175-
tstate->frame = f;
4180+
SLP_STORE_NEXT_FRAME(tstate, f);
4181+
Py_DECREF(f);
41764182
return STACKLESS_PACK(tstate, retval);
41774183
}
41784184
else {
4179-
if (f->f_back != NULL)
4185+
if (f->f_back != NULL) {
41804186
/* use the faster path */
4181-
retval = slp_frame_dispatch(f, f->f_back, 0, retval);
4182-
else {
4187+
PyFrameObject *back = f->f_back;
4188+
Py_INCREF(back);
4189+
retval = slp_frame_dispatch(f, back, 0, retval);
4190+
Py_DECREF(back);
4191+
}
4192+
else {
41834193
Py_DECREF(retval);
41844194
retval = slp_eval_frame(f);
41854195
}
4196+
Py_DECREF(f);
41864197
return retval;
41874198
}
41884199
#else
@@ -4898,7 +4909,7 @@ fast_function(PyObject *func, PyObject ***pp_stack, int n, int na, int nk)
48984909
PCALL(PCALL_FAST_FUNCTION);
48994910
if (argdefs == NULL && co->co_argcount == n &&
49004911
co->co_kwonlyargcount == 0 && nk==0 &&
4901-
co->co_flags == (CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE)) {
4912+
(co->co_flags & (~PyCF_MASK)) == (CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE)) {
49024913
PyFrameObject *f;
49034914
PyObject *retval = NULL;
49044915
PyThreadState *tstate = PyThreadState_GET();
@@ -4928,10 +4939,21 @@ fast_function(PyObject *func, PyObject ***pp_stack, int n, int na, int nk)
49284939
if (STACKLESS_POSSIBLE()) {
49294940
Py_INCREF(Py_None);
49304941
retval = Py_None;
4931-
tstate->frame = f;
4942+
SLP_STORE_NEXT_FRAME(tstate, f);
4943+
Py_DECREF(f);
49324944
return STACKLESS_PACK(tstate, retval);
49334945
}
4934-
return slp_eval_frame(f);
4946+
if (f->f_back != NULL) {
4947+
/* use the faster path */
4948+
PyFrameObject *back = f->f_back;
4949+
Py_INCREF(Py_None);
4950+
Py_INCREF(back);
4951+
retval = slp_frame_dispatch(f, back, 0, Py_None);
4952+
Py_DECREF(back);
4953+
}
4954+
else {
4955+
retval = slp_eval_frame(f);
4956+
}
49354957
#else
49364958
retval = PyEval_EvalFrameEx(f,0);
49374959
#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
@@ -169,7 +168,7 @@ threadstate_getframe(PyThreadState *self)
169168
{
170169
#ifdef STACKLESS
171170
/* make sure to return a real frame */
172-
struct _frame *f = self->frame;
171+
struct _frame *f = SLP_CURRENT_FRAME(self);
173172
while (f != NULL && !PyFrame_Check(f))
174173
f = f->f_back;
175174
return f;
@@ -591,7 +590,7 @@ _PyThread_CurrentFrames(void)
591590
for (t = i->tstate_head; t != NULL; t = t->next) {
592591
PyObject *id;
593592
int stat;
594-
struct _frame *frame = t->frame;
593+
struct _frame *frame = SLP_PEEK_NEXT_FRAME(t);
595594
if (frame == NULL)
596595
continue;
597596
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/111

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)