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 <encukou@gmail.com>
This commit is contained in:
Eric Snow 2024-10-23 10:10:06 -06:00 committed by GitHub
parent de0d5c6e2e
commit 6f26d496d3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 99 additions and 61 deletions

View File

@ -920,6 +920,35 @@ always available.
It is not guaranteed to exist in all implementations of Python. It is not guaranteed to exist in all implementations of Python.
.. function:: getobjects(limit[, type])
This function only exists if CPython was built using the
specialized configure option :option:`--with-trace-refs`.
It is intended only for debugging garbage-collection issues.
Return a list of up to *limit* dynamically allocated Python objects.
If *type* is given, only objects of that exact type (not subtypes)
are included.
Objects from the list are not safe to use.
Specifically, the result will include objects from all interpreters that
share their object allocator state (that is, ones created with
:c:member:`PyInterpreterConfig.use_main_obmalloc` set to 1
or using :c:func:`Py_NewInterpreter`, and the
:ref:`main interpreter <sub-interpreter-support>`).
Mixing objects from different interpreters may lead to crashes
or other unexpected behavior.
.. impl-detail::
This function should be used for specialized purposes only.
It is not guaranteed to exist in all implementations of Python.
.. versionchanged:: next
The result may include objects from other interpreters.
.. function:: getprofile() .. function:: getprofile()
.. index:: .. index::

View File

@ -702,7 +702,7 @@ Debug options
Effects: Effects:
* Define the ``Py_TRACE_REFS`` macro. * Define the ``Py_TRACE_REFS`` macro.
* Add :func:`!sys.getobjects` function. * Add :func:`sys.getobjects` function.
* Add :envvar:`PYTHONDUMPREFS` environment variable. * Add :envvar:`PYTHONDUMPREFS` environment variable.
The :envvar:`PYTHONDUMPREFS` environment variable can be used to dump The :envvar:`PYTHONDUMPREFS` environment variable can be used to dump

View File

@ -416,6 +416,15 @@ symtable
(Contributed by Bénédikt Tran in :gh:`120029`.) (Contributed by Bénédikt Tran in :gh:`120029`.)
sys
---
* The previously undocumented special function :func:`sys.getobjects`,
which only exists in specialized builds of Python, may now return objects
from other interpreters than the one it's called in.
unicodedata unicodedata
----------- -----------

View File

