10000 Handle multiple weakrefs · python/cpython@363a1a6 · GitHub
[go: up one dir, main page]

Skip to content

Commit 363a1a6

Browse files
committed
Handle multiple weakrefs
1 parent 6d00f51 commit 363a1a6

File tree

2 files changed

+256
-3
lines changed

2 files changed

+256
-3
lines changed

Include/cpython/weakrefobject.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
# error "this header file must not be included directly"
33
#endif
44

5+
#ifdef Py_GIL_DISABLED
6+
typedef struct _PyWeakRefUnlinker _PyWeakRefUnlinker;
7+
#endif
8+
59
/* PyWeakReference is the base struct for the Python ReferenceType, ProxyType,
610
* and CallableProxyType.
711
*/
@@ -22,6 +26,13 @@ struct _PyWeakReference {
2226
*/
2327
Py_hash_t hash;
2428

29+
#ifdef Py_GIL_DISABLED
30+
/* A pointer to an object that is used to coordinating concurrent attempts
31+
* at unlinking the weakref.
32+
*/
33+
_PyWeakRefUnlinker *unlinker;
34+
#endif
35+
2536
/* If wr_object is weakly referenced, wr_object has a doubly-linked NULL-
2637
* terminated list of weak references to it. These are the list pointers.
2738
* If wr_object goes away, wr_object is set to Py_None, and these pointers

Objects/weakrefobject.c

Lines changed: 245 additions & 3 deletions
F438
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,103 @@
11
#include "Python.h"
2+
#include "pycore_lock.h"
23
#include "pycore_modsupport.h" // _PyArg_NoKwnames()
34
#include "pycore_object.h" // _PyObject_GET_WEAKREFS_LISTPTR()
45
#include "pycore_pyerrors.h" // _PyErr_ChainExceptions1()
56
#include "pycore_weakref.h" // _PyWeakref_GET_REF()
67

8+
#ifdef Py_GIL_DISABLED
9+
/*
10+
* Thread-safety for free-threaded builds
11+
* ======================================
12+
*
13+
* In free-threaded builds we need to protect mutable state of:
14+
*
15+
* - The weakref
16+
* - The referenced object
17+
* - The linked-list of weakrefs
18+
*
19+
* The above may be modified concurrently when creating weakrefs, destroying
20+
* weakrefs, or destroying the referenced object. Critical sections are
21+
* used to protect the mutable state:
22+
*
23+
* - The weakref is protected by its per-object lock.
24+
* - The referenced object is protected by its per-object lock.
25+
* - The linked-list of weakrefs is protected by the referenced object's
26+
* per-object lock.
27+
* - Both locks must be held (using the two-variant form of critical sections)
28+
* if both the weakref and referenced object or linked-list need to be
29+
* modified.
30+
*
31+
*/
32+
33+
typedef enum {
34+
WR_UNLINK_NOT_STARTED,
35+
WR_UNLINK_STARTED,
36+
WR_UNLINK_DONE,
37+
} _PyWeakRefUnlinkState;
38+
39+
/*
40+
* _PyWeakRefUnlinker is used to coordinate concurrent attempts at unlinking
41+
* weakrefs.
42+
*
43+
* XXX - More docs??
44+
* XXX - Can this be a OnceFlag?
45+
*/
46+
typedef struct _PyWeakRefUnlinker {
47+
/* Holds a value from _PyWeakRefUnlinkState */
48+
int state;
49+
PyEvent done;
50+
Py_ssize_t refcount;
51+
} _PyWeakRefUnlinker;
52+
53+
static _PyWeakRefUnlinker *
54+
_PyWeakRefUnlinker_New(void)
55+
{
56+
_PyWeakRefUnlinker *self = (_PyWeakRefUnlinker *)PyMem_RawCalloc(1, sizeof(_PyWeakRefUnlinker));
57+
if (self == NULL) {
58+
PyErr_NoMemory();
59+
return NULL;
60+
}
61+
self->state = WR_UNLINK_NOT_STARTED;
62+
self->done = (PyEvent){0};
63+
self->refcount = 1;
64+
return self;
65+
}
66+
67+
static void
68+
_PyWeakRefUnlinker_Incref(_PyWeakRefUnlinker *self)
69+
{
70+
_Py_atomic_add_ssize(&self->refcount, 1);
71+
}
72+
73+
static void
74+
_PyWeakRefUnlinker_Decref(_PyWeakRefUnlinker *self)
75+
{
76+
if (_Py_atomic_add_ssize(&self->refcount, -1) == 1) {
77+
PyMem_RawFree(self);
78+
}
79+
}
80+
81+
static int
82+
_PyWeakRefUnlinker_Start(_PyWeakRefUnlinker *self)
83+
{
84+
int expected = WR_UNLINK_NOT_STARTED;
85+
return _Py_atomic_compare_exchange_int(&self->state, &expected, WR_UNLINK_STARTED);
86+
}
87+
88+
static void
89+
_PyWeakRefUnlinker_Wait(_PyWeakRefUnlinker *self)
90+
{
91+
PyEvent_Wait(&self->done);
92+
}
93+
94+
static void
95+
_PyWeakRefUnlinker_Finish(_PyWeakRefUnlinker *self)
96+
{
97+
_Py_atomic_exchange_int(&self->state, WR_UNLINK_DONE);
98+
_PyEvent_Notify(&self->done);
99+
}
100+
#endif // Py_GIL_DISABLED
7101

8102

9103
#define GET_WEAKREFS_LISTPTR(o) \
@@ -24,7 +118,7 @@ _PyWeakref_GetWeakrefCount(PyWeakReference *head)
24118

25119
static PyObject *weakref_vectorcall(PyObject *self, PyObject *const *args, size_t nargsf, PyObject *kwnames);
26120

27-
static void
121+
static int
28122
init_weakref(PyWeakReference *self, PyObject *ob, PyObject *callback)
29123
{
30124
self->hash = -1;
@@ -34,9 +128,15 @@ init_weakref(PyWeakReference *self, PyObject *ob, PyObject *callback)
34128
self->wr_callback = Py_XNewRef(callback);
35129
self->vectorcall = weakref_vectorcall;
36130
#ifdef Py_GIL_DISABLED
131+
self->unlinker = _PyWeakRefUnlinker_New();
132+
if (self->unlinker == NULL) {
133+
Py_XDECREF(self->wr_callback);
134+
return -1;
135+
}
37136
_PyObject_SetMaybeWeakref(ob);
38137
_PyObject_SetMaybeWeakref((PyObject *)self);
39138
#endif
139+
return 0;
40140
}
41141

42142
static PyWeakReference *
@@ -46,12 +146,86 @@ new_weakref(PyObject *ob, PyObject *callback)
46146

47147
result = PyObject_GC_New(PyWeakReference, &_PyWeakref_RefType);
48148
if (result) {
49-
init_weakref(result, ob, callback);
149+
if (init_weakref(result, ob, callback) < 0) {
150+
return NULL;
151+
}
50152
PyObject_GC_Track(result);
51153
}
52154
return result;
53155
}
54156

