Skip to content

Commit cfc11bc

Browse files
committed
typeobject: thread safety
- Make method_cache thread-local - Atomics in assign_version_tag - Use relaxed atomics for tp_flags
1 parent d1b5ed1 commit cfc11bc

File tree

6 files changed

+82
-52
lines changed

6 files changed

+82
-52
lines changed

‎Include/internal/pycore_interp.h‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ typedef struct PyThreadStateImpl {
7070
struct brc_state brc;
7171

7272
struct qsbr *qsbr;
73+
74+
struct type_cache type_cache;
7375
} PyThreadStateImpl;
7476

7577

‎Include/internal/pycore_object.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ extern int _PyTraceMalloc_NewReference(PyObject *op);
8484
// Fast inlined version of PyType_HasFeature()
8585
static inline int
8686
_PyType_HasFeature(PyTypeObject *type, unsigned long feature) {
87-
return ((type->tp_flags & feature) != 0);
87+
return PyType_HasFeature(type, feature);
8888
}
8989

9090
extern void _PyType_InitCache(PyInterpreterState *interp);

‎Include/internal/pycore_runtime.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@ typedef struct pyruntimestate {
165165
struct _Py_dict_runtime_state dict_state;
166166
struct _py_func_runtime_state func_state;
167167

168+
_PyMutex mutex;
168169
struct {
169170
/* Used to set PyTypeObject.tp_version_tag */
170171
// bpo-42745: next_version_tag remains shared by all interpreters

‎Include/internal/pycore_typeobject.h‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@ _PyStaticType_GET_WEAKREFS_LISTPTR(static_builtin_state *state)
6363
}
6464

6565
struct types_state {
66+
#ifndef Py_NOGIL
6667
struct type_cache type_cache;
68+
#endif
6769
size_t num_builtins_initialized;
6870
static_builtin_state builtins[_Py_MAX_STATIC_BUILTIN_TYPES];
6971
};

‎Include/object.h‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1083,6 +1083,8 @@ PyType_HasFeature(PyTypeObject *type, unsigned long feature)
10831083
#ifdef Py_LIMITED_API
10841084
// PyTypeObject is opaque in the limited C API
10851085
flags = PyType_GetFlags(type);
1086+
#elif defined(_Py_THREAD_SANITIZER)
1087+
flags = _Py_atomic_load_ulong_relaxed(&type->tp_flags);
10861088
#else
10871089
flags = type->tp_flags;
10881090
#endif

‎Objects/typeobject.c‎

Lines changed: 74 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,17 @@ class object "PyObject *" "&PyBaseObject_Type"
3737
(((unsigned int)(version) ^ (unsigned int)(name_hash)) \
3838
& ((1 << MCACHE_SIZE_EXP) - 1))
3939

40-
#define MCACHE_HASH_METHOD(type, name) \
41-
MCACHE_HASH((type)->tp_version_tag, ((Py_ssize_t)(name)) >> 3)
40+
static inline unsigned int
41+
MCACHE_HASH_METHOD(PyTypeObject *type, PyObject *name)
42+
{
43+
unsigned int version = _Py_atomic_load_uint32_relaxed(&type->tp_version_tag);
44+
return MCACHE_HASH(version, ((Py_ssize_t)(name)) >> 3);
45+
}
46+
4247
#define MCACHE_CACHEABLE_NAME(name) \
4348
PyUnicode_CheckExact(name) && \
4449
PyUnicode_IS_READY(name) && \
50+
PyUnicode_CHECK_INTERNED(name) && \
4551
(PyUnicode_GET_LENGTH(name) <= MCACHE_MAX_ATTR_SIZE)
4652

4753
#define next_version_tag (_PyRuntime.types.next_version_tag)
@@ -289,66 +295,42 @@ _PyType_GetTextSignatureFromInternalDoc(const char *name, const char *internal_d
289295
static struct type_cache*
290296
get_type_cache(void)
291297
{
292-
PyInterpreterState *interp = _PyInterpreterState_GET();
293-
return &interp->types.type_cache;
294-
}
295-
296-
297-
static void
298-
type_cache_clear(struct type_cache *cache, PyObject *value)
299-
{
300-
for (Py_ssize_t i = 0; i < (1 << MCACHE_SIZE_EXP); i++) {
301-
struct type_cache_entry *entry = &cache->hashtable[i];
302-
entry->version = 0;
303-
Py_XSETREF(entry->name, _Py_XNewRef(value));
304-
entry->value = NULL;
305-
}
298+
PyThreadState *tstate = _PyThreadState_GET();
299+
#ifdef Py_NOGIL
300+
return &((PyThreadStateImpl *)tstate)->type_cache;
301+
#else
302+
return &tstate->interp->types.type_cache;
303+
#endif
306304
}
307305

308306

309307
void
310308
_PyType_InitCache(PyInterpreterState *interp)
311309
{
312-
struct type_cache *cache = &interp->types.type_cache;
313-
for (Py_ssize_t i = 0; i < (1 << MCACHE_SIZE_EXP); i++) {
314-
struct type_cache_entry *entry = &cache->hashtable[i];
315-
assert(entry->name == NULL);
316-
317-
entry->version = 0;
318-
// Set to None so _PyType_Lookup() can use Py_SETREF(),
319-
// rather than using slower Py_XSETREF().
320-
entry->name = Py_None;
321-
entry->value = NULL;
322-
}
323310
}
324311

325312

326313
static unsigned int
327-
_PyType_ClearCache(PyInterpreterState *interp)
314+
_PyType_ClearCache(PyThreadStateImpl *tstate)
328315
{
329-
struct type_cache *cache = &interp->types.type_cache;
330-
// Set to None, rather than NULL, so _PyType_Lookup() can
331-
// use Py_SETREF() rather than using slower Py_XSETREF().
332-
type_cache_clear(cache, Py_None);
333-
316+
memset(&tstate->type_cache, 0, sizeof(tstate->type_cache));
334317
return next_version_tag - 1;
335318
}
336319

337320

338321
unsigned int
339322
PyType_ClearCache(void)
340323
{
341-
PyInterpreterState *interp = _PyInterpreterState_GET();
342-
return _PyType_ClearCache(interp);
324+
// TODO: clear all threads type caches or merge type caches
325+
PyThreadState *tstate = _PyThreadState_GET();
326+
return _PyType_ClearCache((PyThreadStateImpl *)tstate);
343327
}
344328

345329

346330
void
347331
_PyTypes_Fini(PyInterpreterState *interp)
348332
{
349-
struct type_cache *cache = &interp->types.type_cache;
350-
type_cache_clear(cache, NULL);
351-
333+
_PyType_ClearCache(&interp->_initial_thread);
352334
assert(interp->types.num_builtins_initialized == 0);
353335
// All the static builtin types should have been finalized already.
354336
for (size_t i = 0; i < _Py_MAX_STATIC_BUILTIN_TYPES; i++) {
@@ -436,8 +418,8 @@ PyType_Unwatch(int watcher_id, PyObject* obj)
436418
return 0;
437419
}
438420

439-
void
440-
PyType_Modified(PyTypeObject *type)
421+
static void
422+
_PyType_ModifiedEx(PyTypeObject *type)
441423
{
442424
/* Invalidate any cached data for the specified type and all
443425
subclasses. This function is called after the base
@@ -469,7 +451,7 @@ PyType_Modified(PyTypeObject *type)
469451
if (subclass == NULL) {
470452
continue;
471453
}
472-
PyType_Modified(subclass);
454+
_PyType_ModifiedEx(subclass);
473455
Py_DECREF(subclass);
474456
}
475457
}
@@ -496,6 +478,14 @@ PyType_Modified(PyTypeObject *type)
496478
type->tp_version_tag = 0; /* 0 is not a valid version tag */
497479
}
498480

481+
void
482+
PyType_Modified(PyTypeObject *type)
483+
{
484+
_PyMutex_lock(&_PyRuntime.mutex);
485+
_PyType_ModifiedEx(type);
486+
_PyMutex_unlock(&_PyRuntime.mutex);
487+
}
488+
499489
static void
500490
type_mro_modified(PyTypeObject *type, PyObject *bases) {
501491
/*
@@ -549,6 +539,23 @@ type_mro_modified(PyTypeObject *type, PyObject *bases) {
549539
type->tp_version_tag = 0; /* 0 is not a valid version tag */
550540
}
551541

542+
static unsigned int
543+
advance_version_tag(void)
544+
{
545+
retry: ;
546+
unsigned int version_tag = _Py_atomic_load_uint_relaxed(&next_version_tag);
547+
if (version_tag == 0) {
548+
/* We have run out of version numbers */
549+
return 0;
550+
}
551+
if (!_Py_atomic_compare_exchange_uint(&next_version_tag,
552+
version_tag,
553+
version_tag + 1)) {
554+
goto retry;
555+
}
556+
return version_tag;
557+
}
558+
552559
static int
553560
assign_version_tag(PyTypeObject *type)
554561
{
@@ -564,11 +571,13 @@ assign_version_tag(PyTypeObject *type)
564571
return 0;
565572
}
566573

567-
if (next_version_tag == 0) {
574+
unsigned int version_tag = advance_version_tag();
575+
if (version_tag == 0) {
568576
/* We have run out of version numbers */
569577
return 0;
570578
}
571-
type->tp_version_tag = next_version_tag++;
579+
580+
_Py_atomic_store_uint32_relaxed(&type->tp_version_tag, version_tag);
572581
assert (type->tp_version_tag != 0);
573582

574583
PyObject *bases = type->tp_bases;
@@ -578,7 +587,8 @@ assign_version_tag(PyTypeObject *type)
578587
if (!assign_version_tag(_PyType_CAST(b)))
579588
return 0;
580589
}
581-
type->tp_flags |= Py_TPFLAGS_VALID_VERSION_TAG;
590+
_Py_atomic_store_ulong_relaxed(
591+
&type->tp_flags, type->tp_flags | Py_TPFLAGS_VALID_VERSION_TAG);
582592
return 1;
583593
}
584594

@@ -4198,9 +4208,10 @@ _PyType_Lookup(PyTypeObject *type, PyObject *name)
41984208
entry->version = type->tp_version_tag;
41994209
entry->value = res; /* borrowed */
42004210
assert(_PyASCIIObject_CAST(name)->hash != -1);
4201-
OBJECT_STAT_INC_COND(type_cache_collisions, entry->name != Py_None && entry->name != name);
4211+
OBJECT_STAT_INC_COND(type_cache_collisions, entry->name != NULL && entry->name != name);
42024212
assert(_PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG));
4203-
Py_SETREF(entry->name, Py_NewRef(name));
4213+
assert(_PyObject_IS_IMMORTAL(name));
4214+
entry->name = name;
42044215
}
42054216
return res;
42064217
}
@@ -4374,7 +4385,9 @@ type_setattro(PyTypeObject *type, PyObject *name, PyObject *value)
43744385
PyType_Modified(type);
43754386

