Skip to content

Commit a0271bd

Browse files
authored
Merge branch 'python:main' into update-docs-importlib
2 parents ccdb58c + 48b6866 commit a0271bd

File tree

6 files changed

+47
-37
lines changed

6 files changed

+47
-37
lines changed

Lib/test/test_coroutines.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2265,6 +2265,20 @@ def c():
22652265
# before fixing, visible stack from throw would be shorter than from send.
22662266
self.assertEqual(len_send, len_throw)
22672267

2268+
def test_call_generator_in_frame_clear(self):
2269+
# gh-143939: Running a generator while clearing the coroutine's frame
2270+
# should not be misinterpreted as a yield.
2271+
class CallGeneratorOnDealloc:
2272+
def __del__(self):
2273+
next(x for x in [1])
2274+
2275+
async def coro():
2276+
obj = CallGeneratorOnDealloc()
2277+
return 42
2278+
2279+
yielded, result = run_async(coro())
2280+
self.assertEqual(yielded, [])
2281+
self.assertEqual(result, 42)
22682282

22692283
@unittest.skipIf(
22702284
support.is_emscripten or support.is_wasi,
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix erroneous "cannot reuse already awaited coroutine" error that could
2+
occur when a generator was run during the process of clearing a coroutine's
3+
frame.

Objects/genobject.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,9 @@ gen_send_ex2(PyGenObject *gen, PyObject *arg, PyObject **presult, int exc)
280280

281281
if (return_kind == GENERATOR_YIELD) {
282282
assert(result != NULL && !_PyErr_Occurred(tstate));
283+
#ifndef Py_GIL_DISABLED
284+
assert(FRAME_STATE_SUSPENDED(gen->gi_frame_state));
285+
#endif
283286
*presult = result;
284287
return PYGEN_NEXT;
285288
}

Python/ceval.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1914,14 +1914,16 @@ clear_gen_frame(PyThreadState *tstate, _PyInterpreterFrame * frame)
19141914
assert(frame->owner == FRAME_OWNED_BY_GENERATOR);
19151915
PyGenObject *gen = _PyGen_GetGeneratorFromFrame(frame);
19161916
FT_ATOMIC_STORE_INT8_RELEASE(gen->gi_frame_state, FRAME_CLEARED);
1917-
((_PyThreadStateImpl *)tstate)->generator_return_kind = GENERATOR_RETURN;
19181917
assert(tstate->exc_info == &gen->gi_exc_state);
19191918
tstate->exc_info = gen->gi_exc_state.previous_item;
19201919
gen->gi_exc_state.previous_item = NULL;
19211920
assert(frame->frame_obj == NULL || frame->frame_obj->f_frame == frame);
19221921
frame->previous = NULL;
19231922
_PyFrame_ClearExceptCode(frame);
19241923
_PyErr_ClearExcState(&gen->gi_exc_state);
1924+
// gh-143939: There must not be any escaping calls between setting
1925+
// the generator return kind and returning from _PyEval_EvalFrame.
1926+
((_PyThreadStateImpl *)tstate)->generator_return_kind = GENERATOR_RETURN;
19251927
}
19261928

19271929
void

Python/gc_free_threading.c

Lines changed: 23 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -308,18 +308,17 @@ disable_deferred_refcounting(PyObject *op)
308308
// should also be disabled when we turn off deferred refcounting.
309309
_PyObject_DisablePerThreadRefcounting(op);
310310
}
311-
if (_PyObject_GC_IS_TRACKED(op)) {
312-
// Generators and frame objects may contain deferred references to other
313-
// objects. If the pointed-to objects are part of cyclic trash, we may
314-
// have disabled deferred refcounting on them and need to ensure that we
315-
// use strong references, in case the generator or frame object is
316-
// resurrected by a finalizer.
317-
if (PyGen_CheckExact(op) || PyCoro_CheckExact(op) || PyAsyncGen_CheckExact(op)) {
318-
frame_disable_deferred_refcounting(&((PyGenObject *)op)->gi_iframe);
319-
}
320-
else if (PyFrame_Check(op)) {
321-
frame_disable_deferred_refcounting(((PyFrameObject *)op)->f_frame);
322-
}
311+
312+
// Generators and frame objects may contain deferred references to other
313+
// objects. If the pointed-to objects are part of cyclic trash, we may
314+
// have disabled deferred refcounting on them and need to ensure that we
315+
// use strong references, in case the generator or frame object is
316+
// resurrected by a finalizer.
317+
if (PyGen_CheckExact(op) || PyCoro_CheckExact(op) || PyAsyncGen_CheckExact(op)) {
318+
frame_disable_deferred_refcounting(&((PyGenObject *)op)->gi_iframe);
319+
}
320+
else if (PyFrame_Check(op)) {
321+
frame_disable_deferred_refcounting(((PyFrameObject *)op)->f_frame);
323322
}
324323
}
325324

