PyObject *previous_executor;
+ uint64_t dict_global_version;
};
#ifdef Py_DEBUG
#define DICT_WATCHER_AND_MODIFICATION_MASK ((1 << (DICT_MAX_WATCHERS + DICT_WATCHED_MUTATION_BITS)) - 1)
#ifdef Py_GIL_DISABLED
-#define DICT_NEXT_VERSION(INTERP) \
- (_Py_atomic_add_uint64(&(INTERP)->dict_state.global_version, DICT_VERSION_INCREMENT) + DICT_VERSION_INCREMENT)
+
+#define THREAD_LOCAL_DICT_VERSION_COUNT 256
+#define THREAD_LOCAL_DICT_VERSION_BATCH THREAD_LOCAL_DICT_VERSION_COUNT * DICT_VERSION_INCREMENT
+
+static inline uint64_t
+dict_next_version(PyInterpreterState *interp)
+{
+ PyThreadState *tstate = PyThreadState_GET();
+ uint64_t cur_progress = (tstate->dict_global_version &
+ (THREAD_LOCAL_DICT_VERSION_BATCH - 1));
+ if (cur_progress == 0) {
+ uint64_t next = _Py_atomic_add_uint64(&interp->dict_state.global_version,
+ THREAD_LOCAL_DICT_VERSION_BATCH);
+ tstate->dict_global_version = next;
+ }
+ return tstate->dict_global_version += DICT_VERSION_INCREMENT;
+}
+
+#define DICT_NEXT_VERSION(INTERP) dict_next_version(INTERP)
#else
#define DICT_NEXT_VERSION(INTERP) \
from threading import Thread
from unittest import TestCase
+from _testcapi import dict_version
+
from test.support import threading_helper
for ref in thread_list:
self.assertIsNone(ref())
+ def test_dict_version(self):
+ THREAD_COUNT = 10
+ DICT_COUNT = 10000
+ lists = []
+ writers = []
+
+ def writer_func(thread_list):
+ for i in range(DICT_COUNT):
+ thread_list.append(dict_version({}))
+
+ for x in range(THREAD_COUNT):
+ thread_list = []
+ lists.append(thread_list)
+ writer = Thread(target=partial(writer_func, thread_list))
+ writers.append(writer)
+
+ for writer in writers:
+ writer.start()
+
+ for writer in writers:
+ writer.join()
+
+ total_len = 0
+ values = set()
+ for thread_list in lists:
+ for v in thread_list:
+ if v in values:
+ print('dup', v, (v/4096)%256)
+ values.add(v)
+ total_len += len(thread_list)
+ versions = set(dict_version for thread_list in lists for dict_version in thread_list)
+ self.assertEqual(len(versions), THREAD_COUNT*DICT_COUNT)
+
+
if __name__ == "__main__":
unittest.main()
#include "parts.h"
#include "util.h"
-
static PyObject *
dict_containsstring(PyObject *self, PyObject *args)
{
RETURN_INT(PyDict_PopString(dict, key, NULL));
}
+static PyObject *
+dict_version(PyObject *self, PyObject *dict)
+{
+ if (!PyDict_Check(dict)) {
+ PyErr_SetString(PyExc_TypeError, "expected dict");
+ return NULL;
+ }
+_Py_COMP_DIAG_PUSH
+_Py_COMP_DIAG_IGNORE_DEPR_DECLS
+ return PyLong_FromUnsignedLongLong(((PyDictObject *)dict)->ma_version_tag);
+_Py_COMP_DIAG_POP
+}
static PyMethodDef test_methods[] = {
{"dict_containsstring", dict_containsstring, METH_VARARGS},
{"dict_pop_null", dict_pop_null, METH_VARARGS},
{"dict_popstring", dict_popstring, METH_VARARGS},
{"dict_popstring_null", dict_popstring_null, METH_VARARGS},
+ {"dict_version", dict_version, METH_O},
{NULL},
};
tstate->datastack_limit = NULL;
tstate->what_event = -1;
tstate->previous_executor = NULL;
+ tstate->dict_global_version = 0;
tstate->delete_later = NULL;