Skip to content

Commit 4ac4f8b

Browse files
committed
Make PyDictOrValues thread safe
1 parent 8612230 commit 4ac4f8b

File tree

1 file changed

+24
-13
lines changed

1 file changed

+24
-13
lines changed

Objects/dictobject.c

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -113,17 +113,18 @@ As a consequence of this, split keys have a maximum size of 16.
113113
#define PyDict_MINSIZE 8
114114

115115
#include "Python.h"
116-
#include "pycore_bitutils.h" // _Py_bit_length
117-
#include "pycore_call.h" // _PyObject_CallNoArgs()
118-
#include "pycore_ceval.h" // _PyEval_GetBuiltin()
119-
#include "pycore_code.h" // stats
120-
#include "pycore_dict.h" // export _PyDict_SizeOf()
121-
#include "pycore_gc.h" // _PyObject_GC_IS_TRACKED()
122-
#include "pycore_object.h" // _PyObject_GC_TRACK(), _PyDebugAllocatorStats()
123-
#include "pycore_pyerrors.h" // _PyErr_GetRaisedException()
124-
#include "pycore_pystate.h" // _PyThreadState_GET()
125-
#include "pycore_setobject.h" // _PySet_NextEntry()
126-
#include "stringlib/eq.h" // unicode_eq()
116+
#include "pycore_bitutils.h" // _Py_bit_length
117+
#include "pycore_call.h" // _PyObject_CallNoArgs()
118+
#include "pycore_ceval.h" // _PyEval_GetBuiltin()
119+
#include "pycore_code.h" // stats
120+
#include "pycore_critical_section.h" // Py_BEGIN_CRITICAL_SECTION, Py_END_CRITICAL_SECTION
121+
#include "pycore_dict.h" // export _PyDict_SizeOf()
122+
#include "pycore_gc.h" // _PyObject_GC_IS_TRACKED()
123+
#include "pycore_object.h" // _PyObject_GC_TRACK(), _PyDebugAllocatorStats()
124+
#include "pycore_pyerrors.h" // _PyErr_GetRaisedException()
125+
#include "pycore_pystate.h" // _PyThreadState_GET()
126+
#include "pycore_setobject.h" // _PySet_NextEntry()
127+
#include "stringlib/eq.h" // unicode_eq()
127128

128129
#include <stdbool.h>
129130

@@ -5540,6 +5541,9 @@ make_dict_from_instance_attributes(PyInterpreterState *interp,
55405541
track += _PyObject_GC_MAY_BE_TRACKED(val);
55415542
}
55425543
}
5544+
// free-threading TODO: We'll need to mark the values to be freed via qsbr
5545+
// so that a resize doesn't free them immediately while another thread
5546+
// still has a reference to the old values.
55435547
PyObject *res = new_dict(interp, keys, values, used, 0);
55445548
if (track && res) {
55455549
_PyObject_GC_TRACK(res);
@@ -5572,19 +5576,26 @@ _PyObject_MakeInstanceAttributesFromDict(PyObject *obj, PyDictOrValues *dorv)
55725576
return false;
55735577
}
55745578
assert(_PyType_HasFeature(Py_TYPE(obj), Py_TPFLAGS_HEAPTYPE));
5579+
bool success = true;
5580+
Py_BEGIN_CRITICAL_SECTION(dict);
55755581
if (dict->ma_keys != CACHED_KEYS(Py_TYPE(obj)) || Py_REFCNT(dict) != 1) {
5576-
return false;
5582+
success = false;
5583+
goto exit;
55775584
}
55785585
assert(dict->ma_values);
55795586
// We have an opportunity to do something *really* cool: dematerialize it!
55805587
_PyDictKeys_DecRef(dict->ma_keys);
55815588
_PyDictOrValues_SetValues(dorv, dict->ma_values);
55825589
OBJECT_STAT_INC(dict_dematerialized);
5590+
// Lock free readers will need to contend with NULL values here, but
5591+
// because we locked the dict anyone else will be safe.
55835592
// Don't try this at home, kids:
55845593
dict->ma_keys = NULL;
55855594
dict->ma_values = NULL;
55865595
Py_DECREF(dict);
5587-
return true;
5596+
exit:
5597+
Py_END_CRITICAL_SECTION();
5598+
return success;
55885599
}
55895600

55905601
int

0 commit comments

Comments
 (0)