Skip to content

Commit 6c45159

Browse files
Create weakrefs for subclasses with sentinel callback to properly handle them in GC
1 parent 3294f2e commit 6c45159

File tree

8 files changed

+71
-8
lines changed

8 files changed

+71
-8
lines changed

Include/cpython/weakrefobject.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ struct _PyWeakReference {
3838
*/
3939
PyMutex *weakrefs_lock;
4040
#endif
41-
uint8_t is_subclass;
4241
};
4342

4443
PyAPI_FUNC(void) _PyWeakref_ClearRef(PyWeakReference *self);

Include/internal/pycore_interp_structs.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -701,6 +701,7 @@ struct _Py_interp_static_objects {
701701
_PyGC_Head_UNUSED _hamt_empty_gc_not_used;
702702
PyHamtObject hamt_empty;
703703
PyBaseExceptionObject last_resort_memory_error;
704+
PyObject *subclasses_weakref_sentinel;
704705
} singletons;
705706
};
706707

Include/internal/pycore_weakref.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ extern "C" {
99
#endif
1010

1111
#include "pycore_critical_section.h" // Py_BEGIN_CRITICAL_SECTION()
12+
#include "pycore_interp_structs.h" // PyInterpreterState
1213
#include "pycore_lock.h" // PyMutex_LockFlags()
1314
#include "pycore_object.h" // _Py_REF_IS_MERGED()
1415
#include "pycore_pyatomic_ft_wrappers.h"
@@ -127,6 +128,10 @@ extern void _PyWeakref_ClearWeakRefsNoCallbacks(PyObject *obj);
127128

128129
PyAPI_FUNC(int) _PyWeakref_IsDead(PyObject *weakref);
129130

131+
int _PyWeakref_InitSubclassSentinel(PyInterpreterState *interp);
132+
PyObject * _PyWeakref_NewSubclassRef(PyObject *ob);
133+
int _PyWeakref_IsSubclassRef(PyWeakReference *weakref);
134+
130135
#ifdef __cplusplus
131136
}
132137
#endif

Objects/typeobject.c

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9259,15 +9259,12 @@ add_subclass(PyTypeObject *base, PyTypeObject *type)
92599259
if (key == NULL)
92609260
return -1;
92619261

9262-
PyObject *ref = PyWeakref_NewRef((PyObject *)type, NULL);
9262+
PyObject *ref = _PyWeakref_NewSubclassRef((PyObject *)type);
92639263
if (ref == NULL) {
92649264
Py_DECREF(key);
92659265
return -1;
92669266
}
92679267

9268-
((PyWeakReference*)ref)->is_subclass = 1;
9269-
9270-
92719268
// Only get tp_subclasses after creating the key and value.
92729269
// PyWeakref_NewRef() can trigger a garbage collection which can execute
92739270
// arbitrary Python code and so modify base->tp_subclasses.

Objects/weakrefobject.c

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#include "Python.h"
22
#include "pycore_critical_section.h"
3+
#include "pycore_global_objects.h" // _Py_INTERP_STATIC_OBJECT()
4+
#include "pycore_interp_structs.h" // _PyInterpreterState_GET()
35
#include "pycore_lock.h"
46
#include "pycore_modsupport.h" // _PyArg_NoKwnames()
57
#include "pycore_object.h" // _PyObject_GET_WEAKREFS_LISTPTR()
@@ -72,7 +74,6 @@ init_weakref(PyWeakReference *self, PyObject *ob, PyObject *callback)
7274
_PyObject_SetMaybeWeakref(ob);
7375
_PyObject_SetMaybeWeakref((PyObject *)self);
7476
#endif
75-
self->is_subclass = 0;
7677
}
7778

