Skip to content

Commit cdb58a7

Browse files
ericsnowcurrentlyencukou
authored andcommitted
[3.12] gh-125286: Share the Main Refchain With Legacy Interpreters (gh-125709)
They used to be shared, before 3.12. Returning to sharing them resolves a failure on Py_TRACE_REFS builds.
1 parent 762cb25 commit cdb58a7

File tree

9 files changed

+77
-58
lines changed

9 files changed

+77
-58
lines changed

Doc/library/sys.rst

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -907,6 +907,35 @@ always available.
907907
It is not guaranteed to exist in all implementations of Python.
908908

909909

910+
.. function:: getobjects(limit[, type])
911+
912+
This function only exists if CPython was built using the
913+
specialized configure option :option:`--with-trace-refs`.
914+
It is intended only for debugging garbage-collection issues.
915+
916+
Return a list of up to *limit* dynamically allocated Python objects.
917+
If *type* is given, only objects of that exact type (not subtypes)
918+
are included.
919+
920+
Objects from the list are not safe to use.
921+
Specifically, the result will include objects from all interpreters that
922+
share their object allocator state (that is, ones created with
923+
:c:member:`PyInterpreterConfig.use_main_obmalloc` set to 1
924+
or using :c:func:`Py_NewInterpreter`, and the
925+
:ref:`main interpreter <sub-interpreter-support>`).
926+
Mixing objects from different interpreters may lead to crashes
927+
or other unexpected behavior.
928+
929+
.. impl-detail::
930+
931+
This function should be used for specialized purposes only.
932+
It is not guaranteed to exist in all implementations of Python.
933+
934+
.. versionchanged:: next
935+
936+
The result may include objects from other interpreters.
937+
938+
910939
.. function:: getprofile()
911940

912941
.. index::

Doc/using/configure.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,7 @@ Debug options
459459
Effects:
460460

461461
* Define the ``Py_TRACE_REFS`` macro.
462-
* Add :func:`!sys.getobjects` function.
462+
* Add :func:`sys.getobjects` function.
463463
* Add :envvar:`PYTHONDUMPREFS` environment variable.
464464

465465
This build is not ABI compatible with release build (default build) or debug

Doc/whatsnew/3.12.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2294,3 +2294,14 @@ email
22942294
check if the *strict* paramater is available.
22952295
(Contributed by Thomas Dwyer and Victor Stinner for :gh:`102988` to improve
22962296
the CVE-2023-27043 fix.)
2297+
2298+
2299+
Notable changes in 3.12.8
2300+
=========================
2301+
2302+
sys
2303+
---
2304+
2305+
* The previously undocumented special function :func:`sys.getobjects`,
2306+
which only exists in specialized builds of Python, may now return objects
2307+
from other interpreters than the one it's called in.

Include/internal/pycore_object_state.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,13 @@ struct _py_object_state {
2424
* together via the _ob_prev and _ob_next members of a PyObject, which
2525
* exist only in a Py_TRACE_REFS build.
2626
*/
27-
PyObject refchain;
27+
PyObject *refchain;
28+
/* In most cases, refchain points to _refchain_obj.
29+
* In sub-interpreters that share objmalloc state with the main interp,
30+
* refchain points to the main interpreter's _refchain_obj, and their own
31+
* _refchain_obj is unused.
32+
*/
33+
PyObject _refchain_obj;
2834
#endif
2935
int _not_used;
3036
};

Include/internal/pycore_runtime_init.h

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -132,15 +132,8 @@ extern PyTypeObject _PyExc_MemoryError;
132132
.context_ver = 1, \
133133
}
134134

135-
#ifdef Py_TRACE_REFS
136-
# define _py_object_state_INIT(INTERP) \
137-
{ \
138-
.refchain = {&INTERP.object_state.refchain, &INTERP.object_state.refchain}, \
139-
}
140-
#else
141135
# define _py_object_state_INIT(INTERP) \
142136
{ 0 }
143-
#endif
144137

145138

146139
// global objects

Objects/object.c

