Skip to content

Commit aa9707d

Browse files
[3.12] gh-107080: Fix Py_TRACE_REFS Crashes Under Isolated Subinterpreters (#107751)
* Unrevert "[3.12] gh-107080: Fix Py_TRACE_REFS Crashes Under Isolated Subinterpreters (gh-107567) (#107599)". This reverts commit 6e4eec7 (gh-107648). * Initialize each interpreter's refchain properly. * Skip test_basic_multiple_interpreters_deleted_no_reset on tracerefs builds.
1 parent bd2ef82 commit aa9707d

File tree

8 files changed

+93
-30
lines changed

8 files changed

+93
-30
lines changed

Include/internal/pycore_object.h

+3-2
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ _PyType_HasFeature(PyTypeObject *type, unsigned long feature) {
152152

153153
extern void _PyType_InitCache(PyInterpreterState *interp);
154154

155+
extern void _PyObject_InitState(PyInterpreterState *interp);
155156

156157
/* Inline functions trading binary compatibility for speed:
157158
_PyObject_Init() is the fast version of PyObject_Init(), and
@@ -271,8 +272,8 @@ extern void _PyDebug_PrintTotalRefs(void);
271272

272273
#ifdef Py_TRACE_REFS
273274
extern void _Py_AddToAllObjects(PyObject *op, int force);
274-
extern void _Py_PrintReferences(FILE *);
275-
extern void _Py_PrintReferenceAddresses(FILE *);
275+
extern void _Py_PrintReferences(PyInterpreterState *, FILE *);
276+
extern void _Py_PrintReferenceAddresses(PyInterpreterState *, FILE *);
276277
#endif
277278

278279

Include/internal/pycore_object_state.h

+9-4
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,22 @@ extern "C" {
1111
struct _py_object_runtime_state {
1212
#ifdef Py_REF_DEBUG
1313
Py_ssize_t interpreter_leaks;
14-
#else
15-
int _not_used;
1614
#endif
15+
int _not_used;
1716
};
1817

1918
struct _py_object_state {
2019
#ifdef Py_REF_DEBUG
2120
Py_ssize_t reftotal;
22-
#else
23-
int _not_used;
2421
#endif
22+
#ifdef Py_TRACE_REFS
23+
/* Head of circular doubly-linked list of all objects. These are linked
24+
* together via the _ob_prev and _ob_next members of a PyObject, which
25+
* exist only in a Py_TRACE_REFS build.
26+
*/
27+
PyObject refchain;
28+
#endif
29+
int _not_used;
2530
};
2631

2732

Include/internal/pycore_runtime_init.h

+11
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ extern PyTypeObject _PyExc_MemoryError;
101101
{ .threshold = 10, }, \
102102
}, \
103103
}, \
104+
.object_state = _py_object_state_INIT(INTERP), \
104105
.dtoa = _dtoa_state_INIT(&(INTERP)), \
105106
.dict_state = _dict_state_INIT, \
106107
.func_state = { \
@@ -130,6 +131,16 @@ extern PyTypeObject _PyExc_MemoryError;
130131
.context_ver = 1, \
131132
}
132133

134+
#ifdef Py_TRACE_REFS
135+
# define _py_object_state_INIT(INTERP) \
136+
{ \
137+
.refchain = {&INTERP.object_state.refchain, &INTERP.object_state.refchain}, \
138+
}
139+
#else
140+
# define _py_object_state_INIT(INTERP) \
141+
{ 0 }
142+
#endif
143+
133144

134145
// global objects
135146

Lib/test/test_import/__init__.py

+6
Original file line numberDiff line numberDiff line change
@@ -2539,6 +2539,12 @@ def test_basic_multiple_interpreters_main_no_reset(self):
25392539
def test_basic_multiple_interpreters_deleted_no_reset(self):
25402540
# without resetting; already loaded in a deleted interpreter
25412541

2542+
if hasattr(sys, 'getobjects'):
2543+
# It's a Py_TRACE_REFS build.
2544+
# This test breaks interpreter isolation a little,
2545+
# which causes problems on Py_TRACE_REF builds.
2546+
raise unittest.SkipTest('crashes on Py_TRACE_REFS builds')
2547+
25422548
# At this point:
25432549
# * alive in 0 interpreters
25442550
# * module def may or may not be loaded already
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Trace refs builds (``--with-trace-refs``) were crashing when used with
2+
isolated subinterpreters. The problematic global state has been isolated to
3+
each interpreter. Other fixing the crashes, this change does not affect
4+
users.

Objects/object.c

