Skip to content

Commit 8b8a87c

Browse files
committed
Avoid looping if mro() assigns a slot.
mro_internal() now calls mro() once and stores the result with TYPES_PUBLISH_GEN_NONE. The only check inside the stop-the-world is the tp_mro identity test that main already had.
1 parent c7ac4e7 commit 8b8a87c

2 files changed

Lines changed: 51 additions & 33 deletions

File tree

‎Lib/test/test_free_threading/test_type.py‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,29 @@ def __eq__(self, other):
540540
self.assertIsNone(base_ref())
541541
self.assertIsNone(sub_ref())
542542

543+
def test_reentrant_mro_always_publishes(self):
544+
# A custom mro() that updates a slot on every call must not make
545+
# the __bases__ assignment retry forever.
546+
class Other:
547+
pass
548+
549+
class Meta(type):
550+
def mro(cls):
551+
Other.__repr__ = lambda self: "x"
552+
return type.mro(cls)
553+
554+
class A:
555+
pass
556+
557+
class B:
558+
pass
559+
560+
class C(A, metaclass=Meta):
561+
pass
562+
563+
C.__bases__ = (B,)
564+
self.assertEqual(C.__mro__, (C, B, object))
565+
543566
def test_concurrent_bases_assignment(self):
544567
# Several threads reassign __bases__ of the same class, and of its
545568
# base, while others stop the world. The validated bases and the

‎Objects/typeobject.c‎

Lines changed: 28 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3700,43 +3700,38 @@ mro_internal(PyTypeObject *type, int initial, PyObject **p_old_mro)
37003700
Don't let old_mro be GC'ed and its address be reused for
37013701
another object, like (suddenly!) a new tp_mro. */
37023702
old_mro = Py_XNewRef(lookup_tp_mro(type));
3703-
for (;;) {
3704-
uint64_t gen = types_publish_gen();
3705-
new_mro = mro_invoke(type); /* might cause reentrance */
3706-
if (new_mro == NULL) {
3707-
Py_XDECREF(old_mro);
3708-
return -1;
3709-
}
3710-
int res;
3711-
if (initial) {
3712-
// The type has not been revealed to other threads yet, so only
3713-
// same-thread reentrance can have replaced tp_mro.
3714-
if (lookup_tp_mro(type) == old_mro) {
3715-
set_tp_mro(type, new_mro);
3716-
res = 1;
3717-
}
3718-
else {
3719-
res = 0;
3720-
}
3703+
new_mro = mro_invoke(type); /* might cause reentrance */
3704+
if (new_mro == NULL) {
3705+
Py_XDECREF(old_mro);
3706+
return -1;
3707+
}
3708+
int res;
3709+
if (initial) {
3710+
// The type has not been revealed to other threads yet, so only
3711+
// same-thread reentrance can have replaced tp_mro.
3712+
if (lookup_tp_mro(type) == old_mro) {
3713+
set_tp_mro(type, new_mro);
3714+
res = 1;
37213715
}
37223716
else {
3723-
// Store with the world stopped. TYPE_LOCK may have been
3724-
// released while mro_invoke() ran or while waiting to stop the
3725-
// world, so check for reentrance and for concurrent mutation of
3726-
// the hierarchy inside the stop-the-world.
3727-
res = try_set_tp_mro(type, old_mro, new_mro, gen);
3728-
}
3729-
if (res == 1) {
3730-
break;
3717+
res = 0;
37313718
}
3719+
}
3720+
else {
3721+
// Store with the world stopped. TYPE_LOCK may have been released
3722+
// while mro_invoke() ran, so check for reentrance inside the
3723+
// stop-the-world. The publish generation is deliberately not
3724+
// checked: mro() can itself update types (assign a slot, for
3725+
// example) and the result is stored anyway, as on the default
3726+
// build.
3727+
res = try_set_tp_mro(type, old_mro, new_mro, TYPES_PUBLISH_GEN_NONE);
3728+
assert(res != -1);
3729+
}
3730+
if (res == 0) {
3731+
/* reentrance: tp_mro was replaced by someone else */
37323732
Py_DECREF(new_mro);
3733-
if (res == 0) {
3734-
/* reentrance: tp_mro was replaced by someone else */
3735-
Py_XDECREF(old_mro);
3736-
return 0;
3737-
}
3738-
/* stale: another thread changed the hierarchy, compute again */
3739-
assert(res == -1);
3733+
Py_XDECREF(old_mro);
3734+
return 0;
37403735
}
37413736
/* Drop our temporary reference. The reference previously owned by
37423737
the type is now owned by this function and transferred below. */

0 commit comments

Comments
 (0)