8000 Make PyDictOrValues thread safe · python/cpython@57327a2 · GitHub
[go: up one dir, main page]

Skip to content

Commit 57327a2

Browse files
committed
Make PyDictOrValues thread safe
1 parent 01dceba commit 57327a2

14 files changed

+507
-201
lines changed

Include/internal/pycore_dict.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ extern PyObject *_PyDict_FromItems(
248248
PyObject *const *keys, Py_ssize_t keys_offset,
249249
PyObject *const *values, Py_ssize_t values_offset,
250250
Py_ssize_t length);
251+
extern void _PyDict_FreeDictForDematerialization(PyDictObject *obj);
251252

252253
static inline void
253254
_PyDictValues_AddToInsertionOrder(PyDictValues *values, Py_ssize_t ix)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,7 @@ PyAPI_FUNC(void) _PyRWMutex_RUnlock(_PyRWMutex *rwmutex);
251251
PyAPI_FUNC(void) _PyRWMutex_Lock(_PyRWMutex *rwmutex);
252252
PyAPI_FUNC(void) _PyRWMutex_Unlock(_PyRWMutex *rwmutex);
253253

254+
extern void _Py_yield(void);
254255

255256
#ifdef __cplusplus
256257
}

Include/internal/pycore_object.h

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,9 @@ typedef union {
631631
char *values;
632632
} PyDictOrValues;
633633

634+
// Sentinel value to indicate when an update to PyDictOrValues is in-flight
635+
#define _PYDICTORVALUES_UPDATING 0x0000002
636+
634637
static inline PyDictOrValues *
635638
_PyObject_DictOrValuesPointer(PyObject *obj)
636639
{
@@ -651,23 +654,120 @@ _PyDictOrValues_GetValues(PyDictOrValues dorv)
651654
return (PyDictValues *)(dorv.values + 1);
652655
}
653656

657+
static inline PyDictValues *
658+
_PyDictOrValues_TryGetValues(PyDictOrValues *dorv)
659+
{
660+
#ifdef Py_GIL_DISABLED
661+
char *values;
662+
while (1) {
663+
values = _Py_atomic_load_ptr_acquire(&dorv->values);
664+
if (values != (char *)_PYDICTORVALUES_UPDATING) {
665+
if ((uintptr_t)values & 1) {
666+
return (PyDictValues *)(values + 1);
667+
}
668+
// The values have become a dict or is not yet initialized
669+
return NULL;
670+
}
671+
// There is an atomic update of the values in progress...
672+
_Py_yield();
673+
}
674+
#else
675+
if (_PyDictOrValues_IsValues(*dorv)) {
676+
return _PyDictOrValues_GetValues(*dorv);
677+
}
678+
return NULL;
679+
#endif
680+
}
681+
654682
static inline PyObject *
655683
_PyDictOrValues_GetDict(PyDictOrValues dorv)
656684
{
657685
assert(!_PyDictOrValues_IsValues(dorv));
658686
return dorv.dict;
659687
}
660688