+53-20
Original file line numberDiff line numberDiff line change
@@ -158,11 +158,16 @@ _PyDebug_PrintTotalRefs(void) {
158158
Do not call them otherwise, they do not initialize the object! */
159159

160160
#ifdef Py_TRACE_REFS
161-
/* Head of circular doubly-linked list of all objects. These are linked
162-
* together via the _ob_prev and _ob_next members of a PyObject, which
163-
* exist only in a Py_TRACE_REFS build.
164-
*/
165-
static PyObject refchain = {&refchain, &refchain};
161+
162+
#define REFCHAIN(interp) &interp->object_state.refchain
163+
164+
static inline void
165+
init_refchain(PyInterpreterState *interp)
166+
{
167+
PyObject *refchain = REFCHAIN(interp);
168+
refchain->_ob_prev = refchain;
169+
refchain->_ob_next = refchain;
170+
}
166171

167172
/* Insert op at the front of the list of all objects. If force is true,
168173
* op is added even if _ob_prev and _ob_next are non-NULL already. If
@@ -187,10 +192,11 @@ _Py_AddToAllObjects(PyObject *op, int force)
187192
}
188193
#endif
189194
if (force || op->_ob_prev == NULL) {
190-
op->_ob_next = refchain._ob_next;
191-
op->_ob_prev = &refchain;
192-
refchain._ob_next->_ob_prev = op;
193-
refchain._ob_next = op;
195+
PyObject *refchain = REFCHAIN(_PyInterpreterState_GET());
196+
op->_ob_next = refchain->_ob_next;
197+
op->_ob_prev = refchain;
198+
refchain->_ob_next->_ob_prev = op;
199+
refchain->_ob_next = op;
194200
}
195201
}
196202
#endif /* Py_TRACE_REFS */
@@ -1998,6 +2004,18 @@ PyObject _Py_NotImplementedStruct = {
19982004
&_PyNotImplemented_Type
19992005
};
20002006

2007+
2008+
void
2009+
_PyObject_InitState(PyInterpreterState *interp)
2010+
{
2011+
#ifdef Py_TRACE_REFS
2012+
if (!_Py_IsMainInterpreter(interp)) {
2013+
init_refchain(interp);
2014+
}
2015+
#endif
2016+
}
2017+
2018+
20012019
extern PyTypeObject _Py_GenericAliasIterType;
20022020
extern PyTypeObject _PyMemoryIter_Type;
20032021
extern PyTypeObject _PyLineIterator;
@@ -2206,20 +2224,21 @@ _Py_ForgetReference(PyObject *op)
22062224
_PyObject_ASSERT_FAILED_MSG(op, "negative refcnt");
22072225
}
22082226

