Skip to content

Commit

Permalink
[3.12] pythongh-125286: Share the Main Refchain With Legacy Interpret…
Browse files Browse the repository at this point in the history
…ers (pythongh-125709)

They used to be shared, before 3.12.  Returning to sharing them resolves a failure on Py_TRACE_REFS builds.
  • Loading branch information
ericsnowcurrently authored and encukou committed Nov 13, 2024
1 parent 762cb25 commit cdb58a7
Show file tree
Hide file tree
Showing 9 changed files with 77 additions and 58 deletions.
29 changes: 29 additions & 0 deletions Doc/library/sys.rst
Original file line number Diff line number Diff line change
Expand Up @@ -907,6 +907,35 @@ always available.
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()

.. index::
Expand Down
2 changes: 1 addition & 1 deletion Doc/using/configure.rst
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@ Debug options
Effects:

* Define the ``Py_TRACE_REFS`` macro.
* Add :func:`!sys.getobjects` function.
* Add :func:`sys.getobjects` function.
* Add :envvar:`PYTHONDUMPREFS` environment variable.

This build is not ABI compatible with release build (default build) or debug
Expand Down
11 changes: 11 additions & 0 deletions Doc/whatsnew/3.12.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2294,3 +2294,14 @@ email
check if the *strict* paramater is available.
(Contributed by Thomas Dwyer and Victor Stinner for :gh:`102988` to improve
the CVE-2023-27043 fix.)


Notable changes in 3.12.8
=========================

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.
8 changes: 7 additions & 1 deletion Include/internal/pycore_object_state.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,13 @@ struct _py_object_state {
* together via the _ob_prev and _ob_next members of a PyObject, which
* exist only in a Py_TRACE_REFS build.
*/
PyObject refchain;
PyObject *refchain;
/* In most cases, refchain points to _refchain_obj.
* In sub-interpreters that share objmalloc state with the main interp,
* refchain points to the main interpreter's _refchain_obj, and their own
* _refchain_obj is unused.
*/
PyObject _refchain_obj;
#endif
int _not_used;
};
Expand Down
7 changes: 0 additions & 7 deletions Include/internal/pycore_runtime_init.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,15 +132,8 @@ extern PyTypeObject _PyExc_MemoryError;
.context_ver = 1, \
}

#ifdef Py_TRACE_REFS
# define _py_object_state_INIT(INTERP) \
{ \
.refchain = {&INTERP.object_state.refchain, &INTERP.object_state.refchain}, \
}
#else
# define _py_object_state_INIT(INTERP) \
{ 0 }
#endif


// global objects
Expand Down
58 changes: 18 additions & 40 deletions Objects/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -159,11 +159,27 @@ _PyDebug_PrintTotalRefs(void) {

#ifdef Py_TRACE_REFS

#define REFCHAIN(interp) &interp->object_state.refchain
#define REFCHAIN(interp) interp->object_state.refchain

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 inline void
init_refchain(PyInterpreterState *interp)
{
if (!has_own_refchain(interp)) {
// Legacy subinterpreters share a refchain with the main interpreter.
REFCHAIN(interp) = REFCHAIN(_PyInterpreterState_Main());
return;
}
REFCHAIN(interp) = &interp->object_state._refchain_obj;
PyObject *refchain = REFCHAIN(interp);
refchain->_ob_prev = refchain;
refchain->_ob_next = refchain;
Expand Down Expand Up @@ -2010,9 +2026,7 @@ void
_PyObject_InitState(PyInterpreterState *interp)
{
#ifdef Py_TRACE_REFS
if (!_Py_IsMainInterpreter(interp)) {
init_refchain(interp);
}
init_refchain(interp);
#endif
}

Expand Down Expand Up @@ -2218,42 +2232,6 @@ _Py_NewReferenceNoTotal(PyObject *op)


#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
_Py_ForgetReference(PyObject *op)
{
Expand Down
8 changes: 0 additions & 8 deletions Objects/unicodeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -14966,10 +14966,6 @@ _PyUnicode_InternStatic(PyInterpreterState *interp, PyObject **p)
assert(*p);
}

#ifdef Py_TRACE_REFS
extern void _Py_NormalizeImmortalReference(PyObject *);
#endif

static void
immortalize_interned(PyObject *s)
{
Expand All @@ -14985,10 +14981,6 @@ immortalize_interned(PyObject *s)
#endif
_PyUnicode_STATE(s).interned = SSTATE_INTERNED_IMMORTAL;
_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*
Expand Down
8 changes: 8 additions & 0 deletions Python/pylifecycle.c
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,10 @@ pycore_create_interpreter(_PyRuntimeState *runtime,
return status;
}

// This could be done in init_interpreter() (in pystate.c) if it
// didn't depend on interp->feature_flags being set already.
_PyObject_InitState(interp);

PyThreadState *tstate = _PyThreadState_New(interp);
if (tstate == NULL) {
return _PyStatus_ERR("can't make first thread");
Expand Down Expand Up @@ -2103,6 +2107,10 @@ new_interpreter(PyThreadState **tstate_p, const PyInterpreterConfig *config)
goto error;
}

// This could be done in init_interpreter() (in pystate.c) if it
// didn't depend on interp->feature_flags being set already.
_PyObject_InitState(interp);

status = init_interp_create_gil(tstate, config->gil);
if (_PyStatus_EXCEPTION(status)) {
goto error;
Expand Down
4 changes: 3 additions & 1 deletion Python/pystate.c
Original file line number Diff line number Diff line change
Expand Up @@ -686,7 +686,9 @@ init_interpreter(PyInterpreterState *interp,
_obmalloc_pools_INIT(interp->obmalloc.pools);
memcpy(&interp->obmalloc.pools.used, temp, sizeof(temp));
}
_PyObject_InitState(interp);

// We would call _PyObject_InitState() at this point
// if interp->feature_flags were alredy set.

_PyEval_InitState(interp, pending_lock);
_PyGC_InitState(&interp->gc);
Expand Down

0 comments on commit cdb58a7

Please sign in to comment.