157+
#ifdef Py_GIL_DISABLED
158+
159+
static void
160+
unlink_weakref_lock_held(PyWeakReference *self)
161+
{
162+
if (self->wr_object != Py_None) {
163+
PyWeakReference **list = GET_WEAKREFS_LISTPTR(self->wr_object);
164+
if (*list == self)
165+
/* If 'self' is the end of the list (and thus self->wr_next == NULL)
166+
then the weakref list itself (and thus the value of *list) will
167+
end up being set to NULL. */
168+
*list = self->wr_next;
169+
self->wr_object = Py_None;
170+
if (self->wr_prev != NULL)
171+
self->wr_prev->wr_next = self->wr_next;
172+
if (self->wr_next != NULL)
173+
self->wr_next->wr_prev = self->wr_prev;
174+
self->wr_prev = NULL;
175+
self->wr_next = NULL;
176+
}
177+
}
178+
179+
static void
180+
gc_unlink_weakref(PyWeakReference *self)
181+
{
182+
unlink_weakref_lock_held(self);
183+
_PyWeakRefUnlinker_Finish(self->unlinker);
184+
}
185+
186+
static void
187+
unlink_weakref(PyWeakReference *self)
188+
{
189+
Py_BEGIN_CRITICAL_SECTION2(self->wr_object, self);
190+
unlink_weakref_lock_held(self);
191+
Py_END_CRITICAL_SECTION2();
192+
}
193+
194+
static void
195+
clear_weakref(PyWeakReference *self)
196+
{
197+
_PyWeakRefUnlinker *unlinker = self->unlinker;
198+
_PyWeakRefUnlinker_Incref(unlinker);
199+
200+
if (_PyWeakRefUnlinker_Start(unlinker)) {
201+
unlink_weakref(self);
202+
Py_BEGIN_CRITICAL_SECTION(self);
203+
if (self->wr_callback != NULL) {
204+
PyObject *callback = self->wr_callback;
205+
self->wr_callback = NULL;
206+
Py_DECREF(callback);
207+
}
208+
Py_END_CRITICAL_SECTION();
209+
_PyWeakRefUnlinker_Finish(unlinker);
210+
}
211+
else {
212+
_PyWeakRefUnlinker_Wait(unlinker);
213+
}
214+
215+
_PyWeakRefUnlinker_Decref(unlinker);
216+
}
217+
218+
static void
219+
gc_clear_weakref(PyWeakReference *self)
220+
{
221+
gc_unlink_weakref(self);
222+
if (self->wr_callback != NULL) {
223+
Py_DECREF(self->wr_callback);
224+
self->wr_callback = NULL;
225+
}
226+
}
227+
228+
#else
55229