2209-
if (op == &refchain ||
2227+
PyObject *refchain = REFCHAIN(_PyInterpreterState_GET());
2228+
if (op == refchain ||
22102229
op->_ob_prev->_ob_next != op || op->_ob_next->_ob_prev != op)
22112230
{
22122231
_PyObject_ASSERT_FAILED_MSG(op, "invalid object chain");
22132232
}
22142233

22152234
#ifdef SLOW_UNREF_CHECK
22162235
PyObject *p;
2217-
for (p = refchain._ob_next; p != &refchain; p = p->_ob_next) {
2236+
for (p = refchain->_ob_next; p != refchain; p = p->_ob_next) {
22182237
if (p == op) {
22192238
break;
22202239
}
22212240
}
2222-
if (p == &refchain) {
2241+
if (p == refchain) {
22232242
/* Not found */
22242243
_PyObject_ASSERT_FAILED_MSG(op,
22252244
"object not found in the objects list");
@@ -2235,11 +2254,15 @@ _Py_ForgetReference(PyObject *op)
22352254
* interpreter must be in a healthy state.
22362255
*/
22372256
void
2238-
_Py_PrintReferences(FILE *fp)
2257+
_Py_PrintReferences(PyInterpreterState *interp, FILE *fp)
22392258
{
22402259
PyObject *op;
2260+
if (interp == NULL) {
2261+
interp = _PyInterpreterState_Main();
2262+
}
22412263
fprintf(fp, "Remaining objects:\n");
2242-
for (op = refchain._ob_next; op != &refchain; op = op->_ob_next) {
2264+
PyObject *refchain = REFCHAIN(interp);
2265+
for (op = refchain->_ob_next; op != refchain; op = op->_ob_next) {
22432266
fprintf(fp, "%p [%zd] ", (void *)op, Py_REFCNT(op));
22442267
if (PyObject_Print(op, fp, 0) != 0) {
22452268
PyErr_Clear();
@@ -2251,34 +2274,42 @@ _Py_PrintReferences(FILE *fp)
22512274
/* Print the addresses of all live objects. Unlike _Py_PrintReferences, this
22522275
* doesn't make any calls to the Python C API, so is always safe to call.
22532276
*/
2277+
// XXX This function is not safe to use if the interpreter has been
2278+
// freed or is in an unhealthy state (e.g. late in finalization).
2279+
// The call in Py_FinalizeEx() is okay since the main interpreter
2280+
// is statically allocated.
22542281
void
2255-
_Py_PrintReferenceAddresses(FILE *fp)
2282+
_Py_PrintReferenceAddresses(PyInterpreterState *interp, FILE *fp)
22562283
{
22572284
PyObject *op;
2285+
PyObject *refchain = REFCHAIN(interp);
22582286
fprintf(fp, "Remaining object addresses:\n");
2259-
for (op = refchain._ob_next; op != &refchain; op = op->_ob_next)
2287+
for (op = refchain->_ob_next; op != refchain; op = op->_ob_next)
22602288
fprintf(fp, "%p [%zd] %s\n", (void *)op,
22612289
Py_REFCNT(op), Py_TYPE(op)->tp_name);
22622290
}
22632291

2292+
/* The implementation of sys.getobjects(). */
22642293
PyObject *
22652294
_Py_GetObjects(PyObject *self, PyObject *args)
22662295
{
22672296
int i, n;
22682297
PyObject *t = NULL;
22692298
PyObject *res, *op;
2299+
PyInterpreterState *interp = _PyInterpreterState_GET();
22702300

22712301
if (!PyArg_ParseTuple(args, "i|O", &n, &t))
22722302
return NULL;
2273-
op = refchain._ob_next;
2303+
PyObject *refchain = REFCHAIN(interp);
2304+
op = refchain->_ob_next;
22742305
res = PyList_New(0);
22752306
if (res == NULL)
22762307
return NULL;
2277-
for (i = 0; (n == 0 || i < n) && op != &refchain; i++) {
2308+
for (i = 0; (n == 0 || i < n) && op != refchain; i++) {
22782309
while (op == self || op == args || op == res || op == t ||
22792310
(t != NULL && !Py_IS_TYPE(op, (PyTypeObject *) t))) {
22802311
op = op->_ob_next;
2281-
if (op == &refchain)
2312+
if (op == refchain)
22822313
return res;
22832314
}
22842315
if (PyList_Append(res, op) < 0) {
@@ -2290,7 +2321,9 @@ _Py_GetObjects(PyObject *self, PyObject *args)
22902321
return res;
22912322
}
22922323

2293-
#endif
2324+
#undef REFCHAIN
2325+
2326+
#endif /* Py_TRACE_REFS */
22942327

22952328

22962329
/* Hack to force loading of abstract.o */

Python/pylifecycle.c

+6-4
Original file line numberDiff line numberDiff line change
@@ -1920,11 +1920,11 @@ Py_FinalizeEx(void)
19201920
}
19211921

19221922
if (dump_refs) {
1923-
_Py_PrintReferences(stderr);
1923+
_Py_PrintReferences(tstate->interp, stderr);
19241924
}
19251925

19261926
if (dump_refs_fp != NULL) {
1927-
_Py_PrintReferences(dump_refs_fp);
1927+
_Py_PrintReferences(tstate->interp, dump_refs_fp);
19281928
}
19291929
#endif /* Py_TRACE_REFS */
19301930

@@ -1960,11 +1960,11 @@ Py_FinalizeEx(void)
19601960
*/
19611961

19621962
if (dump_refs) {
1963-
_Py_PrintReferenceAddresses(stderr);
1963+
_Py_PrintReferenceAddresses(tstate->interp, stderr);
19641964
}
19651965

19661966
if (dump_refs_fp != NULL) {
1967-
_Py_PrintReferenceAddresses(dump_refs_fp);
1967+
_Py_PrintReferenceAddresses(tstate->interp, dump_refs_fp);
19681968
fclose(dump_refs_fp);
19691969
}
19701970
#endif /* Py_TRACE_REFS */
@@ -2074,6 +2074,8 @@ new_interpreter(PyThreadState **tstate_p, const PyInterpreterConfig *config)
20742074
}
20752075
has_gil = 1;
20762076

2077+
/* No objects have been created yet. */
2078+
20772079
status = pycore_interp_init(tstate);
20782080
if (_PyStatus_EXCEPTION(status)) {
20792081
goto error;

Python/pystate.c

+1
Original file line numberDiff line numberDiff line change
@@ -673,6 +673,7 @@ init_interpreter(PyInterpreterState *interp,
673673
_obmalloc_pools_INIT(interp->obmalloc.pools);
674674
memcpy(&interp->obmalloc.pools.used, temp, sizeof(temp));
675675
}
676+
_PyObject_InitState(interp);
676677

677678
_PyEval_InitState(interp, pending_lock);
678679
_PyGC_InitState(&interp->gc);

0 commit comments

Comments
 (0)