aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/Python/gc_free_threading.c
diff options
context:
space:
mode:
authorSam Gross <colesbury@gmail.com>2024-09-12 12:37:06 -0400
committerGitHub <noreply@github.com>2024-09-12 12:37:06 -0400
commitb2afe2aae487ebf89897e22c01d9095944fd334f (patch)
tree3ffa3ebfe3c69cd21968ce76d8d7cb2f325ff6d3 /Python/gc_free_threading.c
parent4ed7d1d6acc22807bfb5983c98fd59f7cb5061db (diff)
downloadcpython-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.c137
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;
}