Skip to content

Commit 56e519f

Browse files
yoneymeta-codesync[bot]
authored andcommitted
Fix static type leak checks in free-threaded builds
Summary: Account for pending per-thread references and the deferred-refcount marker when checking whether a static type has leaked. Reviewed By: alexmalyshev Differential Revision: D114594103 fbshipit-source-id: a7cced7a356732ceb57bc9517fde9d2399a778e9
1 parent e100ef4 commit 56e519f

1 file changed

Lines changed: 41 additions & 1 deletion

File tree

cinderx/StaticPython/_static.c

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,18 @@
33
#include "cinderx/python.h"
44

55
#include "internal/pycore_call.h"
6+
#include "internal/pycore_object.h"
67
#include "internal/pycore_pystate.h"
78

89
#if PY_VERSION_HEX >= 0x030D0000
910
#include "internal/pycore_modsupport.h"
1011
#endif
1112

13+
#if PY_VERSION_HEX >= 0x030E0000
14+
#include "internal/pycore_object_deferred.h"
15+
#include "internal/pycore_uniqueid.h"
16+
#endif
17+
1218
#include "cinderx/CachedProperties/cached_properties.h"
1319
#include "cinderx/Common/audit.h"
1420
#include "cinderx/Common/extra-py-flags.h"
@@ -26,6 +32,39 @@
2632
#include "cinderx/StaticPython/vtable_builder.h"
2733
#include "cinderx/UpstreamBorrow/borrowed.h"
2834

35+
static int type_refcount_indicates_escape(
36+
PyObject* type,
37+
Py_ssize_t expected_refcount) {
38+
assert(PyType_Check(type));
39+
assert(PyType_HasFeature((PyTypeObject*)type, Py_TPFLAGS_HEAPTYPE));
40+
Py_ssize_t refcount = Py_REFCNT(type);
41+
42+
#ifdef Py_GIL_DISABLED
43+
// TODO: Pending references owned by other threads cannot be read safely
44+
// here. If a metaclass or base publishes the type, this check can miss
45+
// instances created by another thread. Closing that race requires
46+
// synchronizing all threads across both this check and the layout update, or
47+
// finalizing the layout before user callbacks can publish the type.
48+
PyHeapTypeObject* heap_type = (PyHeapTypeObject*)type;
49+
Py_ssize_t unique_id = heap_type->unique_id;
50+
51+
if (unique_id != _Py_INVALID_UNIQUE_ID) {
52+
Py_ssize_t index = unique_id - 1;
53+
_PyThreadStateImpl* tstate = (_PyThreadStateImpl*)_PyThreadState_GET();
54+
55+
if (index < tstate->refcounts.size) {
56+
refcount += tstate->refcounts.values[index];
57+
}
58+
}
59+
60+
if (_PyObject_HasDeferredRefcount(type)) {
61+
refcount -= _Py_REF_DEFERRED;
62+
}
63+
#endif
64+
65+
return refcount != expected_refcount;
66+
}
67+
2968
PyDoc_STRVAR(
3069
_static__doc__,
3170
"_static contains types related to static Python\n");
@@ -1595,9 +1634,10 @@ static PyObject* _static___build_cinder_class__(
15951634
slot_count++;
15961635
}
15971636
#endif
1637+
15981638
// Type by default has 2 references, the one which we'll return, and one
15991639
// which is a circular reference between the type and its MRO
1600-
if (Py_REFCNT(type) != 2 + slot_count) {
1640+
if (type_refcount_indicates_escape(type, 2 + slot_count)) {
16011641
leaked_type = 1;
16021642
}
16031643
}

0 commit comments

Comments
 (0)