@@ -507,6 +506,10 @@ gc_visit_thread_stacks(PyInterpreterState *interp, struct collection_state *stat
507506
static bool
508507
gc_maybe_untrack(PyObject *op)
509508
{
509+
if (_PyObject_HasDeferredRefcount(op)) {
510+
// deferred refcounting only works if the object is tracked
511+
return false;
512+
}
510513
// Currently we only check for tuples containing only non-GC objects. In
511514
// theory we could check other immutable objects that contain references
512515
// to non-GC objects.
@@ -1019,7 +1022,7 @@ update_refs(const mi_heap_t *heap, const mi_heap_area_t *area,
10191022
}
10201023
_PyObject_ASSERT(op, refcount >= 0);
10211024

1022-
if (refcount > 0 && !_PyObject_HasDeferredRefcount(op)) {
1025+
if (refcount > 0) {
10231026
if (gc_maybe_untrack(op)) {
10241027
gc_restore_refs(op);
10251028
return true;
@@ -1241,30 +1244,19 @@ scan_heap_visitor(const mi_heap_t *heap, const mi_heap_area_t *area,
12411244
return true;
12421245
}
12431246

1247+
if (state->reason == _Py_GC_REASON_SHUTDOWN) {
1248+
// Disable deferred refcounting for reachable objects as well during
1249+
// interpreter shutdown. This ensures that these objects are collected
1250+
// immediately when their last reference is removed.
1251+
disable_deferred_refcounting(op);
1252+
}
1253+
12441254
// object is reachable, restore `ob_tid`; we're done with these objects
12451255
gc_restore_tid(op);
12461256
gc_clear_alive(op);
12471257
return true;
12481258
}
12491259

1250-
// Disable deferred refcounting for reachable objects during interpreter
1251-
// shutdown. This ensures that these objects are collected immediately when
1252-
// their last reference is removed. This needs to consider both tracked and
1253-
// untracked GC objects, since either might have deferred refcounts enabled.
1254-
static bool
1255-
scan_heap_disable_deferred(const mi_heap_t *heap, const mi_heap_area_t *area,
1256-
void *block, size_t block_size, void *args)
1257-
{
1258-
PyObject *op = op_from_block_all_gc(block, args);
1259-
if (op == NULL) {
1260-
return true;
1261-
}
1262-
if (!_Py_IsImmortal(op)) {
1263-
disable_deferred_refcounting(op);
1264-
}
1265-
return true;
1266-
}
1267-
12681260
static int
12691261
move_legacy_finalizer_reachable(struct collection_state *state);
12701262

@@ -1499,10 +1491,6 @@ deduce_unreachable_heap(PyInterpreterState *interp,
14991491
// Restores ob_tid for reachable objects.
15001492
gc_visit_heaps(interp, &scan_heap_visitor, &state->base);
15011493

1502-
if (state->reason == _Py_GC_REASON_SHUTDOWN) {
1503-
gc_visit_heaps(interp, &scan_heap_disable_deferred, &state->base);
1504-
}
1505-
15061494
if (state->legacy_finalizers.head) {
15071495
// There may be objects reachable from legacy finalizers that are in
15081496
// the unreachable set. We need to mark them as reachable.

Python/specialize.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ static uint32_t function_get_version(PyObject *o, int opcode);
362362
static void
363363
maybe_enable_deferred_ref_count(PyObject *op)
364364
{
365-
if (!_Py_IsOwnedByCurrentThread(op)) {
365+
if (!_Py_IsOwnedByCurrentThread(op) && _PyObject_GC_IS_TRACKED(op)) {
366366
// For module level variables that are heavily used from multiple
367367
// threads, deferred reference counting provides good scaling
368368
// benefits. The downside is that the object will only be deallocated

0 commit comments

Comments
 (0)