Skip to content

Commit 65e40f4

Browse files
committed
Fixes based on review feedback.
* In type_set_bases(), always use stop-the-world. * Fixes for mro_invoke(). We need to do different things depending on if there is a custom mro() method and where we are called from. If from type_ready(), need the TYPE_LOCK mutex. If from assignment to __bases__, need stop-the-world. * Remove some places TYPE_LOCK is acquired when it no longer needs to be. * Add comments to some _lock_held() functions to note that they might be called with the world stopped, rather than holding the lock. * Remove update_slot_world_stopped() by inlining it.
1 parent 895a86a commit 65e40f4

File tree

1 file changed

+54
-52
lines changed

1 file changed

+54
-52
lines changed

Objects/typeobject.c

+54-52
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ types_mutex_unlock(void)
156156
}
157157

158158
#define ASSERT_TYPE_LOCK_HELD() \
159-
{if (!types_world_is_stopped()) assert(types_mutex_is_owned());}
159+
assert(types_world_is_stopped() || types_mutex_is_owned())
160160

161161
#else
162162

@@ -432,7 +432,8 @@ type_set_flags(PyTypeObject *tp, unsigned long flags)
432432
if (tp->tp_flags & Py_TPFLAGS_READY) {
433433
// It's possible the type object has been exposed to other threads
434434
// if it's been marked ready. In that case, the type lock should be
435-
// held when flags are modified.
435+
// held when flags are modified (or the stop-the-world mechanism should
436+
// be active).
436437
ASSERT_TYPE_LOCK_HELD();
437438
}
438439
ASSERT_NEW_OR_STOPPED(tp);
@@ -1923,18 +1924,9 @@ type_set_bases(PyObject *tp, PyObject *new_bases, void *Py_UNUSED(closure))
19231924
{
19241925
PyTypeObject *type = PyTypeObject_CAST(tp);
19251926
int res;
1926-
bool need_stop = !types_world_is_stopped();
1927-
if (need_stop) {
1928-
// This function can be re-entered. We want to avoid trying to stop
1929-
// the world a second time if that happens. Example call chain:
1930-
// mro_invoke() -> call_unbound_noarg() -> type_setattro() ->
1931-
// type_set_bases()
1932-
types_stop_world();
1933-
}
1927+
types_stop_world();
19341928
res = type_set_bases_unlocked(type, new_bases);
1935-
if (need_stop) {
1936-
types_start_world();
1937-
}
1929+
types_start_world();
19381930
return res;
19391931
}
19401932

@@ -3487,16 +3479,48 @@ mro_invoke(PyTypeObject *type)
34873479
const int custom = !Py_IS_TYPE(type, &PyType_Type);
34883480

