Skip to content

Commit 4b97934

Browse files
committed
Refactor type_ready_publish().
Call type_ready() and add a 'fix_slots' option. This simplifies code in the callers and makes it harder to use this wrong. Revert changes to TYPE_IS_REVEALED() macro, they are not needed. Make fixup_slot_dispatchers() void again, it cannot fail.
1 parent 8b79946 commit 4b97934

1 file changed

Lines changed: 48 additions & 73 deletions

File tree

Objects/typeobject.c

Lines changed: 48 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -81,23 +81,13 @@ types_world_is_stopped(void)
8181
#endif
8282

8383
// Checks that the type has not yet been revealed (exposed) to other
84-
// threads. The _Py_TYPE_REVEALED_FLAG flag is set by type_ready_publish(),
85-
// just before the type is added to the subclasses of its bases. That is the
86-
// point where other threads can find it. The flag only exists on 64-bit
87-
// platforms (we only have ob_flags there) and in debug builds.
88-
//
89-
// The flag is set with the type lock held and it is never cleared. A thread
90-
// that can see the type has either found it through the subclasses of its
91-
// bases, which requires the type lock, or the world is stopped. So, plain
92-
// loads and stores are enough here.
93-
#if defined(Py_DEBUG) && SIZEOF_VOID_P > 4
94-
#define TYPE_IS_REVEALED(tp) \
95-
((((PyObject *)(tp))->ob_flags & _Py_TYPE_REVEALED_FLAG) != 0)
96-
#define TYPE_SET_REVEALED(tp) \
97-
((void)(((PyObject *)(tp))->ob_flags |= _Py_TYPE_REVEALED_FLAG))
84+
// threads. The _Py_TYPE_REVEALED_FLAG flag is set by type_new() and
85+
// PyType_FromMetaclass() to indicate that a newly initialized type might be
86+
// revealed. We only have ob_flags on 64-bit platforms.
87+
#if SIZEOF_VOID_P > 4
88+
#define TYPE_IS_REVEALED(tp) ((((PyObject *)(tp))->ob_flags & _Py_TYPE_REVEALED_FLAG) != 0)
9889
#else
9990
#define TYPE_IS_REVEALED(tp) 0
100-
#define TYPE_SET_REVEALED(tp) ((void)0)
10191
#endif
10292

10393
#ifdef Py_DEBUG
@@ -183,7 +173,6 @@ type_lock_allow_release(void)
183173
#define END_TYPE_DICT_LOCK()
184174
#define ASSERT_TYPE_LOCK_HELD()
185175
#define TYPE_IS_REVEALED(tp) 0
186-
#define TYPE_SET_REVEALED(tp) ((void)0)
187176
#define ASSERT_WORLD_STOPPED_OR_NEW_TYPE(tp)
188177
#define ASSERT_NEW_TYPE_OR_LOCKED(tp)
189178
#define types_world_is_stopped() 1
@@ -3902,9 +3891,9 @@ static void object_dealloc(PyObject *);
39023891
static PyObject *object_new(PyTypeObject *, PyObject *, PyObject *);
39033892
static int object_init(PyObject *, PyObject *, PyObject *);
39043893
static int update_slot(PyTypeObject *, PyObject *, slot_update_t *update);
3905-
static int fixup_slot_dispatchers(PyTypeObject *);
3894+
static void fixup_slot_dispatchers(PyTypeObject *);
39063895
static int type_ready(PyTypeObject *, int, int);
3907-
static int type_ready_publish(PyTypeObject *);
3896+
static int type_ready_publish(PyTypeObject *, int);
39083897
static int type_new_set_names(PyTypeObject *);
39093898
static int type_new_init_subclass(PyTypeObject *, PyObject *);
39103899
static bool has_slotdef(PyObject *);
@@ -4910,27 +4899,8 @@ type_new_impl(type_new_ctx *ctx)
49104899
goto error;
49114900
}
49124901

