Skip to content

Commit 58ef741

Browse files
gh-107080: Fix Py_TRACE_REFS Crashes Under Isolated Subinterpreters (gh-107567)
The linked list of objects was a global variable, which broke isolation between interpreters, causing crashes. To solve this, we've moved the linked list to each interpreter.
1 parent 14fbd4e commit 58ef741

File tree

6 files changed

+62
-29
lines changed

6 files changed

+62
-29
lines changed

Include/internal/pycore_object.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -292,8 +292,8 @@ extern void _PyDebug_PrintTotalRefs(void);
292292

293293
#ifdef Py_TRACE_REFS
294294
extern void _Py_AddToAllObjects(PyObject *op, int force);
295-
extern void _Py_PrintReferences(FILE *);
296-
extern void _Py_PrintReferenceAddresses(FILE *);
295+
extern void _Py_PrintReferences(PyInterpreterState *, FILE *);
296+
extern void _Py_PrintReferenceAddresses(PyInterpreterState *, FILE *);
297297
#endif
298298

299299

Include/internal/pycore_object_state.h

Lines changed: 9 additions & 4 deletions
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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ extern PyTypeObject _PyExc_MemoryError;
157157
{ .threshold = 10, }, \
158158
}, \
159159
}, \
160+
.object_state = _py_object_state_INIT(INTERP), \
160161
.dtoa = _dtoa_state_INIT(&(INTERP)), \
161162
.dict_state = _dict_state_INIT, \
162163
.func_state = { \
@@ -186,6 +187,16 @@ extern PyTypeObject _PyExc_MemoryError;
186187
.context_ver = 1, \
187188
}
188189

190+
#ifdef Py_TRACE_REFS
191+
# define _py_object_state_INIT(INTERP) \
192+
{ \
193+
.refchain = {&INTERP.object_state.refchain, &INTERP.object_state.refchain}, \
194+
}
195+
#else
196+
# define _py_object_state_INIT(INTERP) \
197+
{ 0 }
198+
#endif
199+
189200

190201
// global objects
191202

Lines changed: 4 additions & 0 deletions
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

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -159,11 +159,8 @@ _PyDebug_PrintTotalRefs(void) {
159159
Do not call them otherwise, they do not initialize the object! */
160160

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

168165
/* Insert op at the front of the list of all objects. If force is true,
169166
* op is added even if _ob_prev and _ob_next are non-NULL already. If
@@ -188,10 +185,11 @@ _Py_AddToAllObjects(PyObject *op, int force)
188185
}
189186
#endif
190187
if (force || op->_ob_prev == NULL) {
191-
op->_ob_next = refchain._ob_next;
192-
op->_ob_prev = &refchain;
193-
refchain._ob_next->_ob_prev = op;
194-
refchain._ob_next = op;
188+
PyObject *refchain = REFCHAIN(_PyInterpreterState_GET());
189+
op->_ob_next = refchain->_ob_next;
190+
op->_ob_prev = refchain;
191+
refchain->_ob_next->_ob_prev = op;
192+
refchain->_ob_next = op;
195193
}
196194
}
197195
#endif /* Py_TRACE_REFS */
@@ -2229,20 +2227,21 @@ _Py_ForgetReference(PyObject *op)
22292227
_PyObject_ASSERT_FAILED_MSG(op, "negative refcnt");
22302228
}
22312229

