8000 gh-112075: Make instance attributes stored in inline "dict" thread sa… · python/cpython@8b541c0 · GitHub
[go: up one dir, main page]

Skip to content

Commit 8b541c0

Browse files
authored
gh-112075: Make instance attributes stored in inline "dict" thread safe (#114742)
Make instance attributes stored in inline "dict" thread safe on free-threaded builds
1 parent 1446024 commit 8b541c0

File tree

13 files changed

+419
-142
lines changed

13 files changed

+419
-142
lines changed

Include/cpython/object.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,7 @@ do { \
493493
PyAPI_FUNC(void *) PyObject_GetItemData(PyObject *obj);
494494

495495
PyAPI_FUNC(int) PyObject_VisitManagedDict(PyObject *obj, visitproc visit, void *arg);
496+
PyAPI_FUNC(void) _PyObject_SetManagedDict(PyObject *obj, PyObject *new_dict);
496497
PyAPI_FUNC(void) PyObject_ClearManagedDict(PyObject *obj);
497498

498499
#define TYPE_MAX_WATCHERS 8

Include/internal/pycore_dict.h

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
#ifndef Py_INTERNAL_DICT_H
32
#define Py_INTERNAL_DICT_H
43
#ifdef __cplusplus
@@ -9,9 +8,10 @@ extern "C" {
98
# error "this header requires Py_BUILD_CORE define"
109
#endif
1110

12-
#include "pycore_freelist.h" // _PyFreeListState
13-
#include "pycore_identifier.h" // _Py_Identifier
14-
#include "pycore_object.h" // PyManagedDictPointer
11+
#include "pycore_freelist.h" // _PyFreeListState
12+
#include "pycore_identifier.h" // _Py_Identifier
13+
#include "pycore_object.h" // PyManagedDictPointer
14+
#include "pycore_pyatomic_ft_wrappers.h" // FT_ATOMIC_LOAD_SSIZE_ACQUIRE
1515

1616
// Unsafe flavor of PyDict_GetItemWithError(): no error checking
1717
extern PyObject* _PyDict_GetItemWithError(PyObject *dp, PyObject *key);
@@ -249,7 +249,7 @@ _PyDict_NotifyEvent(PyInterpreterState *interp,
249249
return DICT_NEXT_VERSION(interp) | (mp->ma_version_tag & DICT_WATCHER_AND_MODIFICATION_MASK);
250250
}
251251

252-
extern PyDictObject *_PyObject_MakeDictFromInstanceAttributes(PyObject *obj);
252+
extern PyDictObject *_PyObject_MaterializeManagedDict(PyObject *obj);
253253

254254
PyAPI_FUNC(PyObject *)_PyDict_FromItems(
255255
PyObject *const *keys, Py_ssize_t keys_offset,
@@ -277,19 +277,16 @@ _PyDictValues_AddToInsertionOrder(PyDictValues *values, Py_ssize_t ix)
277277
static inline size_t
278278
shared_keys_usable_size(PyDictKeysObject *keys)
279279
{
280-
#ifdef Py_GIL_DISABLED
281280
// dk_usable will decrease for each instance that is created and each
282281
// value that is added. dk_nentries will increase for each value that
283282
// is added. We want to always return the right value or larger.
284283
// We therefore increase dk_nentries first and we decrease dk_usable
285284
// second, and conversely here we read dk_usable first and dk_entries
286285
// second (to avoid the case where we read entries before the increment
287286
// and read usable after the decrement)
288-
return (size_t)(_Py_atomic_load_ssize_acquire(&keys->dk_usable) +
289-
_Py_atomic_load_ssize_acquire(&keys->dk_nentries));
290-
#else
291-
return (size_t)keys->dk_nentries + (size_t)keys->dk_usable;
292-
#endif
287+
Py_ssize_t dk_usable = FT_ATOMIC_LOAD_SSIZE_ACQUIRE(keys->dk_usable);
288+
Py_ssize_t dk_nentries = FT_ATOMIC_LOAD_SSIZE_ACQUIRE(keys->dk_nentries);
289+
return dk_nentries + dk_usable;
293290
}
294291

295292
static inline size_t

Include/internal/pycore_object.h

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ extern "C" {
1212
#include "pycore_gc.h" // _PyObject_GC_IS_TRACKED()
1313
#include "pycore_emscripten_trampoline.h" // _PyCFunction_TrampolineCall()
1414
#include "pycore_interp.h" // PyInterpreterState.gc
15+
#include "pycore_pyatomic_ft_wrappers.h" // FT_ATOMIC_STORE_PTR_RELAXED
1516
#include "pycore_pystate.h" // _PyInterpreterState_GET()
1617

1718
/* Check if an object is consistent. For example, ensure that the reference
@@ -659,10 +660,10 @@ extern PyObject* _PyType_GetDocFromInternalDoc(const char *, const char *);
659660
extern PyObject* _PyType_GetTextSignatureFromInternalDoc(const char *, const char *, int);
660661

661662
void _PyObject_InitInlineValues(PyObject *obj, PyTypeObject *tp);
662-
extern int _PyObject_StoreInstanceAttribute(PyObject *obj, PyDictValues *values,
663-
PyObject *name, PyObject *value);
664-
PyObject * _PyObject_GetInstanceAttribute(PyObject *obj, PyDictValues *values,
665-
PyObject *name);
663+
extern int _PyObject_StoreInstanceAttribute(PyObject *obj,
664+
PyObject *name, PyObject *value);
665+
extern bool _PyObject_TryGetInstanceAttribute(PyObject *obj, PyObject *name,
666+
PyObject **attr);
666667

667668
#ifdef Py_GIL_DISABLED
668669
# define MANAGED_DICT_OFFSET (((Py_ssize_t)sizeof(PyObject *))*-1)
@@ -683,6 +684,13 @@ _PyObject_ManagedDictPointer(PyObject *obj)
683684
return (PyManagedDictPointer *)((char *)obj + MANAGED_DICT_OFFSET);
684685
}
685686

687+
static inline PyDictObject *
688+
_PyObject_GetManagedDict(PyObject *obj)
689+
{
690+
PyManagedDictPointer *dorv = _PyObject_ManagedDictPointer(obj);
691+
return (PyDictObject *)FT_ATOMIC_LOAD_PTR_RELAXED(dorv->dict);
692+
}
693+
686694
static inline PyDictValues *
687695
_PyObject_InlineValues(PyObject *obj)
688696
{

Include/internal/pycore_pyatomic_ft_wrappers.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ extern "C" {
2121

2222
#ifdef Py_GIL_DISABLED
2323
#define FT_ATOMIC_LOAD_PTR(value) _Py_atomic_load_ptr(&value)
24+
#define FT_ATOMIC_STORE_PTR(value, new_value) _Py_atomic_store_ptr(&value, new_value)
2425
#define FT_ATOMIC_LOAD_SSIZE(value) _Py_atomic_load_ssize(&value)
26+
#define FT_ATOMIC_LOAD_SSIZE_ACQUIRE(value) \
27+
_Py_atomic_load_ssize_acquire(&value)
2528
#define FT_ATOMIC_LOAD_SSIZE_RELAXED(value) \
2629
_Py_atomic_load_ssize_relaxed(&value)
2730
#define FT_ATOMIC_STORE_PTR(value, new_value) \
@@ -30,6 +33,12 @@ extern "C" {
3033
_Py_atomic_load_ptr_acquire(&value)
3134
#define FT_ATOMIC_LOAD_UINTPTR_ACQUIRE(value) \
3235
_Py_atomic_load_uintptr_acquire(&value)
36+
#define FT_ATOMIC_LOAD_PTR_RELAXED(value) \
37+
_Py_atomic_load_ptr_relaxed(&value)
38+
#define FT_ATOMIC_LOAD_UINT8(value) \
39+
_Py_atomic_load_uint8(&value)
40+
#define FT_ATOMIC_STORE_UINT8(value, new_value) \
41+
_Py_atomic_store_uint8(&value, new_value)
3342
#define FT_ATOMIC_STORE_PTR_RELAXED(value, new_value) \
3443
_Py_atomic_store_ptr_relaxed(&value, new_value)
3544
#define FT_ATOMIC_STORE_PTR_RELEASE(value, new_value) \
@@ -43,11 +52,16 @@ extern "C" {
4352

4453
#else
4554
#define FT_ATOMIC_LOAD_PTR(value) value
55+
#define FT_ATOMIC_STORE_PTR(value, new_value) value = new_value
4656
#define FT_ATOMIC_LOAD_SSIZE(value) value
57+
#define FT_ATOMIC_LOAD_SSIZE_ACQUIRE(value) value
4758
#define FT_ATOMIC_LOAD_SSIZE_RELAXED(value) value
4859
#define FT_ATOMIC_STORE_PTR(value, new_value) value = new_value
4960
#define FT_ATOMIC_LOAD_PTR_ACQUIRE(value) value
5061
#define FT_ATOMIC_LOAD_UINTPTR_ACQUIRE(value) value
62+
#define FT_ATOMIC_LOAD_PTR_RELAXED(value) value
63+
#define FT_ATOMIC_LOAD_UINT8(value) value
64+
#define FT_ATOMIC_STORE_UINT8(value, new_value) value = new_value
5165
#define FT_ATOMIC_STORE_PTR_RELAXED(value, new_value) value = new_value
5266
#define FT_ATOMIC_STORE_PTR_RELEASE(value, new_value) value = new_value
5367
#define FT_ATOMIC_STORE_UINTPTR_RELEASE(value, new_value) value = new_value

Lib/test/test_class.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -873,6 +873,15 @@ def __init__(self):
873873
obj.foo = None # Aborted here
874874
self.assertEqual(obj.__dict__, {"foo":None})
875875

876+
def test_store_attr_deleted_dict(self):
877+
class Foo:
878+
pass
879+
880+
f = Foo()
881+
del f.__dict__
882+
f.a = 3
883+
self.assertEqual(f.a, 3)
884+
876885

877886
if __name__ == '__main__':
878887
unittest.main()

0 commit comments

Comments
 (0)
0