56230
/* This function clears the passed-in reference and removes it from the
57231
* list of weak references for the referent. This is the only code that
@@ -85,6 +259,8 @@ clear_weakref(PyWeakReference *self)
85259
}
86260
}
87261

262+
#endif // Py_GIL_DISABLED
263+
88264
/* Cyclic gc uses this to *just* clear the passed-in reference, leaving
89265
* the callback intact and uncalled. It must be possible to call self's
90266
* tp_dealloc() after calling this, so self has to be left in a sane enough
@@ -106,7 +282,11 @@ _PyWeakref_ClearRef(PyWeakReference *self)
106282
/* Preserve and restore the callback around clear_weakref. */
107283
callback = self->wr_callback;
108284
self->wr_callback = NULL;
285+
#ifdef Py_GIL_DISABLED
286+
gc_unlink_weakref(self);
287+
#else
109288
clear_weakref(self);
289+
#endif
110290
self->wr_callback = callback;
111291
}
112292

@@ -115,6 +295,9 @@ weakref_dealloc(PyObject *self)
115295
{
116296
PyObject_GC_UnTrack(self);
117297
clear_weakref((PyWeakReference *) self);
298+
#ifdef Py_GIL_DISABLED
299+
_PyWeakRefUnlinker_Decref(((PyWeakReference*)self)->unlinker);
300+
#endif
118301
Py_TYPE(self)->tp_free(self);
119302
}
120303

@@ -130,7 +313,11 @@ gc_traverse(PyWeakReference *self, visitproc visit, void *arg)
130313
static int
131314
gc_clear(PyWeakReference *self)
132315
{
316+
#ifdef Py_GIL_DISABLED
317+
gc_clear_weakref(self);
318+
#else
133319
clear_weakref(self);
320+
#endif
134321
return 0;
135322
}
136323