4913-
/* Initialize the rest, put the proper slots in place and only then
4914-
publish the type as a subclass of its bases. Since the type is not
4915-
reachable by other threads before it is published, the slots can be
4916-
updated without stopping the world.
4917-
4918-
All of it is done with the type lock held. Otherwise a thread that
4919-
assigns to a special method of a base could look for the subclasses to
4920-
update after we have looked up the special methods in the bases but
4921-
before the type is published, and the type would be left with a stale
4922-
slot. */
4923-
int res;
4924-
BEGIN_TYPE_LOCK();
4925-
res = type_ready(type, 1, 0);
4926-
if (res == 0) {
4927-
res = fixup_slot_dispatchers(type);
4928-
}
4929-
if (res == 0) {
4930-
res = type_ready_publish(type);
4931-
}
4932-
END_TYPE_LOCK();
4933-
if (res < 0) {
4902+
/* Initialize the rest */
4903+
if (type_ready_publish(type, 1) < 0) {
49344904
goto error;
49354905
}
49364906

@@ -5694,16 +5664,7 @@ type_from_slots_or_spec(
56945664
* After this call we should generally only touch up what's
56955665
* accessible to Python code, like __dict__.
56965666
*/
5697-
5698-
BEGIN_TYPE_LOCK();
5699-
r = type_ready(type, 1, 0);
5700-
if (r == 0) {
5701-
/* The type is only revealed to other threads once it is fully
5702-
initialized. */
5703-
r = type_ready_publish(type);
5704-
}
5705-
END_TYPE_LOCK();
5706-
if (r < 0) {
5667+
if (type_ready_publish(type, 0) < 0) {
57075668
goto finally;
57085669
}
57095670

@@ -9506,28 +9467,43 @@ type_ready(PyTypeObject *type, int initial, int add_subclasses)
95069467
}
95079468

95089469
static int
9509-
type_ready_publish(PyTypeObject *type)
9470+
type_ready_publish(PyTypeObject *type, int fix_slots)
95109471
{
9511-
ASSERT_TYPE_LOCK_HELD();
9512-
assert(!(type->tp_flags & Py_TPFLAGS_READY));
9513-
assert(!is_readying(type));
9514-
9515-
/* Set the ready flag before revealing the type since type_add_flags()
9516-
may only be used on types that are not yet revealed. */
9517-
type_add_flags(type, Py_TPFLAGS_READY);
9518-
9519-
/* Mark the type as revealed while still holding the type lock. Threads
9520-
can only find the type through the subclasses of its bases, which is
9521-
done below with the lock held. So, they cannot see the type before
9522-
the flag is set. */
9523-
TYPE_SET_REVEALED(type);
9472+
int res;
9473+
BEGIN_TYPE_LOCK();
9474+
res = type_ready(type, 1, 0);
9475+
if (res == 0) {
9476+
assert(!(type->tp_flags & Py_TPFLAGS_READY));
9477+
assert(!is_readying(type));
9478+
9479+
if (fix_slots) {
9480+
// Put the proper slots in place and only then publish the type as
9481+
// a subclass of its bases. Since the type is not reachable by
9482+
// other threads before it is published, the slots can be updated
9483+
// without stopping the world. This step is skipped for
9484+
// type_from_slots_or_spec().
9485+
fixup_slot_dispatchers(type);
9486+
}
9487+
9488+
// Set the ready flag before revealing the type since type_add_flags()
9489+
// may only be used on types that are not yet revealed.
9490+
type_add_flags(type, Py_TPFLAGS_READY);
9491+
9492+
#if defined(Py_GIL_DISABLED) && defined(Py_DEBUG) && SIZEOF_VOID_P > 4
9493+
// Mark the type as revealed while still holding the type lock.
9494+
// Threads can only find the type through the subclasses of its bases,
9495+
// which is done below with the lock held. So, they cannot see the
9496+
// type before the flag is set.
9497+
((PyObject*)type)->ob_flags |= _Py_TYPE_REVEALED_FLAG;
9498+
#endif
95249499

9525-
if (type_ready_add_subclasses(type) < 0) {
9526-
return -1;
9500+
res = type_ready_add_subclasses(type);
9501+
if (res == 0) {
9502+
assert(_PyType_CheckConsistency(type));
9503+
}
95279504
}
9528-
9529-
assert(_PyType_CheckConsistency(type));
9530-
return 0;
9505+
END_TYPE_LOCK();
9506+
return res;
95319507
}
95329508

95339509
int
@@ -12052,18 +12028,17 @@ update_slot(PyTypeObject *type, PyObject *name, slot_update_t *queued_updates)
1205212028
definition time, based upon which operations the class overrides in its
1205312029
dict. The type must not be revealed to other threads yet, so that the
1205412030
slots can be updated directly rather than with the world stopped. */
12055-
static int
12031+
static void
1205612032
fixup_slot_dispatchers(PyTypeObject *type)
1205712033
{
1205812034
ASSERT_TYPE_LOCK_HELD();
1205912035
ASSERT_WORLD_STOPPED_OR_NEW_TYPE(type);
1206012036
assert(!PyErr_Occurred());
1206112037
for (pytype_slotdef *p = slotdefs; p->name; ) {
12062-
if (update_one_slot(type, p, &p, NULL) < 0) {
12063-
return -1;
12064-
}
12038+
int rv = update_one_slot(type, p, &p, NULL);
12039+
// always returns 0 if queued_updates == NULL
12040+
assert (rv == 0);
1206512041
}
12066-
return 0;
1206712042
}
1206812043

1206912044
#ifdef Py_GIL_DISABLED

0 commit comments

Comments
 (0)