43764387
if (is_dunder_name(name)) {
4388+
_PyMutex_lock(&_PyRuntime.mutex);
43774389
res = update_slot(type, name);
4390+
_PyMutex_unlock(&_PyRuntime.mutex);
43784391
}
43794392
assert(_PyType_CheckConsistency(type));
43804393
}
@@ -6992,6 +7005,15 @@ _PyStaticType_InitBuiltin(PyTypeObject *self)
69927005
return res;
69937006
}
69947007

7008+
static PyObject *
7009+
init_once(void *dst, PyObject *obj)
7010+
{
7011+
if (!_Py_atomic_compare_exchange_ptr(dst, NULL, obj)) {
7012+
Py_DECREF(obj);
7013+
obj = _Py_atomic_load_ptr(dst);
7014+
}
7015+
return obj;
7016+
}
69957017

69967018
static PyObject *
69977019
init_subclasses(PyTypeObject *self)
@@ -7002,11 +7024,9 @@ init_subclasses(PyTypeObject *self)
70027024
}
70037025
if (self->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN) {
70047026
static_builtin_state *state = _PyStaticType_GetState(self);
7005-
state->tp_subclasses = subclasses;
7006-
return subclasses;
7027+
return init_once(&state->tp_subclasses, subclasses);
70077028
}
7008-
self->tp_subclasses = (void *)subclasses;
7009-
return subclasses;
7029+
return init_once(&self->tp_subclasses, subclasses);
70107030
}
70117031

70127032
static void
@@ -9074,13 +9094,16 @@ update_all_slots(PyTypeObject* type)
90749094
{
90759095
pytype_slotdef *p;
90769096

9097+
_PyMutex_lock(&_PyRuntime.mutex);
9098+
90779099
/* Clear the VALID_VERSION flag of 'type' and all its subclasses. */
9078-
PyType_Modified(type);
9100+
_PyType_ModifiedEx(type);
90799101

90809102
for (p = slotdefs; p->name; p++) {
90819103
/* update_slot returns int but can't actually fail */
90829104
update_slot(type, p->name_strobj);
90839105
}
9106+
_PyMutex_unlock(&_PyRuntime.mutex);
90849107
}
90859108

90869109

0 commit comments

Comments
 (0)