Skip to content

Commit ce76162

Browse files
jhassesaghul
authored andcommitted
Restore compatibility with Python 3.11
* PyFrameObjects are now lazily allocated and no longer part of the PyThreadState. Therefore switch the _PyCFrame instead, but keep the PyFrameObject* for GC. python/cpython@ae0a2b7 * The recursion_depth is no longer stored directly in the PyThreadState and needs to be calculated from recursion_remaining and recursion_limit. python/cpython@b931077 * The exc_state was also simplified (no more exc_type and exc_traceback). python/cpython@396b583 * Finally the frame "data stack" needs to be saved and restored. How this was done in python-greenlet was used as a reference. python-greenlet/greenlet#280 * Add new test test_clean_callstack to check that the call stack from the creation of the Fiber doesn't leak into it (i.e. `tstate->cframe->current_frame = NULL;` in `stacklet__callback`; `tstate->frame = NULL;` in older Python versions).
1 parent b693038 commit ce76162

File tree

4 files changed

+73
-9
lines changed

4 files changed

+73
-9
lines changed

.github/workflows/python-package.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
# would use windows-latest, but distutils tries to build against the version of MSVC that python was compiled with
2121
# https://github.com/pypa/setuptools/blob/main/setuptools/_distutils/msvc9compiler.py#L403
2222
os: [ "ubuntu-latest", "macos-12", "windows-2019" ]
23-
python-version: ["3.7", "3.8", "3.9", "3.10"]
23+
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
2424
#os: [ "ubuntu-latest" ]
2525
#python-version: ["3.8"]
2626

src/fibers.c

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -221,17 +221,32 @@ stacklet__callback(stacklet_handle h, void *arg)
221221
/* set current thread state before starting this new Fiber */
222222
tstate = PyThreadState_Get();
223223
ASSERT(tstate != NULL);
224+
tstate->exc_state.exc_value = NULL;
225+
#if PY_MINOR_VERSION < 11
224226
tstate->frame = NULL;
225227
tstate->exc_state.exc_type = NULL;
226-
tstate->exc_state.exc_value = NULL;
227228
tstate->exc_state.exc_traceback = NULL;
229+
#else
230+
tstate->datastack_chunk = NULL;
231+
tstate->datastack_top = NULL;
232+
tstate->datastack_limit = NULL;
233+
tstate->cframe->current_frame = NULL;
234+
#endif
228235
tstate->exc_state.previous_item = NULL;
229236

230-
self->ts.recursion_depth = tstate->recursion_depth;
231237
self->ts.frame = NULL;
232-
self->ts.exc_state.exc_type = NULL;
233238
self->ts.exc_state.exc_value = NULL;
239+
#if PY_MINOR_VERSION < 11
240+
self->ts.recursion_depth = tstate->recursion_depth;
241+
self->ts.exc_state.exc_type = NULL;
234242
self->ts.exc_state.exc_traceback = NULL;
243+
#else
244+
self->ts.recursion_depth = tstate->recursion_limit - tstate->recursion_remaining;
245+
self->ts.cframe = NULL;
246+
self->ts.datastack_chunk = NULL;
247+
self->ts.datastack_top = NULL;
248+
self->ts.datastack_limit = NULL;
249+
#endif
235250
self->ts.exc_state.previous_item = NULL;
236251

237252
if (value == NULL) {
@@ -285,11 +300,21 @@ do_switch(Fiber *self, PyObject *value)
285300
tstate = PyThreadState_Get();
286301
ASSERT(tstate != NULL);
287302
ASSERT(tstate->dict != NULL);
303+
current->ts.exc_state.exc_value = tstate->exc_state.exc_value;
304+
#if PY_MINOR_VERSION < 11
288305
current->ts.recursion_depth = tstate->recursion_depth;
289306
current->ts.frame = tstate->frame;
290307
current->ts.exc_state.exc_type = tstate->exc_state.exc_type;
291-
current->ts.exc_state.exc_value = tstate->exc_state.exc_value;
292308
current->ts.exc_state.exc_traceback = tstate->exc_state.exc_traceback;
309+
#else
310+
current->ts.recursion_depth = tstate->recursion_limit - tstate->recursion_remaining;
311+
current->ts.frame = PyThreadState_GetFrame(tstate);
312+
Py_XDECREF(current->ts.frame);
313+
current->ts.cframe = tstate->cframe;
314+
current->ts.datastack_chunk = tstate->datastack_chunk;
315+
current->ts.datastack_top = tstate->datastack_top;
316+
current->ts.datastack_limit = tstate->datastack_limit;
317+
#endif
293318
current->ts.exc_state.previous_item = tstate->exc_state.previous_item;
294319
ASSERT(current->stacklet_h == NULL);
295320

@@ -328,17 +353,32 @@ do_switch(Fiber *self, PyObject *value)
328353
}
329354

