8000 Protect wr_object with a separate mutex · python/cpython@d493e7d · GitHub
[go: up one dir, main page]

Skip to content

Commit d493e7d

Browse files
committed
Protect wr_object with a separate mutex
1 parent 817a5a3 commit d493e7d

File tree

8 files changed

+307
-121
lines changed

8 files changed

+307
-121
lines changed

Include/cpython/object.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,13 @@ PyAPI_FUNC(void) _PyTrash_end(PyThreadState *tstate);
465465
/* Python 3.10 private API, invoked by the Py_TRASHCAN_BEGIN(). */
466466
PyAPI_FUNC(int) _PyTrash_cond(PyObject *op, destructor dealloc);
467467

468+
#ifdef Py_GIL_DISABLED
469+
470+
/* Private API, used by weakrefs in free-threaded builds */
471+
PyAPI_FUNC(int) _PyTrash_contains(PyObject *op);
472+
473+
#endif
474+
468475
#define Py_TRASHCAN_BEGIN_CONDITION(op, cond) \
469476
do { \
470477
PyThreadState *_tstate = NULL; \

Include/cpython/weakrefobject.h

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

5-
#ifdef Py_GIL_DISABLED
6-
struct _PyOnceFlagRC;
7-
#endif
8-
95
/* PyWeakReference is the base struct for the Python ReferenceType, ProxyType,
106
* and CallableProxyType.
117
*/
@@ -27,10 +23,8 @@ struct _PyWeakReference {
2723
Py_hash_t hash;
2824

2925
#ifdef Py_GIL_DISABLED
30-
/* Used in free-threaded builds to ensure that a weakref is only cleared
31-
* once.
32-
*/
33-
struct _PyOnceFlagRC *clear_once;
26+
/* Used in free-threaded builds to protect wr_object and wr_callback. */
27+
struct _PyWeakRefClearState *clear_state;
3428
#endif
3529

3630
/* If wr_object is weakly referenced, wr_object has a doubly-linked NULL-

Include/internal/pycore_lock.h

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -217,17 +217,6 @@ _PyOnceFlag_CallOnce(_PyOnceFlag *flag, _Py_once_fn_t *fn, void *arg)
217217
return _PyOnceFlag_CallOnceSlow(flag, fn, arg);
218218
}
219219

220-
// A refcounted flag
221-
typedef struct _PyOnceFlagRC {
222-
_PyOnceFlag flag;
223-
224-
Py_ssize_t refcount;
225-
} _PyOnceFlagRC;
226-
227-
PyAPI_FUNC(_PyOnceFlagRC *) _PyOnceFlagRC_New(void);
228-
PyAPI_FUNC(void) _PyOnceFlagRC_Incref(_PyOnceFlagRC *flag);
229-
PyAPI_FUNC(void) _PyOnceFlagRC_Decref(_PyOnceFlagRC *flag);
230-
231220
// A readers-writer (RW) lock. The lock supports multiple concurrent readers or
232221
// a single writer. The lock is write-preferring: if a writer is waiting while
233222
// the lock is read-locked then, new readers will be blocked. This avoids

Include/internal/pycore_weakref.h

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,18 @@ extern "C" {
88
# error "this header requires Py_BUILD_CORE define"
99
#endif
1010

11-
#include "pycore_critical_section.h" // Py_BEGIN_CRITICAL_SECTION()
11+
#include "pycore_lock.h"
1212
#include "pycore_object.h" // _Py_REF_IS_MERGED()
1313

14+
#ifdef Py_GIL_DISABLED
15+
typedef struct _PyWeakRefClearState {
16+
PyMutex mutex;
17+
_PyOnceFlag once;
18+
int cleared;
19+
Py_ssize_t refcount;
20+
} _PyWeakRefClearState;
21+
#endif
22+
1423
static inline int _is_dead(PyObject *obj)
1524
{
1625
// Explanation for the Py_REFCNT() check: when a weakref's target is part
@@ -27,12 +36,23 @@ static inline int _is_dead(PyObject *obj)
2736
#endif
2837
}
2938

39+
#if defined(Py_GIL_DISABLED)
40+
# define LOCK_WR_OBJECT(weakref) PyMutex_Lock(&(weakref)->clear_state->mutex);
41+
# define UNLOCK_WR_OBJECT(weakref) PyMutex_Unlock(&(weakref)->clear_state->mutex);
42+
#else
43+
# define LOCK_WR_OBJECT(weakref)
44+
# define UNLOCK_WR_OBJECT(weakref)
45+
#endif
46+
47+
// NB: In free-threaded builds nothing between the LOCK/UNLOCK calls below can
48+
// suspend.
49+
3050
static inline PyObject* _PyWeakref_GET_REF(PyObject *ref_obj)
3151
{
3252
assert(PyWeakref_Check(ref_obj));
3353
PyObject *ret = NULL;
34-
Py_BEGIN_CRITICAL_SECTION(ref_obj);
3554
PyWeakReference *ref = _Py_CAST(PyWeakReference*, ref_obj);
55+
LOCK_WR_OBJECT(ref)
3656
PyObject *obj = ref->wr_object;
3757

3858
if (obj == Py_None) {
@@ -48,16 +68,16 @@ static inline PyObject* _PyWeakref_GET_REF(PyObject *ref_obj)
4868
#endif
4969
ret = Py_NewRef(obj);
5070
end:
51-
Py_END_CRITICAL_SECTION();
71+
UNLOCK_WR_OBJECT(ref);
5272
return ret;
5373
}
5474

5575
static inline int _PyWeakref_IS_DEAD(PyObject *ref_obj)
5676
{
5777
assert(PyWeakref_Check(ref_obj));
5878
int ret = 0;
59-
Py_BEGIN_CRITICAL_SECTION(ref_obj);
6079
PyWeakReference *ref = _Py_CAST(PyWeakReference*, ref_obj);
80+
LOCK_WR_OBJECT(ref);
6181
PyObject *obj = ref->wr_object;
6282
if (obj == Py_None) {
6383
// clear_weakref() was called
@@ -67,7 +87,7 @@ static inline int _PyWeakref_IS_DEAD(PyObject *ref_obj)
6787
// See _PyWeakref_GET_REF() for the rationale of this test
6888
ret = _is_dead(obj);
6989
}
70-
Py_END_CRITICAL_SECTION();
90+
UNLOCK_WR_OBJECT(ref);
7191
return ret;
7292
}
7393

Objects/object.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2835,6 +2835,25 @@ _PyTrash_cond(PyObject *op, destructor dealloc)
28352835
return Py_TYPE(op)->tp_dealloc == dealloc;
28362836
}
28372837

2838+
#ifdef Py_GIL_DISABLED
2839+
2840+
int
2841+
_PyTrash_contains(PyObject *op)
2842+
{
2843+
PyThreadState *tstate = _PyThreadState_GET();
2844+
struct _py_trashcan *trash = _PyTrash_get_state(tstate);
2845+
PyObject *cur = trash->delete_later;
2846+
while (cur) {
2847+
if (cur == op) {
2848+
return 1;
2849+
}
2850+
cur = (PyObject *) cur->ob_tid;
2851+
}
2852+
return 0;
2853+
}
2854+
2855+
#endif
2856+
28382857

28392858
void _Py_NO_RETURN
28402859
_PyObject_AssertFailed(PyObject *obj, const char *expr, const char *msg,

Objects/typeobject.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "pycore_abstract.h" // _PySequence_IterSearch()
55
#include "pycore_call.h" // _PyObject_VectorcallTstate()
66
#include "pycore_code.h" // CO_FAST_FREE
7+
#include "pycore_critical_section.h"
78
#include "pycore_dict.h" // _PyDict_KeysSize()
89
#include "pycore_frame.h" // _PyInterpreterFrame
910
#include "pycore_lock.h" // _PySeqLock_*

0 commit comments

Comments
 (0)
0