34893481
if (custom) {
3482+
// Custom mro() method on metaclass. This is potentially
3483+
// re-entrant. We are called either from type_ready(), in which case
3484+
// the TYPE_LOCK mutex is held, or from type_set_bases() (assignment to
3485+
// __bases__), in which case we need to stop-the-world in order to avoid
3486+
// data races.
3487+
bool stopped = types_world_is_stopped();
3488+
if (stopped) {
3489+
// Called for type_set_bases(), re-assigment of __bases__
3490+
types_start_world();
3491+
}
3492+
else {
3493+
// Called from type_ready()
3494+
ASSERT_TYPE_LOCK_HELD();
3495+
types_mutex_unlock();
3496+
}
34903497
mro_result = call_method_noarg((PyObject *)type, &_Py_ID(mro));
3498+
if (mro_result != NULL) {
3499+
new_mro = PySequence_Tuple(mro_result);
3500+
Py_DECREF(mro_result);
3501+
}
3502+
else {
3503+
new_mro = NULL;
3504+
}
3505+
if (stopped) {
3506+
types_stop_world();
3507+
}
3508+
else {
3509+
types_mutex_lock();
3510+
}
34913511
}
34923512
else {
3513+
// In this case, the mro() method on the type object is being used and
3514+
// we know that these calls are not re-entrant.
34933515
mro_result = mro_implementation(type);
3516+
if (mro_result != NULL) {
3517+
new_mro = PySequence_Tuple(mro_result);
3518+
Py_DECREF(mro_result);
3519+
}
3520+
else {
3521+
new_mro = NULL;
3522+
}
34943523
}
3495-
if (mro_result == NULL)
3496-
return NULL;
3497-
3498-
new_mro = PySequence_Tuple(mro_result);
3499-
Py_DECREF(mro_result);
35003524
if (new_mro == NULL) {
35013525
return NULL;
35023526
}
@@ -3549,9 +3573,7 @@ mro_internal_unlocked(PyTypeObject *type, int initial, PyObject **p_old_mro)
35493573
Don't let old_mro be GC'ed and its address be reused for
35503574
another object, like (suddenly!) a new tp_mro. */
35513575
old_mro = Py_XNewRef(lookup_tp_mro(type));
3552-
types_mutex_unlock();
35533576
new_mro = mro_invoke(type); /* might cause reentrance */
3554-
types_mutex_lock();
35553577
reent = (lookup_tp_mro(type) != old_mro);
35563578
Py_XDECREF(old_mro);
35573579
if (new_mro == NULL) {
@@ -5523,7 +5545,6 @@ PyType_GetModuleByDef(PyTypeObject *type, PyModuleDef *def)
55235545
}
55245546

55255547
PyObject *res = NULL;
5526-
types_mutex_lock();
55275548

55285549
PyObject *mro = lookup_tp_mro(type);
55295550
// The type must be ready
@@ -5550,7 +5571,6 @@ PyType_GetModuleByDef(PyTypeObject *type, PyModuleDef *def)
55505571
break;
55515572
}
55525573
}
5553-
types_mutex_unlock();
55545574

