Skip to content

Commit 6f26d49

Browse files
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. Co-authored-by: Petr Viktorin <[email protected]>
1 parent de0d5c6 commit 6f26d49

File tree

7 files changed

+99
-61
lines changed

7 files changed

+99
-61
lines changed

Doc/library/sys.rst

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

922922

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

925954
.. index::

Doc/using/configure.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -702,7 +702,7 @@ Debug options
702702
Effects:
703703

704704
* Define the ``Py_TRACE_REFS`` macro.
705-
* Add :func:`!sys.getobjects` function.
705+
* Add :func:`sys.getobjects` function.
706706
* Add :envvar:`PYTHONDUMPREFS` environment variable.
707707

708708
The :envvar:`PYTHONDUMPREFS` environment variable can be used to dump

Doc/whatsnew/3.14.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,15 @@ symtable
416416

417417
(Contributed by Bénédikt Tran in :gh:`120029`.)
418418

419+
420+
sys
421+
---
422+
423+
* The previously undocumented special function :func:`sys.getobjects`,
424+
which only exists in specialized builds of Python, may now return objects
425+
from other interpreters than the one it's called in.
426+
427+
419428
unicodedata
420429
-----------
421430

Objects/object.c

Lines changed: 44 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,48 @@ _PyDebug_PrintTotalRefs(void) {
171171
#define REFCHAIN(interp) interp->object_state.refchain
172172
#define REFCHAIN_VALUE ((void*)(uintptr_t)1)
173173

174+
static inline int
175+
has_own_refchain(PyInterpreterState *interp)
176+
{
177+
if (interp->feature_flags & Py_RTFLAGS_USE_MAIN_OBMALLOC) {
178+
return (_Py_IsMainInterpreter(interp)
179+
|| _PyInterpreterState_Main() == NULL);
180+
}
181+
return 1;
182+
}
183+
184+
static int
185+
refchain_init(PyInterpreterState *interp)
186+
{
187+
if (!has_own_refchain(interp)) {
188+
// Legacy subinterpreters share a refchain with the main interpreter.
189+
REFCHAIN(interp) = REFCHAIN(_PyInterpreterState_Main());
190+
return 0;
191+
}
192+
_Py_hashtable_allocator_t alloc = {
193+
// Don't use default PyMem_Malloc() and PyMem_Free() which
194+
// require the caller to hold the GIL.
195+
.malloc = PyMem_RawMalloc,
196+
.free = PyMem_RawFree,
197+
};
198+
REFCHAIN(interp) = _Py_hashtable_new_full(
199+
_Py_hashtable_hash_ptr, _Py_hashtable_compare_direct,
200+
NULL, NULL, &alloc);
201+
if (REFCHAIN(interp) == NULL) {
202+
return -1;
203+
}
204+
return 0;
205+
}
206+
207+
static void
208+
refchain_fini(PyInterpreterState *interp)
209+
{
210+
if (has_own_refchain(interp) && REFCHAIN(interp) != NULL) {
211+
_Py_hashtable_destroy(REFCHAIN(interp));
212+
}
213+
REFCHAIN(interp) = NULL;
214+
}
215+
174216
bool
175217
_PyRefchain_IsTraced(PyInterpreterState *interp, PyObject *obj)
176218
{
@@ -2191,16 +2233,7 @@ PyStatus
21912233
_PyObject_InitState(PyInterpreterState *interp)
21922234
{
21932235
#ifdef Py_TRACE_REFS
2194-
_Py_hashtable_allocator_t alloc = {
2195-
// Don't use default PyMem_Malloc() and PyMem_Free() which
2196-
// require the caller to hold the GIL.
2197-
.malloc = PyMem_RawMalloc,
2198-
.free = PyMem_RawFree,
2199-
};
2200-
REFCHAIN(interp) = _Py_hashtable_new_full(
2201-
_Py_hashtable_hash_ptr, _Py_hashtable_compare_direct,
2202-
NULL, NULL, &alloc);
2203-
if (REFCHAIN(interp) == NULL) {
2236+
if (refchain_init(interp) < 0) {
22042237
return _PyStatus_NO_MEMORY();
22052238
}
22062239
#endif
@@ -2211,8 +2244,7 @@ void
22112244
_PyObject_FiniState(PyInterpreterState *interp)
22122245
{
22132246
#ifdef Py_TRACE_REFS
2214-
_Py_hashtable_destroy(REFCHAIN(interp));
2215-
REFCHAIN(interp) = NULL;
2247+
refchain_fini(interp);
22162248
#endif
22172249
}
22182250

@@ -2501,42 +2533,6 @@ _Py_ResurrectReference(PyObject *op)
25012533

25022534

25032535
#ifdef Py_TRACE_REFS
2504-
/* Make sure the ref is associated with the right interpreter.
2505-
* This only needs special attention for heap-allocated objects
2506-
* that have been immortalized, and only when the object might
2507-
* outlive the interpreter where it was created. That means the
2508-
* object was necessarily created using a global allocator
2509-
* (i.e. from the main interpreter). Thus in that specific case
2510-
* we move the object over to the main interpreter's refchain.
2511-
*
2512-
* This was added for the sake of the immortal interned strings,
2513-
* where legacy subinterpreters share the main interpreter's
2514-
* interned dict (and allocator), and therefore the strings can
2515-
* outlive the subinterpreter.
2516-
*
2517-
* It may make sense to fold this into _Py_SetImmortalUntracked(),
2518-
* but that requires further investigation. In the meantime, it is
2519-
* up to the caller to know if this is needed. There should be
2520-
* very few cases.
2521-
*/
2522-
void
2523-
_Py_NormalizeImmortalReference(PyObject *op)
2524-
{
2525-
assert(_Py_IsImmortal(op));
2526-
PyInterpreterState *interp = _PyInterpreterState_GET();
2527-
if (!_PyRefchain_IsTraced(interp, op)) {
2528-
return;
2529-
}
2530-
PyInterpreterState *main_interp = _PyInterpreterState_Main();
2531-
if (interp != main_interp
2532-
&& interp->feature_flags & Py_RTFLAGS_USE_MAIN_OBMALLOC)
2533-
{
2534-
assert(!_PyRefchain_IsTraced(main_interp, op));
2535-
_PyRefchain_Remove(interp, op);
2536-
_PyRefchain_Trace(main_interp, op);
2537-
}
2538-
}
2539-
25402536
void
25412537
_Py_ForgetReference(PyObject *op)
25422538
{

Objects/unicodeobject.c

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15444,10 +15444,6 @@ _PyUnicode_InternStatic(PyInterpreterState *interp, PyObject **p)
1544415444
assert(*p);
1544515445
}
1544615446

15447-
#ifdef Py_TRACE_REFS
15448-
extern void _Py_NormalizeImmortalReference(PyObject *);
15449-
#endif
15450-
1545115447
static void
1545215448
immortalize_interned(PyObject *s)
1545315449
{
@@ -15463,10 +15459,6 @@ immortalize_interned(PyObject *s)
1546315459
#endif
1546415460
_PyUnicode_STATE(s).interned = SSTATE_INTERNED_IMMORTAL;
1546515461
_Py_SetImmortal(s);
15466-
#ifdef Py_TRACE_REFS
15467-
/* Make sure the ref is associated with the right interpreter. */
15468-
_Py_NormalizeImmortalReference(s);
15469-
#endif
1547015462
}
1547115463

1547215464
static /* non-null */ PyObject*

Python/pylifecycle.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,13 @@ pycore_create_interpreter(_PyRuntimeState *runtime,
674674
return status;
675675
}
676676

677+
// This could be done in init_interpreter() (in pystate.c) if it
678+
// didn't depend on interp->feature_flags being set already.
679+
status = _PyObject_InitState(interp);
680+
if (_PyStatus_EXCEPTION(status)) {
681+
return status;
682+
}
683+
677684
// initialize the interp->obmalloc state. This must be done after
678685
// the settings are loaded (so that feature_flags are set) but before
679686
// any calls are made to obmalloc functions.
@@ -2297,6 +2304,13 @@ new_interpreter(PyThreadState **tstate_p,
22972304
goto error;
22982305
}
22992306

2307+
// This could be done in init_interpreter() (in pystate.c) if it
2308+
// didn't depend on interp->feature_flags being set already.
2309+
status = _PyObject_InitState(interp);
2310+
if (_PyStatus_EXCEPTION(status)) {
2311+
return status;
2312+
}
2313+
23002314
// initialize the interp->obmalloc state. This must be done after
23012315
// the settings are loaded (so that feature_flags are set) but before
23022316
// any calls are made to obmalloc functions.

Python/pystate.c

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -629,10 +629,8 @@ init_interpreter(PyInterpreterState *interp,
629629
assert(next != NULL || (interp == runtime->interpreters.main));
630630
interp->next = next;
631631

632-
PyStatus status = _PyObject_InitState(interp);
633-
if (_PyStatus_EXCEPTION(status)) {
634-
return status;
635-
}
632+
// We would call _PyObject_InitState() at this point
633+
// if interp->feature_flags were alredy set.
636634

637635
_PyEval_InitState(interp);
638636
_PyGC_InitState(&interp->gc);

0 commit comments

Comments
 (0)