7879
// Clear the weakref and steal its callback into `callback`, if provided.
@@ -1133,3 +1134,55 @@ _PyWeakref_IsDead(PyObject *weakref)
11331134
{
11341135
return _PyWeakref_IS_DEAD(weakref);
11351136
}
1137+
1138+
int
1139+
_PyWeakref_InitSubclassSentinel(PyInterpreterState *interp)
1140+
{
1141+
PyObject *code = (PyObject *)PyCode_NewEmpty(
1142+
"<generated>",
1143+
"<subclasses_weakref_sentinel>",
1144+
0);
1145+
if (code == NULL) {
1146+
return -1;
1147+
}
1148+
1149+
PyObject *globals = PyDict_New();
1150+
if (globals == NULL) {
1151+
Py_DECREF(code);
1152+
return -1;
1153+
}
1154+
1155+
PyObject *func = PyFunction_New(code, globals);
1156+
Py_DECREF(globals);
1157+
Py_DECREF(code);
1158+
if (func == NULL) {
1159+
return -1;
1160+
}
1161+
1162+
_Py_INTERP_SINGLETON(interp, subclasses_weakref_sentinel) = func;
1163+
return 0;
1164+
}
1165+
1166+
PyObject *
1167+
_PyWeakref_NewSubclassRef(PyObject *ob)
1168+
{
1169+
PyInterpreterState *interp = _PyInterpreterState_GET();
1170+
if (interp != interp->runtime->interpreters.main) {
1171+
interp = interp->runtime->interpreters.main;
1172+
}
1173+
PyObject *func = _Py_INTERP_SINGLETON(interp, subclasses_weakref_sentinel);
1174+
return PyWeakref_NewRef(ob, func);
1175+
}
1176+
1177+
int
1178+
_PyWeakref_IsSubclassRef(PyWeakReference *weakref)
1179+
{
1180+
assert(weakref != NULL);
1181+
1182+
PyInterpreterState *interp = _PyInterpreterState_GET();
1183+
if (interp != interp->runtime->interpreters.main) {
1184+
interp = interp->runtime->interpreters.main;
1185+
}
1186+
PyObject *func = _Py_INTERP_SINGLETON(interp, subclasses_weakref_sentinel);
1187+
return weakref->wr_callback == func;
1188+
}

Python/gc.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -907,7 +907,7 @@ handle_weakrefs(PyGC_Head *unreachable, PyGC_Head *old)
907907
* will run and potentially cause a crash. See bpo-38006 for
908908
* one example.
909909
*/
910-
if (!((PyWeakReference *)op)->is_subclass) {
910+
if (!_PyWeakref_IsSubclassRef((PyWeakReference *)op)) {
911911
_PyWeakref_ClearRef((PyWeakReference *)op);
912912
}
913913
else {
@@ -935,7 +935,7 @@ handle_weakrefs(PyGC_Head *unreachable, PyGC_Head *old)
935935
PyGC_Head *wrasgc; /* AS_GC(wr) */
936936

937937
wr_next = wr->wr_next;
938-
if (wr->is_subclass == 1) {
938+
if (_PyWeakref_IsSubclassRef(wr) == 1) {
939939
continue;
940940
}
941941

Python/pylifecycle.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1376,6 +1376,12 @@ init_interp_main(PyThreadState *tstate)
13761376
return _PyStatus_ERR("failed to set builtin dict watcher");
13771377
}
13781378

1379+
if (is_main_interp) {
1380+
if (_PyWeakref_InitSubclassSentinel(interp) < 0) {
1381+
return _PyStatus_ERR("failed to create subclasses weakref sentinel");
1382+
}
1383+
}
1384+
13791385
assert(!_PyErr_Occurred(tstate));
13801386

13811387
return _PyStatus_OK();

Python/pystate.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -760,6 +760,8 @@ interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate)
760760

761761
Py_CLEAR(interp->audit_hooks);
762762

763+
Py_CLEAR(_Py_INTERP_SINGLETON(interp, subclasses_weakref_sentinel));
764+
763765
// At this time, all the threads should be cleared so we don't need atomic
764766
// operations for instrumentation_version or eval_breaker.
765767
interp->ceval.instrumentation_version = 0;

0 commit comments

Comments
 (0)