Lines changed: 18 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -159,11 +159,27 @@ _PyDebug_PrintTotalRefs(void) {
159159

160160
#ifdef Py_TRACE_REFS
161161

162-
#define REFCHAIN(interp) &interp->object_state.refchain
162+
#define REFCHAIN(interp) interp->object_state.refchain
163+
164+
static inline int
165+
has_own_refchain(PyInterpreterState *interp)
166+
{
167+
if (interp->feature_flags & Py_RTFLAGS_USE_MAIN_OBMALLOC) {
168+
return (_Py_IsMainInterpreter(interp)
169+
|| _PyInterpreterState_Main() == NULL);
170+
}
171+
return 1;
172+
}
163173

164174
static inline void
165175
init_refchain(PyInterpreterState *interp)
166176
{
177+
if (!has_own_refchain(interp)) {
178+
// Legacy subinterpreters share a refchain with the main interpreter.
179+
REFCHAIN(interp) = REFCHAIN(_PyInterpreterState_Main());
180+
return;
181+
}
182+
REFCHAIN(interp) = &interp->object_state._refchain_obj;
167183
PyObject *refchain = REFCHAIN(interp);
168184
refchain->_ob_prev = refchain;
169185
refchain->_ob_next = refchain;
@@ -2010,9 +2026,7 @@ void
20102026
_PyObject_InitState(PyInterpreterState *interp)
20112027
{
20122028
#ifdef Py_TRACE_REFS
2013-
if (!_Py_IsMainInterpreter(interp)) {
2014-
init_refchain(interp);
2015-
}
2029+
init_refchain(interp);
20162030
#endif
20172031
}
20182032

@@ -2218,42 +2232,6 @@ _Py_NewReferenceNoTotal(PyObject *op)
22182232

22192233

22202234
#ifdef Py_TRACE_REFS
2221-
/* Make sure the ref is associated with the right interpreter.
2222-
* This only needs special attention for heap-allocated objects
2223-
* that have been immortalized, and only when the object might
2224-
* outlive the interpreter where it was created. That means the
2225-
* object was necessarily created using a global allocator
2226-
* (i.e. from the main interpreter). Thus in that specific case
2227-
* we move the object over to the main interpreter's refchain.
2228-
*
2229-
* This was added for the sake of the immortal interned strings,
2230-
* where legacy subinterpreters share the main interpreter's
2231-
* interned dict (and allocator), and therefore the strings can
2232-
* outlive the subinterpreter.
2233-
*
2234-
* It may make sense to fold this into _Py_SetImmortalUntracked(),
2235-
* but that requires further investigation. In the meantime, it is
2236-
* up to the caller to know if this is needed. There should be
2237-
* very few cases.
2238-
*/
2239-
void
2240-
_Py_NormalizeImmortalReference(PyObject *op)
2241-
{
2242-
assert(_Py_IsImmortal(op));
2243-
PyInterpreterState *interp = _PyInterpreterState_GET();
2244-
if (!_PyRefchain_IsTraced(interp, op)) {
2245-
return;
2246-
}
2247-
PyInterpreterState *main_interp = _PyInterpreterState_Main();
2248-
if (interp != main_interp
2249-
&& interp->feature_flags & Py_RTFLAGS_USE_MAIN_OBMALLOC)
2250-
{
2251-
assert(!_PyRefchain_IsTraced(main_interp, op));
2252-
_PyRefchain_Remove(interp, op);
2253-
_PyRefchain_Trace(main_interp, op);
2254-
}
2255-
}
2256-
22572235
void
22582236
_Py_ForgetReference(PyObject *op)
22592237
{

Objects/unicodeobject.c

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14966,10 +14966,6 @@ _PyUnicode_InternStatic(PyInterpreterState *interp, PyObject **p)
1496614966
assert(*p);
1496714967
}
1496814968

14969-
#ifdef Py_TRACE_REFS
14970-
extern void _Py_NormalizeImmortalReference(PyObject *);
14971-
#endif
14972-
1497314969
static void
1497414970
immortalize_interned(PyObject *s)
1497514971
{
@@ -14985,10 +14981,6 @@ immortalize_interned(PyObject *s)
1498514981
#endif
1498614982
_PyUnicode_STATE(s).interned = SSTATE_INTERNED_IMMORTAL;
1498714983
_Py_SetImmortal(s);
14988-
#ifdef Py_TRACE_REFS
14989-
/* Make sure the ref is associated with the right interpreter. */
14990-
_Py_NormalizeImmortalReference(s);
14991-
#endif
1499214984
}
1499314985

1499414986
static /* non-null */ PyObject*

Python/pylifecycle.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,10 @@ pycore_create_interpreter(_PyRuntimeState *runtime,
650650
return status;
651651
}
652652

653+
// This could be done in init_interpreter() (in pystate.c) if it
654+
// didn't depend on interp->feature_flags being set already.
655+
_PyObject_InitState(interp);
656+
653657
PyThreadState *tstate = _PyThreadState_New(interp);
654658
if (tstate == NULL) {
655659
return _PyStatus_ERR("can't make first thread");
@@ -2103,6 +2107,10 @@ new_interpreter(PyThreadState **tstate_p, const PyInterpreterConfig *config)
21032107
goto error;
21042108
}
21052109

2110+
// This could be done in init_interpreter() (in pystate.c) if it
2111+
// didn't depend on interp->feature_flags being set already.
2112+
_PyObject_InitState(interp);
2113+
21062114
status = init_interp_create_gil(tstate, config->gil);
21072115
if (_PyStatus_EXCEPTION(status)) {
21082116
goto error;

Python/pystate.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -686,7 +686,9 @@ init_interpreter(PyInterpreterState *interp,
686686
_obmalloc_pools_INIT(interp->obmalloc.pools);
687687
memcpy(&interp->obmalloc.pools.used, temp, sizeof(temp));
688688
}
689-
_PyObject_InitState(interp);
689+
690+
// We would call _PyObject_InitState() at this point
691+
// if interp->feature_flags were alredy set.
690692

691693
_PyEval_InitState(interp, pending_lock);
692694
_PyGC_InitState(&interp->gc);

0 commit comments

Comments
 (0)