330355
/* restore state */
356+
tstate->exc_state.exc_value = current->ts.exc_state.exc_value;
357+
#if PY_MINOR_VERSION < 11
331358
tstate->recursion_depth = current->ts.recursion_depth;
332359
tstate->frame = current->ts.frame;
333360
tstate->exc_state.exc_type = current->ts.exc_state.exc_type;
334-
tstate->exc_state.exc_value = current->ts.exc_state.exc_value;
335361
tstate->exc_state.exc_traceback = current->ts.exc_state.exc_traceback;
362+
#else
363+
tstate->recursion_remaining = tstate->recursion_limit - current->ts.recursion_depth;
364+
tstate->cframe = current->ts.cframe;
365+
tstate->datastack_chunk = current->ts.datastack_chunk;
366+
tstate->datastack_top = current->ts.datastack_top;
367+
tstate->datastack_limit = current->ts.datastack_limit;
368+
#endif
336369
tstate->exc_state.previous_item = current->ts.exc_state.previous_item;
337370

338371
current->ts.frame = NULL;
339-
current->ts.exc_state.exc_type = NULL;
340372
current->ts.exc_state.exc_value = NULL;
373+
#if PY_MINOR_VERSION < 11
374+
current->ts.exc_state.exc_type = NULL;
341375
current->ts.exc_state.exc_traceback = NULL;
376+
#else
377+
current->ts.cframe = NULL;
378+
current->ts.datastack_chunk = NULL;
379+
current->ts.datastack_top = NULL;
380+
current->ts.datastack_limit = NULL;
381+
#endif
342382
current->ts.exc_state.previous_item = NULL;
343383

344384
return result;
@@ -590,9 +630,11 @@ Fiber_tp_traverse(Fiber *self, visitproc visit, void *arg)
590630
Py_VISIT(self->ts_dict);
591631
Py_VISIT(self->parent);
592632
Py_VISIT(self->ts.frame);
593-
Py_VISIT(self->ts.exc_state.exc_type);
594633
Py_VISIT(self->ts.exc_state.exc_value);
634+
#if PY_MINOR_VERSION < 11
635+
Py_VISIT(self->ts.exc_state.exc_type);
595636
Py_VISIT(self->ts.exc_state.exc_traceback);
637+
#endif
596638
Py_VISIT(self->ts.exc_state.previous_item);
597639

598640
return 0;
@@ -609,9 +651,11 @@ Fiber_tp_clear(Fiber *self)
609651
Py_CLEAR(self->ts_dict);
610652
Py_CLEAR(self->parent);
611653
Py_CLEAR(self->ts.frame);
612-
Py_CLEAR(self->ts.exc_state.exc_type);
613654
Py_CLEAR(self->ts.exc_state.exc_value);
655+
#if PY_MINOR_VERSION < 11
656+
Py_CLEAR(self->ts.exc_state.exc_type);
614657
Py_CLEAR(self->ts.exc_state.exc_traceback);
658+
#endif
615659
Py_CLEAR(self->ts.exc_state.previous_item);
616660

617661
return 0;

src/fibers.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ typedef struct _fiber {
3030
PyObject *args;
3131
PyObject *kwargs;
3232
struct {
33+
#if PY_MINOR_VERSION >= 11
34+
_PyCFrame *cframe;
35+
_PyStackChunk *datastack_chunk;
36+
PyObject **datastack_top;
37+
PyObject **datastack_limit;
38+
#endif
3339
struct _frame *frame;
3440
int recursion_depth;
3541
_PyErr_StackItem exc_state;

tests/test_fibers.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import os
88
import sys
9+
import traceback
910

1011
import fibers
1112
from fibers import Fiber, current
@@ -69,6 +70,19 @@ def f():
6970
lst.append(4)
7071
assert lst == list(range(5))
7172

73+
def test_clean_callstack(self):
74+
lst = []
75+
76+
def f():
77+
for line in traceback.format_stack():
78+
lst.append(line)
79+
f()
80+
expected = [lst[-1]]
81+
lst = []
82+
g = Fiber(f)
83+
g.switch()
84+
assert lst == expected
85+
7286
def test_two_children(self):
7387
lst = []
7488

0 commit comments

Comments
 (0)