@@ -324,7 +511,9 @@ new_weakref_lock_held(PyTypeObject *type, PyObject *ob, PyObject *callback)
324511
them. */
325512
PyWeakReference *self = (PyWeakReference *) (type->tp_alloc(type, 0));
326513
if (self != NULL) {
327-
init_weakref(self, ob, callback);
514+
if (init_weakref(self, ob, callback) < 0) {
515+
return NULL;
516+
}
328517
if (callback == NULL && type == &_PyWeakref_RefType) {
329518
insert_head(self, list);
330519
}
@@ -976,6 +1165,7 @@ handle_callback(PyWeakReference *ref, PyObject *callback)
9761165
Py_DECREF(cbresult);
9771166
}
9781167

1168+
9791169
/* This function is called by the tp_dealloc handler to clear weak references.
9801170
*
9811171
* This iterates through the weak references for 'object' and calls callbacks
@@ -995,6 +1185,57 @@ PyObject_ClearWeakRefs(PyObject *object)
9951185
return;
9961186
}
9971187
list = GET_WEAKREFS_LISTPTR(object);
1188+
#ifdef Py_GIL_DISABLED
1189+
/* Protect the linked-list and the head pointer in object */
1190+
Py_BEGIN_CRITICAL_SECTION(object);
1191+
1192+
/* Remove the callback-less basic and proxy references, which always appear
1193+
at the head of the list. There may be two of each - one live and one in
1194+
the process of being destroyed.
1195+
*/
1196+
for (;;) {
1197+
if (*list != NULL && (*list)->wr_callback == NULL) {
1198+
clear_weakref(*list);
1199+
}
1200+
else {
1201+
break;
1202+
}
1203+
}
1204+
1205+
/* Deal with non-canonical (subtypes or refs with callbacks) references.
1206+
At this point no new weakrefs to this object can be created, so we
1207+
should be safe to iterate until the list is empty.
1208+
*/
1209+
PyObject *exc = PyErr_GetRaisedException();
1210+
while (*list != NULL) {
1211+
PyWeakReference *current = *list;
1212+
_PyWeakRefUnlinker *unlinker = current->unlinker;
1213+
_PyWeakRefUnlinker_Incref(unlinker);
1214+
if (_PyWeakRefUnlinker_Start(unlinker)) {
1215+
PyObject *callback;
1216+
Py_BEGIN_CRITICAL_SECTION(current);
1217+
callback = current->wr_callback;
1218+
current->wr_callback = NULL;
1219+
Py_END_CRITICAL_SECTION();
1220+
unlink_weakref(current);
1221+
_PyWeakRefUnlinker_Finish(unlinker);
1222+
if (callback != NULL) {
1223+
if (_Py_TryIncref((PyObject **) &current, (PyObject *) current)) {
1224+
handle_callback(current, callback);
1225+
Py_DECREF(current);
1226+
}
1227+
Py_DECREF(callback);
1228+
}
1229+
} else {
1230+
_PyWeakRefUnlinker_Wait(unlinker);
1231+
}
1232+
_PyWeakRefUnlinker_Decref(unlinker);
1233+
}
1234+
assert(!PyErr_Occurred());
1235+
PyErr_SetRaisedException(exc);
1236+
1237+
Py_END_CRITICAL_SECTION();
1238+
#else
9981239
/* Remove the callback-less basic and proxy references */
9991240
if (*list != NULL && (*list)->wr_callback == NULL) {
10001241
clear_weakref(*list);
@@ -1056,6 +1297,7 @@ PyObject_ClearWeakRefs(PyObject *object)
10561297
assert(!PyErr_Occurred());
10571298
PyErr_SetRaisedException(exc);
10581299
}
1300+
#endif // Py_GIL_DISABLED
10591301
}
10601302

10611303
/* This function is called by _PyStaticType_Dealloc() to clear weak references.

0 commit comments

Comments
 (0)
0