2232-
if (op == &refchain ||
2230+
PyObject *refchain = REFCHAIN(_PyInterpreterState_GET());
2231+
if (op == refchain ||
22332232
op->_ob_prev->_ob_next != op || op->_ob_next->_ob_prev != op)
22342233
{
22352234
_PyObject_ASSERT_FAILED_MSG(op, "invalid object chain");
22362235
}
22372236

22382237
#ifdef SLOW_UNREF_CHECK
22392238
PyObject *p;
2240-
for (p = refchain._ob_next; p != &refchain; p = p->_ob_next) {
2239+
for (p = refchain->_ob_next; p != refchain; p = p->_ob_next) {
22412240
if (p == op) {
22422241
break;
22432242
}
22442243
}
2245-
if (p == &refchain) {
2244+
if (p == refchain) {
22462245
/* Not found */
22472246
_PyObject_ASSERT_FAILED_MSG(op,
22482247
"object not found in the objects list");
@@ -2258,11 +2257,15 @@ _Py_ForgetReference(PyObject *op)
22582257
* interpreter must be in a healthy state.
22592258
*/
22602259
void
2261-
_Py_PrintReferences(FILE *fp)
2260+
_Py_PrintReferences(PyInterpreterState *interp, FILE *fp)
22622261
{
22632262
PyObject *op;
2263+
if (interp == NULL) {
2264+
interp = _PyInterpreterState_Main();
2265+
}
22642266
fprintf(fp, "Remaining objects:\n");
2265-
for (op = refchain._ob_next; op != &refchain; op = op->_ob_next) {
2267+
PyObject *refchain = REFCHAIN(interp);
2268+
for (op = refchain->_ob_next; op != refchain; op = op->_ob_next) {
22662269
fprintf(fp, "%p [%zd] ", (void *)op, Py_REFCNT(op));
22672270
if (PyObject_Print(op, fp, 0) != 0) {
22682271
PyErr_Clear();
@@ -2274,34 +2277,42 @@ _Py_PrintReferences(FILE *fp)
22742277
/* Print the addresses of all live objects. Unlike _Py_PrintReferences, this
22752278
* doesn't make any calls to the Python C API, so is always safe to call.
22762279
*/
2280+
// XXX This function is not safe to use if the interpreter has been
2281+
// freed or is in an unhealthy state (e.g. late in finalization).
2282+
// The call in Py_FinalizeEx() is okay since the main interpreter
2283+
// is statically allocated.
22772284
void
2278-
_Py_PrintReferenceAddresses(FILE *fp)
2285+
_Py_PrintReferenceAddresses(PyInterpreterState *interp, FILE *fp)
22792286
{
22802287
PyObject *op;
2288+
PyObject *refchain = REFCHAIN(interp);
22812289
fprintf(fp, "Remaining object addresses:\n");
2282-
for (op = refchain._ob_next; op != &refchain; op = op->_ob_next)
2290+
for (op = refchain->_ob_next; op != refchain; op = op->_ob_next)
22832291
fprintf(fp, "%p [%zd] %s\n", (void *)op,
22842292
Py_REFCNT(op), Py_TYPE(op)->tp_name);
22852293
}
22862294

2295+
/* The implementation of sys.getobjects(). */
22872296
PyObject *
22882297
_Py_GetObjects(PyObject *self, PyObject *args)
22892298
{
22902299
int i, n;
22912300
PyObject *t = NULL;
22922301
PyObject *res, *op;
2302+
PyInterpreterState *interp = _PyInterpreterState_GET();
22932303

22942304
if (!PyArg_ParseTuple(args, "i|O", &n, &t))
22952305
return NULL;
2296-
op = refchain._ob_next;
2306+
PyObject *refchain = REFCHAIN(interp);
2307+
op = refchain->_ob_next;
22972308
res = PyList_New(0);
22982309
if (res == NULL)
22992310
return NULL;
2300-
for (i = 0; (n == 0 || i < n) && op != &refchain; i++) {
2311+
for (i = 0; (n == 0 || i < n) && op != refchain; i++) {
23012312
while (op == self || op == args || op == res || op == t ||
23022313
(t != NULL && !Py_IS_TYPE(op, (PyTypeObject *) t))) {
23032314
op = op->_ob_next;
2304-
if (op == &refchain)
2315+
if (op == refchain)
23052316
return res;
23062317
}
23072318
if (PyList_Append(res, op) < 0) {
@@ -2313,6 +2324,8 @@ _Py_GetObjects(PyObject *self, PyObject *args)
23132324
return res;
23142325
}
23152326

2327+
#undef REFCHAIN
2328+
23162329
#endif
23172330

23182331

Python/pylifecycle.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1921,11 +1921,11 @@ Py_FinalizeEx(void)
19211921
}
19221922

19231923
if (dump_refs) {
1924-
_Py_PrintReferences(stderr);
1924+
_Py_PrintReferences(tstate->interp, stderr);
19251925
}
19261926

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

@@ -1961,11 +1961,11 @@ Py_FinalizeEx(void)
19611961
*/
19621962

19631963
if (dump_refs) {
1964-
_Py_PrintReferenceAddresses(stderr);
1964+
_Py_PrintReferenceAddresses(tstate->interp, stderr);
19651965
}
19661966

19671967
if (dump_refs_fp != NULL) {
1968-
_Py_PrintReferenceAddresses(dump_refs_fp);
1968+
_Py_PrintReferenceAddresses(tstate->interp, dump_refs_fp);
19691969
fclose(dump_refs_fp);
19701970
}
19711971
#endif /* Py_TRACE_REFS */

0 commit comments

Comments
 (0)