diff options
author | Sam Gross <colesbury@gmail.com> | 2024-09-12 12:37:06 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-09-12 12:37:06 -0400 |
commit | b2afe2aae487ebf89897e22c01d9095944fd334f (patch) | |
tree | 3ffa3ebfe3c69cd21968ce76d8d7cb2f325ff6d3 /Python/gc_free_threading.c | |
parent | 4ed7d1d6acc22807bfb5983c98fd59f7cb5061db (diff) | |
download | cpython-b2afe2aae487ebf89897e22c01d9095944fd334f.tar.gz cpython-b2afe2aae487ebf89897e22c01d9095944fd334f.zip |
gh-123923: Defer refcounting for `f_executable` in `_PyInterpreterFrame` (#123924)
Use a `_PyStackRef` and defer the reference to `f_executable` when
possible. This avoids some reference count contention in the common case
of executing the same code object from multiple threads concurrently in
the free-threaded build.
Diffstat (limited to 'Python/gc_free_threading.c')
-rw-r--r-- | Python/gc_free_threading.c | 137 |
1 files changed, 78 insertions, 59 deletions
diff --git a/Python/gc_free_threading.c b/Python/gc_free_threading.c index 54de0c2671a..e981f87401a 100644 --- a/Python/gc_free_threading.c +++ b/Python/gc_free_threading.c @@ -161,39 +161,6 @@ gc_decref(PyObject *op) op->ob_tid -= 1; } -static void -disable_deferred_refcounting(PyObject *op) -{ - if (!_PyObject_HasDeferredRefcount(op)) { - return; - } - - op->ob_gc_bits &= ~_PyGC_BITS_DEFERRED; - op->ob_ref_shared -= _Py_REF_SHARED(_Py_REF_DEFERRED, 0); - - if (PyType_Check(op)) { - // Disable thread-local refcounting for heap types - PyTypeObject *type = (PyTypeObject *)op; - if (PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) { - _PyType_ReleaseId((PyHeapTypeObject *)op); - } - } - else if (PyGen_CheckExact(op) || PyCoro_CheckExact(op) || PyAsyncGen_CheckExact(op)) { - // Ensure any non-refcounted pointers in locals are converted to - // strong references. This ensures that the generator/coroutine is not - // freed before its locals. - PyGenObject *gen = (PyGenObject *)op; - struct _PyInterpreterFrame *frame = &gen->gi_iframe; - assert(frame->stackpointer != NULL); - for (_PyStackRef *ref = frame->localsplus; ref < frame->stackpointer; ref++) { - if (!PyStackRef_IsNull(*ref) && PyStackRef_IsDeferred(*ref)) { - // Convert a deferred reference to a strong reference. - *ref = PyStackRef_FromPyObjectSteal(PyStackRef_AsPyObjectSteal(*ref)); - } - } - } -} - static Py_ssize_t merge_refcount(PyObject *op, Py_ssize_t extra) { @@ -214,6 +181,51 @@ merge_refcount(PyObject *op, Py_ssize_t extra) } static void +frame_disable_deferred_refcounting(_PyInterpreterFrame *frame) +{ + // Convert locals, variables, and the executable object to strong + // references from (possibly) deferred references. + assert(frame->stackpointer != NULL); + frame->f_executable = PyStackRef_AsStrongReference(frame->f_executable); + for (_PyStackRef *ref = frame->localsplus; ref < frame->stackpointer; ref++) { + if (!PyStackRef_IsNull(*ref) && PyStackRef_IsDeferred(*ref)) { + *ref = PyStackRef_AsStrongReference(*ref); + } + } +} + +static void +disable_deferred_refcounting(PyObject *op) +{ + if (_PyObject_HasDeferredRefcount(op)) { + op->ob_gc_bits &= ~_PyGC_BITS_DEFERRED; + op->ob_ref_shared -= _Py_REF_SHARED(_Py_REF_DEFERRED, 0); + merge_refcount(op, 0); + } + + // Heap types also use thread-local refcounting -- disable it here. + if (PyType_Check(op)) { + // Disable thread-local refcounting for heap types + PyTypeObject *type = (PyTypeObject *)op; + if (PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) { + _PyType_ReleaseId((PyHeapTypeObject *)op); + } + } + + // Generators and frame objects may contain deferred references to other + // objects. If the pointed-to objects are part of cyclic trash, we may + // have disabled deferred refcounting on them and need to ensure that we + // use strong references, in case the generator or frame object is + // resurrected by a finalizer. + if (PyGen_CheckExact(op) || PyCoro_CheckExact(op) || PyAsyncGen_CheckExact(op)) { + frame_disable_deferred_refcounting(&((PyGenObject *)op)->gi_iframe); + } + else if (PyFrame_Check(op)) { + frame_disable_deferred_refcounting(((PyFrameObject *)op)->f_frame); + } +} + +static void gc_restore_tid(PyObject *op) { assert(_PyInterpreterState_GET()->stoptheworld.world_stopped); @@ -349,16 +361,18 @@ gc_visit_thread_stacks(PyInterpreterState *interp) { HEAD_LOCK(&_PyRuntime); for (PyThreadState *p = interp->threads.head; p != NULL; p = p->next) { - _PyInterpreterFrame *f = p->current_frame; - while (f != NULL) { - if (f->f_executable != NULL && PyCode_Check(f->f_executable)) { - PyCodeObject *co = (PyCodeObject *)f->f_executable; - int max_stack = co->co_nlocalsplus + co->co_stacksize; - for (int i = 0; i < max_stack; i++) { - gc_visit_stackref(f->localsplus[i]); - } + for (_PyInterpreterFrame *f = p->current_frame; f != NULL; f = f->previous) { + PyObject *executable = PyStackRef_AsPyObjectBorrow(f->f_executable); + if (executable == NULL || !PyCode_Check(executable)) { + continue; + } + + PyCodeObject *co = (PyCodeObject *)executable; + int max_stack = co->co_nlocalsplus + co->co_stacksize; + gc_visit_stackref(f->f_executable); + for (int i = 0; i < max_stack; i++) { + gc_visit_stackref(f->localsplus[i]); } - f = f->previous; } } HEAD_UNLOCK(&_PyRuntime); @@ -623,23 +637,19 @@ scan_heap_visitor(const mi_heap_t *heap, const mi_heap_area_t *area, else { worklist_push(&state->unreachable, op); } + return true; } - else if (state->reason == _Py_GC_REASON_SHUTDOWN && - _PyObject_HasDeferredRefcount(op)) - { + + if (state->reason == _Py_GC_REASON_SHUTDOWN) { // Disable deferred refcounting for reachable objects as well during // interpreter shutdown. This ensures that these objects are collected // immediately when their last reference is removed. disable_deferred_refcounting(op); - merge_refcount(op, 0); - state->long_lived_total++; - } - else { - // object is reachable, restore `ob_tid`; we're done with these objects - gc_restore_tid(op); - state->long_lived_total++; } + // object is reachable, restore `ob_tid`; we're done with these objects + gc_restore_tid(op); + state->long_lived_total++; return true; } @@ -952,19 +962,28 @@ visit_decref_unreachable(PyObject *op, void *data) } int +_PyGC_VisitStackRef(_PyStackRef *ref, visitproc visit, void *arg) +{ + // This is a bit tricky! We want to ignore deferred references when + // computing the incoming references, but otherwise treat them like + // regular references. + if (!PyStackRef_IsDeferred(*ref) || + (visit != visit_decref && visit != visit_decref_unreachable)) + { + Py_VISIT(PyStackRef_AsPyObjectBorrow(*ref)); + } + return 0; +} + +int _PyGC_VisitFrameStack(_PyInterpreterFrame *frame, visitproc visit, void *arg) { _PyStackRef *ref = _PyFrame_GetLocalsArray(frame); /* locals and stack */ for (; ref < frame->stackpointer; ref++) { - // This is a bit tricky! We want to ignore deferred references when - // computing the incoming references, but otherwise treat them like - // regular references. - if (PyStackRef_IsDeferred(*ref) && - (visit == visit_decref || visit == visit_decref_unreachable)) { - continue; + if (_PyGC_VisitStackRef(ref, visit, arg) < 0) { + return -1; } - Py_VISIT(PyStackRef_AsPyObjectBorrow(*ref)); } return 0; } |