689+
// Trys to get the dict from the PyDictOrValues and returns
690+
// a new strong reference if successful.
691+
static inline PyObject *
692+
_PyDictOrValues_TryGetDict(PyDictOrValues *dorv)
693+
{
694+
#ifdef Py_GIL_DISABLED
695+
PyObject *dict;
696+
while (1) {
697+
dict = _Py_atomic_load_ptr_acquire(&dorv->dict);
698+
if (dict != (PyObject *)_PYDICTORVALUES_UPDATING) {
699+
if ((uintptr_t)dict & 1) {
700+
// The dict has become a values
701+
return NULL;
702+
} else if (dict != NULL) {
703+
Py_INCREF(dict);
704+
if (_Py_atomic_load_ptr_acquire(&dorv->dict) == dict) {
705+
return dict;
706+
}
707+
708+
// We've lost a race (presumably with dematerialization) so
709+
// we'll try again...
710+
Py_DECREF(dict);
711+
} else {
712+
// The dict is not yet initialized...
713+
return dict;
714+
}
715+
}
716+
// There is an atomic update of the values in progress...
717+
_Py_yield();
718+
}
719+
#else
720+
if (!_PyDictOrValues_IsValues(*dorv)) {
721+
Py_XINCREF(dorv->dict);
722+
return dorv->dict;
723+
}
724+
return NULL;
725+
#endif
726+
}
727+
728+
static inline bool
729+
_PyDictOrValues_TrySetDict(PyDictOrValues *dorv_ptr, void *expected, PyObject *dict)
730+
{
731+
#ifdef Py_GIL_DISABLED
732+
if (!_Py_atomic_compare_exchange_ptr(&dorv_ptr->dict, &expected, dict)) {
733+
return false;
734+
}
735+
return true;
736+
#else
737+
dorv_ptr->dict = dict;
738+
return true;
739+
#endif
740+
}
741+
742+
743+
744+
static inline void
745+
_PyDictOrValues_FreeValues(PyDictValues *values)
746+
{
747+
// TODO: Maybe free with qsbr
748+
int prefix_size = ((uint8_t *)values)[-1];
749+
PyMem_Free(((char *)values)-prefix_size);
750+
}
751+
661752
static inline void
662753
_PyDictOrValues_SetValues(PyDictOrValues *ptr, PyDictValues *values)
663754
{
755+
#ifdef Py_GIL_DISABLED
756+
char *expected = NULL;
757+
if (!_Py_atomic_compare_exchange_ptr(&ptr->values, &expected, ((char *)values) - 1)) {
758+
_PyDictOrValues_FreeValues(values);
759+
}
760+
#else
664761
ptr->values = ((char *)values) - 1;
762+
#endif
665763
}
666764

667765
extern PyObject ** _PyObject_ComputedDictPointer(PyObject *);
668766
extern void _PyObject_FreeInstanceAttributes(PyObject *obj);
669767
extern int _PyObject_IsInstanceDictEmpty(PyObject *);
670768

769+
extern int _PyObject_SetDict(PyObject *obj, PyObject *new_dict);
770+
671771
// Export for 'math' shared extension
672772
PyAPI_FUNC(PyObject*) _PyObject_LookupSpecial(PyObject *, PyObject *);
673773

Include/internal/pycore_opcode_metadata.h

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_uop_ids.h

Lines changed: 52 additions & 52 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_uop_metadata.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,16 +114,16 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = {
114114
[_LOAD_ATTR] = HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
115115
[_GUARD_TYPE_VERSION] = HAS_DEOPT_FLAG | HAS_PASSTHROUGH_FLAG,
116116
[_CHECK_MANAGED_OBJECT_HAS_VALUES] = HAS_DEOPT_FLAG | HAS_PASSTHROUGH_FLAG,
117-
[_LOAD_ATTR_INSTANCE_VALUE] = HAS_ARG_FLAG | HAS_DEOPT_FLAG,
117+
[_LOAD_ATTR_INSTANCE_VALUE] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG,
118118
[_CHECK_ATTR_MODULE] = HAS_DEOPT_FLAG | HAS_PASSTHROUGH_FLAG,
119119
[_LOAD_ATTR_MODULE] = HAS_ARG_FLAG | HAS_DEOPT_FLAG,
120-
[_CHECK_ATTR_WITH_HINT] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG | HAS_PASSTHROUGH_FLAG,
121-
[_LOAD_ATTR_WITH_HINT] = HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG,
120+
[_CHECK_ATTR_WITH_HINT] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG,
121+
[_LOAD_ATTR_WITH_HINT] = HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_DEOPT_FLAG,
122122
[_LOAD_ATTR_SLOT] = HAS_ARG_FLAG | HAS_DEOPT_FLAG,
123123
[_CHECK_ATTR_CLASS] = HAS_DEOPT_FLAG | HAS_PASSTHROUGH_FLAG,
124124
[_LOAD_ATTR_CLASS] = HAS_ARG_FLAG,
125125
[_GUARD_DORV_VALUES] = HAS_DEOPT_FLAG | HAS_PASSTHROUGH_FLAG,
126-
[_STORE_ATTR_INSTANCE_VALUE] = HAS_ESCAPES_FLAG,
126+
[_STORE_ATTR_INSTANCE_VALUE] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG,
127127
[_STORE_ATTR_SLOT] = HAS_ESCAPES_FLAG,
128128
[_COMPARE_OP] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG,
129129
[_COMPARE_OP_FLOAT] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG,

0 commit comments

Comments
 (0)
0