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

Commit 070e7f0

Browse files
author
Anselm Kruis
committed
Stackless issue #166: Fix PyEval_EvalFrameEx()
Support using PyEval_EvalFrameEx() without a main tasklet. And fix a potential reference counting problem. (cherry picked from commit 187184b)
1 parent 3692811 commit 070e7f0

File tree

3 files changed

+162
-16
lines changed

3 files changed

+162
-16
lines changed

Python/ceval.c

Lines changed: 80 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4104,6 +4104,48 @@ slp_eval_frame_with_cleanup(PyFrameObject *f, int throwflag, PyObject *retval)
41044104
return r;
41054105
}
41064106

4107+
static PyObject *
4108+
run_frame_dispatch(PyFrameObject *f, int exc, PyObject *retval)
4109+
{
4110+
PyThreadState *ts = PyThreadState_GET();
4111+
PyCFrameObject *cf = (PyCFrameObject*) f;
4112+
PyFrameObject *f_back;
4113+
int done = cf->i;
4114+
4115+
SLP_SET_CURRENT_FRAME(ts, f);
4116+
4117+
if (retval == NULL || done)
4118+
goto exit_run_frame_dispatch;
4119+
4120+
Py_DECREF(retval);
4121+
4122+
f = (PyFrameObject *)cf->ob1;
4123+
Py_INCREF(f); /* cf->ob1 is just a borrowed ref */
4124+
f_back = (PyFrameObject *)cf->ob2;
4125+
Py_XINCREF(f_back); /* cf->ob2 is just a borrowed ref */
4126+
if (cf->n) { /* cf->n is throwflag */
4127+
retval = NULL;
4128+
slp_bomb_explode(cf->ob3); /* slp_bomb_explode steals the ref to the bomb */
4129+
cf->ob3 = NULL;
4130+
} else {
4131+
retval = cf->ob3; /* cf->ob3 is just a borrowed ref */
4132+
Py_XINCREF(retval);
4133+
}
4134+
retval = slp_frame_dispatch(f, f_back, cf->n, retval);
4135+
assert(!STACKLESS_UNWINDING(retval));
4136+
assert(SLP_CURRENT_FRAME_IS_VALID(ts));
4137+
4138+
cf->i = 1; /* mark ourself as done */
4139+
4140+
Py_DECREF(f);
4141+
Py_XDECREF(f_back);
4142+
4143+
/* pop frame */
4144+
exit_run_frame_dispatch:
4145+
SLP_STORE_NEXT_FRAME(ts, cf->f_back);
4146+
return retval;
4147+
}
4148+
41074149
PyObject *
41084150
_PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag)
41094151
{
@@ -4113,19 +4155,11 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag)
41134155
*/
41144156
PyThreadState *tstate = PyThreadState_GET();
41154157
PyObject * retval = NULL;
4158+
PyFrameObject * f_back;
41164159

41174160
if (f == NULL)
41184161
return NULL;
4119-
/* make sure that the Stackless system has been initialized. */
4120-
assert(tstate->st.main && tstate->st.current);
4121-
/* sanity check. */
4122-
assert(SLP_CURRENT_FRAME_IS_VALID(tstate));
4123-
if (!throwflag) {
4124-
/* If throwflag is true, retval must be NULL. Otherwise it must be non-NULL.
4125-
*/
4126-
Py_INCREF(Py_None);
4127-
retval = Py_None;
4128-
}
4162+
41294163
if (PyFrame_Check(f) && f->f_execute == NULL) {
41304164
/* A new frame returned from PyFrame_New() has f->f_execute == NULL.
41314165
*/
@@ -4157,7 +4191,42 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag)
41574191
return NULL;
41584192
}
41594193
}
4160-
retval = slp_frame_dispatch(f, f->f_back, throwflag, retval);
4194+
4195+
if (!throwflag) {
4196+
/* If throwflag is true, retval must be NULL. Otherwise it must be non-NULL.
4197+
*/
4198+
Py_INCREF(Py_None);
4199+
retval = Py_None;
4200+
}
4201+
4202+
/* test, if the stackless system has been initialized. */
4203+
if (tstate->st.main == NULL) {
4204+
/* Call from extern. Same logic as PyStackless_Call_Main */
4205+
PyCFrameObject *cf;
4206+
4207+
cf = slp_cframe_new(run_frame_dispatch, 0);
4208+
if (cf == NULL)
4209+
return NULL;
4210+
if (throwflag) {
4211+
retval = slp_curexc_to_bomb();
4212+
}
4213+
Py_INCREF(f);
4214+
cf->ob1 = (PyObject*)f;
4215+
Py_XINCREF(f->f_back);
4216+
cf->ob2 = (PyObject*)f->f_back;
4217+
cf->ob3 = retval; /* transfer our ref. slp_frame_dispatch steals the ref */
4218+
cf->n = throwflag;
4219+
retval = slp_eval_frame((PyFrameObject *) cf);
4220+
Py_DECREF((PyObject *)cf);
4221+
return retval;
4222+
}
4223+
4224+
/* sanity check. */
4225+
assert(SLP_CURRENT_FRAME_IS_VALID(tstate));
4226+
f_back = f->f_back;
4227+
Py_XINCREF(f_back);
4228+
retval = slp_frame_dispatch(f, f_back, throwflag, retval);
4229+
Py_XDECREF(f_back);
41614230
assert(!STACKLESS_UNWINDING(retval));
41624231
assert(SLP_CURRENT_FRAME_IS_VALID(tstate));
41634232
return retval;

Stackless/module/stacklessmodule.c

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1134,19 +1134,25 @@ by Stackless Python.\n\
11341134
The function creates a frame from code, globals and args and executes the frame.");
11351135

