Skip to content

Commit 9673b8a

Browse files
committed
Mark generators left on stack when clearing frame as zombies.
1 parent 01bf540 commit 9673b8a

File tree

3 files changed

+26
-15
lines changed

3 files changed

+26
-15
lines changed

Include/internal/pycore_frame.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,12 @@ typedef enum _framestate {
4646
FRAME_SUSPENDED_YIELD_FROM = -1,
4747
FRAME_EXECUTING = 0,
4848
FRAME_COMPLETED = 1,
49-
FRAME_CLEARED = 4
49+
FRAME_CLEARED = 4,
50+
FRAME_ZOMBIE = 5, /* For generators left on the stack of cleared threads */
5051
} PyFrameState;
5152

5253
#define FRAME_STATE_SUSPENDED(S) ((S) == FRAME_SUSPENDED || (S) == FRAME_SUSPENDED_YIELD_FROM)
53-
#define FRAME_STATE_FINISHED(S) ((S) >= FRAME_COMPLETED)
54+
#define FRAME_STATE_FINISHED(S) ((S) > FRAME_EXECUTING)
5455

5556
enum _frameowner {
5657
FRAME_OWNED_BY_THREAD = 0,

Objects/genobject.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ gen_traverse(PyObject *self, visitproc visit, void *arg)
5858
PyGenObject *gen = _PyGen_CAST(self);
5959
Py_VISIT(gen->gi_name);
6060
Py_VISIT(gen->gi_qualname);
61-
if (gen->gi_frame_state < FRAME_EXECUTING) {
61+
if (gen->gi_frame_state < FRAME_EXECUTING || gen->gi_frame_state == FRAME_ZOMBIE) {
6262
_PyInterpreterFrame *frame = &gen->gi_iframe;
6363
assert(frame->frame_obj == NULL ||
6464
frame->frame_obj->f_frame->owner == FRAME_OWNED_BY_GENERATOR);

Python/pystate.c

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1636,18 +1636,28 @@ PyThreadState_Clear(PyThreadState *tstate)
16361636

16371637
int verbose = _PyInterpreterState_GetConfig(tstate->interp)->verbose;
16381638

1639-
if (verbose && tstate->current_frame != NULL) {
1640-
/* bpo-20526: After the main thread calls
1641-
_PyInterpreterState_SetFinalizing() in Py_FinalizeEx()
1642-
(or in Py_EndInterpreter() for subinterpreters),
1643-
threads must exit when trying to take the GIL.
1644-
If a thread exit in the middle of _PyEval_EvalFrameDefault(),
1645-
tstate->frame is not reset to its previous value.
1646-
It is more likely with daemon threads, but it can happen
1647-
with regular threads if threading._shutdown() fails
1648-
(ex: interrupted by CTRL+C). */
1649-
fprintf(stderr,
1650-
"PyThreadState_Clear: warning: thread still has a frame\n");
1639+
if (tstate->current_frame != NULL) {
1640+
_PyInterpreterFrame *frame = tstate->current_frame;
1641+
if (verbose) {
1642+
/* bpo-20526: After the main thread calls
1643+
_PyInterpreterState_SetFinalizing() in Py_FinalizeEx()
1644+
(or in Py_EndInterpreter() for subinterpreters),
1645+
threads must exit when trying to take the GIL.
1646+
If a thread exit in the middle of _PyEval_EvalFrameDefault(),
1647+
tstate->frame is not reset to its previous value.
1648+
It is more likely with daemon threads, but it can happen
1649+
with regular threads if threading._shutdown() fails
1650+
(ex: interrupted by CTRL+C). */
1651+
fprintf(stderr,
1652+
"PyThreadState_Clear: warning: thread still has a frame\n");
1653+
}
1654+
do {
1655+
if (frame->owner == FRAME_OWNED_BY_GENERATOR) {
1656+
PyGenObject *gen = _PyGen_GetGeneratorFromFrame(frame);
1657+
gen->gi_frame_state = FRAME_ZOMBIE;
1658+
}
1659+
frame = frame->previous;
1660+
} while (frame != NULL);
16511661
}
16521662

16531663
/* At this point tstate shouldn't be used any more,

0 commit comments

Comments
 (0)