55555575
if (res == NULL) {
55565576
PyErr_Format(
@@ -5806,12 +5826,14 @@ _PyTypes_AfterFork(void)
58065826
#endif
58075827
}
58085828

5809-
// Try to assign a new type version tag, return it if
5810-
// successful. Return 0 if no version was assigned.
5829+
// Try to assign a new type version tag, return it if successful. Return 0
5830+
// if no version was assigned. Note that this function can be called while
5831+
// holding the TYPE_LOCK mutex or when the stop-the-world mechanism is active.
58115832
static unsigned int
58125833
type_assign_version_lock_held(PyTypeObject *type)
58135834
{
58145835
ASSERT_TYPE_LOCK_HELD();
5836+
assert(!types_world_is_stopped());
58155837
PyInterpreterState *interp = _PyInterpreterState_GET();
58165838
if (assign_version_tag(interp, type)) {
58175839
return type->tp_version_tag;
@@ -5916,6 +5938,8 @@ type_lookup_ex(PyTypeObject *type, PyObject *name, _PyStackRef *out,
59165938
return assigned_version;
59175939
}
59185940

5941+
// Note that this function can be called while holding the TYPE_LOCK mutex or
5942+
// when the stop-the-world mechanism is active.
59195943
static unsigned int
59205944
type_lookup_lock_held(PyTypeObject *type, PyObject *name, _PyStackRef *out)
59215945
{
@@ -6019,15 +6043,13 @@ _PyType_CacheGetItemForSpecialization(PyHeapTypeObject *ht, PyObject *descriptor
60196043
void
60206044
_PyType_SetFlags(PyTypeObject *self, unsigned long mask, unsigned long flags)
60216045
{
6022-
types_mutex_lock();
60236046
unsigned long new_flags = (self->tp_flags & ~mask) | flags;
60246047
if (new_flags != self->tp_flags) {
60256048
types_stop_world();
60266049
// can't use new_flags here since they could be out-of-date
60276050
self->tp_flags = (self->tp_flags & ~mask) | flags;
60286051
types_start_world();
60296052
}
6030-
types_mutex_unlock();
60316053
}
60326054

60336055
int
@@ -6190,18 +6212,6 @@ _Py_type_getattro(PyObject *tp, PyObject *name)
61906212
return _Py_type_getattro_impl(type, name, NULL);
61916213
}
61926214

6193-
#ifdef Py_GIL_DISABLED
6194-
static int
6195-
update_slot_world_stopped(PyTypeObject *type, PyObject *name)
6196-
{
6197-
int ret;
6198-
types_stop_world();
6199-
ret = update_slot(type, name);
6200-
types_start_world();
6201-
return ret;
6202-
}
6203-
#endif
6204-
62056215
// Called by type_setattro(). Updates both the type dict and
62066216
// any type slots that correspond to the modified entry.
62076217
static int
@@ -6241,7 +6251,11 @@ type_update_dict(PyTypeObject *type, PyDictObject *dict, PyObject *name,
62416251
#ifdef Py_GIL_DISABLED
62426252
if (is_dunder_name(name) && has_slotdef(name)) {
62436253
// update is potentially changing one or more slots
6244-
return update_slot_world_stopped(type, name);
6254+
int ret;
6255+
types_stop_world();
6256+
ret = update_slot(type, name);
6257+
types_start_world();
6258+
return ret;
62456259
}
62466260
#else
62476261
if (is_dunder_name(name)) {
@@ -6319,7 +6333,7 @@ type_setattro(PyObject *self, PyObject *name, PyObject *value)
63196333
}
63206334
}
63216335

6322-
// FIXME: can this deadlock? if so, how to avoid
6336+
// FIXME: I'm not totaly sure this is safe from a deadlock. If so, how to avoid?
63236337
types_mutex_lock();
63246338
Py_BEGIN_CRITICAL_SECTION(dict);
63256339
res = type_update_dict(type, (PyDictObject *)dict, name, value, &old_value);
@@ -7254,14 +7268,10 @@ object_set_class(PyObject *self, PyObject *value, void *closure)
72547268
return -1;
72557269
}
72567270

7257-
#ifdef Py_GIL_DISABLED
72587271
types_stop_world();
7259-
#endif
72607272
PyTypeObject *oldto = Py_TYPE(self);
72617273
int res = object_set_class_world_stopped(self, newto);
7262-
#ifdef Py_GIL_DISABLED
72637274
types_start_world();
7264-
#endif
72657275
if (res == 0) {
72667276
if (oldto->tp_flags & Py_TPFLAGS_HEAPTYPE) {
72677277
Py_DECREF(oldto);
@@ -10736,9 +10746,7 @@ releasebuffer_maybe_call_super(PyObject *self, Py_buffer *buffer)
1073610746
{
1073710747
releasebufferproc base_releasebuffer;
1073810748

10739-
types_mutex_lock();
1074010749
base_releasebuffer = releasebuffer_maybe_call_super_unlocked(self, buffer);
10741-
types_mutex_unlock();
1074210750

1074310751
if (base_releasebuffer != NULL) {
1074410752
base_releasebuffer(self, buffer);
@@ -11806,14 +11814,8 @@ _super_lookup_descr(PyTypeObject *su_type, PyTypeObject *su_obj_type, PyObject *
1180611814
PyObject *mro, *res;
1180711815
Py_ssize_t i, n;
1180811816

11809-
types_mutex_lock();
1181011817
mro = lookup_tp_mro(su_obj_type);
11811-
/* keep a strong reference to mro because su_obj_type->tp_mro can be
11812-
replaced during PyDict_GetItemRef(dict, name, &res) and because
11813-
another thread can modify it after we end the critical section
11814-
below */
1181511818
Py_XINCREF(mro);
11816-
types_mutex_unlock();
1181711819

1181811820
if (mro == NULL)
1181911821
return NULL;

0 commit comments

Comments
 (0)