11361136
static PyObject* test_PyEval_EvalFrameEx(PyObject *self, PyObject *args, PyObject *kwds) {
1137-
static char *kwlist[] = {"code", "globals", "args", "alloca", NULL};
1137+
static char *kwlist[] = {"code", "globals", "args", "alloca", "throw", NULL};
11381138
PyThreadState *tstate = PyThreadState_GET();
11391139
PyCodeObject *co;
11401140
PyObject *globals, *co_args = NULL;
11411141
Py_ssize_t alloca_size = 0;
1142+
PyObject *exc = NULL;
11421143
PyFrameObject *f;
11431144
PyObject *result = NULL;
11441145
void *p;
11451146
Py_ssize_t na;
11461147

1147-
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!O!|O!n:test_PyEval_EvalFrameEx", kwlist,
1148-
&PyCode_Type, &co, &PyDict_Type, &globals, &PyTuple_Type, &co_args, &alloca_size))
1148+
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O!O!|O!nO:test_PyEval_EvalFrameEx", kwlist,
1149+
&PyCode_Type, &co, &PyDict_Type, &globals, &PyTuple_Type, &co_args, &alloca_size,
1150+
&exc))
11491151
return NULL;
1152+
if (exc && !PyExceptionInstance_Check(exc)) {
1153+
PyErr_SetString(PyExc_TypeError, "exc must be an exception instance");
1154+
return NULL;
1155+
}
11501156
p = alloca(alloca_size);
11511157
assert(globals != NULL);
11521158
assert(tstate != NULL);
@@ -1184,7 +1190,10 @@ static PyObject* test_PyEval_EvalFrameEx(PyObject *self, PyObject *args, PyObjec
11841190
Py_SETREF(fastlocals[0], PyLong_FromVoidPtr(p));
11851191
}
11861192
}
1187-
result = PyEval_EvalFrameEx(f,0);
1193+
if (exc) {
1194+
PyErr_SetObject(PyExceptionInstance_Class(exc), exc);
1195+
}
1196+
result = PyEval_EvalFrameEx(f, exc != NULL);
11881197
/* result = Py_None; Py_INCREF(Py_None); */
11891198
exit:
11901199
++tstate->recursion_depth;

Stackless/unittests/test_capi.py

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,23 @@
2323
from __future__ import print_function, absolute_import, division
2424

2525
import inspect
26+
import io
2627
import stackless
2728
import sys
29+
import time
30+
import traceback
2831
import unittest
2932

3033
from support import test_main # @UnusedImport
31-
from support import StacklessTestCase
34+
from support import StacklessTestCase, withThreads, require_one_thread
35+
36+
if withThreads:
37+
try:
38+
import thread as _thread
39+
except ImportError:
40+
import _thread
41+
else:
42+
_thread = None
3243

3344

3445
class Test_PyEval_EvalFrameEx(StacklessTestCase):
@@ -93,6 +104,63 @@ def f():
93104
self.assertTrue(self.other_done)
94105
self.assertEqual(result, 666)
95106

107+
def test_throw(self):
108+
def f():
109+
raise RuntimeError("must not be called")
110+
111+
try:
112+
exc = ZeroDivisionError("test")
113+
stackless.test_PyEval_EvalFrameEx(f.__code__, f.__globals__, (), throw=exc)
114+
except ZeroDivisionError:
115+
e, tb = sys.exc_info()[1:]
116+
self.assertIs(e, exc)
117+
self.assertEqual(traceback.extract_tb(tb)[-1][2], f.__name__)
118+
else:
119+
self.fail("expected exception")
120+
121+
@unittest.skipUnless(withThreads, "test requires threading")
122+
def test_no_main_tasklet(self):
123+
def f(self, lock):
124+
self.thread_done = True
125+
lock.release()
126+
127+
def g(func, *args, **kw):
128+
return func(*args, **kw)
129+
130+
lock = _thread.allocate_lock()
131+
132+
# first a dry run to check the threading logic
133+
lock.acquire()
134+
self.thread_done = False
135+
_thread.start_new_thread(g, (stackless.test_PyEval_EvalFrameEx, f.__code__, f.__globals__, (self, lock)))
136+
lock.acquire()
137+
self.assertTrue(self.thread_done)
138+
# return # uncomment to skip the real test
139+
# and now the real smoke test: now PyEval_EvalFrameEx gets called without a main tasklet
140+
self.thread_done = False
141+
_thread.start_new_thread(stackless.test_PyEval_EvalFrameEx, (f.__code__, f.__globals__, (self, lock)))
142+
lock.acquire()
143+
self.assertTrue(self.thread_done)
144+
145+
@unittest.skipUnless(withThreads, "test requires threading")
146+
@require_one_thread # we fiddle with sys.stderr
147+
def test_throw_no_main_tasklet(self):
148+
def f():
149+
raise RuntimeError("must not be called")
150+
151+
exc = ZeroDivisionError("test")
152+
saved_sys_stderr = sys.stderr
153+
sys.stderr = iobuffer = io.StringIO()
154+
try:
155+
_thread.start_new_thread(stackless.test_PyEval_EvalFrameEx, (f.__code__, f.__globals__, ()), dict(throw=exc))
156+
time.sleep(0.1)
157+
finally:
158+
sys.stderr = saved_sys_stderr
159+
msg = str(iobuffer.getvalue())
160+
self.assertNotIn("Pending error while entering Stackless subsystem", msg)
161+
self.assertIn(str(exc), msg)
162+
#print(msg)
163+
96164

97165
if __name__ == "__main__":
98166
if not sys.argv[1:]:

0 commit comments

Comments
 (0)