@ -171,6 +171,48 @@ _PyDebug_PrintTotalRefs(void) {
#define REFCHAIN(interp) interp->object_state.refchain #define REFCHAIN(interp) interp->object_state.refchain
#define REFCHAIN_VALUE ((void*)(uintptr_t)1) #define REFCHAIN_VALUE ((void*)(uintptr_t)1)
static inline int
has_own_refchain(PyInterpreterState *interp)
{
if (interp->feature_flags & Py_RTFLAGS_USE_MAIN_OBMALLOC) {
return (_Py_IsMainInterpreter(interp)
|| _PyInterpreterState_Main() == NULL);
}
return 1;
}
static int
refchain_init(PyInterpreterState *interp)
{
if (!has_own_refchain(interp)) {
// Legacy subinterpreters share a refchain with the main interpreter.
REFCHAIN(interp) = REFCHAIN(_PyInterpreterState_Main());
return 0;
}
_Py_hashtable_allocator_t alloc = {
// Don't use default PyMem_Malloc() and PyMem_Free() which
// require the caller to hold the GIL.
.malloc = PyMem_RawMalloc,
.free = PyMem_RawFree,
};
REFCHAIN(interp) = _Py_hashtable_new_full(
_Py_hashtable_hash_ptr, _Py_hashtable_compare_direct,
NULL, NULL, &alloc);
if (REFCHAIN(interp) == NULL) {
return -1;
}
return 0;
}
static void
refchain_fini(PyInterpreterState *interp)
{
if (has_own_refchain(interp) && REFCHAIN(interp) != NULL) {
_Py_hashtable_destroy(REFCHAIN(interp));
}
REFCHAIN(interp) = NULL;
}
bool bool
_PyRefchain_IsTraced(PyInterpreterState *interp, PyObject *obj) _PyRefchain_IsTraced(PyInterpreterState *interp, PyObject *obj)
{ {
@ -2191,16 +2233,7 @@ PyStatus
_PyObject_InitState(PyInterpreterState *interp) _PyObject_InitState(PyInterpreterState *interp)
{ {
#ifdef Py_TRACE_REFS #ifdef Py_TRACE_REFS
_Py_hashtable_allocator_t alloc = { if (refchain_init(interp) < 0) {
// Don't use default PyMem_Malloc() and PyMem_Free() which
// require the caller to hold the GIL.
.malloc = PyMem_RawMalloc,
.free = PyMem_RawFree,
};
REFCHAIN(interp) = _Py_hashtable_new_full(
_Py_hashtable_hash_ptr, _Py_hashtable_compare_direct,
NULL, NULL, &alloc);
if (REFCHAIN(interp) == NULL) {
return _PyStatus_NO_MEMORY(); return _PyStatus_NO_MEMORY();
} }
#endif #endif
@ -2211,8 +2244,7 @@ void
_PyObject_FiniState(PyInterpreterState *interp) _PyObject_FiniState(PyInterpreterState *interp)
{ {
#ifdef Py_TRACE_REFS #ifdef Py_TRACE_REFS
_Py_hashtable_destroy(REFCHAIN(interp)); refchain_fini(interp);
REFCHAIN(interp) = NULL;
#endif #endif
} }
@ -2501,42 +2533,6 @@ _Py_ResurrectReference(PyObject *op)
#ifdef Py_TRACE_REFS #ifdef Py_TRACE_REFS
/* Make sure the ref is associated with the right interpreter.
* This only needs special attention for heap-allocated objects
* that have been immortalized, and only when the object might
* outlive the interpreter where it was created. That means the
* object was necessarily created using a global allocator
* (i.e. from the main interpreter). Thus in that specific case
* we move the object over to the main interpreter's refchain.
*
* This was added for the sake of the immortal interned strings,
* where legacy subinterpreters share the main interpreter's
* interned dict (and allocator), and therefore the strings can
* outlive the subinterpreter.
*
* It may make sense to fold this into _Py_SetImmortalUntracked(),
* but that requires further investigation. In the meantime, it is
* up to the caller to know if this is needed. There should be
* very few cases.
*/
void
_Py_NormalizeImmortalReference(PyObject *op)
{
assert(_Py_IsImmortal(op));
PyInterpreterState *interp = _PyInterpreterState_GET();
if (!_PyRefchain_IsTraced(interp, op)) {
return;
}
PyInterpreterState *main_interp = _PyInterpreterState_Main();
if (interp != main_interp
&& interp->feature_flags & Py_RTFLAGS_USE_MAIN_OBMALLOC)
{
assert(!_PyRefchain_IsTraced(main_interp, op));
_PyRefchain_Remove(interp, op);
_PyRefchain_Trace(main_interp, op);
}
}
void void
_Py_ForgetReference(PyObject *op) _Py_ForgetReference(PyObject *op)
{ {

View File

@ -15444,10 +15444,6 @@ _PyUnicode_InternStatic(PyInterpreterState *interp, PyObject **p)
assert(*p); assert(*p);
} }
#ifdef Py_TRACE_REFS
extern void _Py_NormalizeImmortalReference(PyObject *);
#endif
static void static void
immortalize_interned(PyObject *s) immortalize_interned(PyObject *s)
{ {
@ -15463,10 +15459,6 @@ immortalize_interned(PyObject *s)
#endif #endif
_PyUnicode_STATE(s).interned = SSTATE_INTERNED_IMMORTAL; _PyUnicode_STATE(s).interned = SSTATE_INTERNED_IMMORTAL;
_Py_SetImmortal(s); _Py_SetImmortal(s);
#ifdef Py_TRACE_REFS
/* Make sure the ref is associated with the right interpreter. */
_Py_NormalizeImmortalReference(s);
#endif
} }
static /* non-null */ PyObject* static /* non-null */ PyObject*

View File

@ -674,6 +674,13 @@ pycore_create_interpreter(_PyRuntimeState *runtime,
return status; return status;
} }
// This could be done in init_interpreter() (in pystate.c) if it
// didn't depend on interp->feature_flags being set already.
status = _PyObject_InitState(interp);
if (_PyStatus_EXCEPTION(status)) {
return status;
}
// initialize the interp->obmalloc state. This must be done after // initialize the interp->obmalloc state. This must be done after
// the settings are loaded (so that feature_flags are set) but before // the settings are loaded (so that feature_flags are set) but before
// any calls are made to obmalloc functions. // any calls are made to obmalloc functions.
@ -2297,6 +2304,13 @@ new_interpreter(PyThreadState **tstate_p,
goto error; goto error;
} }
// This could be done in init_interpreter() (in pystate.c) if it
// didn't depend on interp->feature_flags being set already.
status = _PyObject_InitState(interp);
if (_PyStatus_EXCEPTION(status)) {
return status;
}
// initialize the interp->obmalloc state. This must be done after // initialize the interp->obmalloc state. This must be done after
// the settings are loaded (so that feature_flags are set) but before // the settings are loaded (so that feature_flags are set) but before
// any calls are made to obmalloc functions. // any calls are made to obmalloc functions.

View File

@ -629,10 +629,8 @@ init_interpreter(PyInterpreterState *interp,
assert(next != NULL || (interp == runtime->interpreters.main)); assert(next != NULL || (interp == runtime->interpreters.main));
interp->next = next; interp->next = next;
PyStatus status = _PyObject_InitState(interp); // We would call _PyObject_InitState() at this point
if (_PyStatus_EXCEPTION(status)) { // if interp->feature_flags were alredy set.
return status;
}
_PyEval_InitState(interp); _PyEval_InitState(interp);
_PyGC_InitState(&interp->gc); _PyGC_InitState(&interp->gc);