Skip to content

Commit f447ce4

Browse files
committed
For update_all_slots(), do updates more safely.
To avoid deadlocks while the world is stopped, we need to avoid calling APIs like _PyObject_HashFast() and _PyDict_GetItemRef_KnownHash(). Collect the slot updates to be done and then apply them all at once. This reduces the amount of code running in the stop-the-world condition.
1 parent 3094372 commit f447ce4

File tree

1 file changed

+226
-31
lines changed

1 file changed

+226
-31
lines changed

Objects/typeobject.c

Lines changed: 226 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1687,7 +1687,7 @@ static int add_subclass(PyTypeObject*, PyTypeObject*);
16871687
static int add_all_subclasses(PyTypeObject *type, PyObject *bases);
16881688
static void remove_subclass(PyTypeObject *, PyTypeObject *);
16891689
static void remove_all_subclasses(PyTypeObject *type, PyObject *bases);
1690-
static void update_all_slots(PyTypeObject *);
1690+
static int update_all_slots(PyTypeObject *);
16911691

16921692
typedef int (*update_callback)(PyTypeObject *, void *);
16931693
static int update_subclasses(PyTypeObject *type, PyObject *attr_name,
@@ -1862,10 +1862,9 @@ type_set_bases_unlocked(PyTypeObject *type, PyObject *new_bases, PyTypeObject *b
18621862
add to all new_bases */
18631863
remove_all_subclasses(type, old_bases);
18641864
res = add_all_subclasses(type, new_bases);
1865-
types_stop_world();
1866-
update_all_slots(type);
1867-
types_start_world();
1868-
ASSERT_TYPE_LOCK_HELD();
1865+
if (update_all_slots(type) < 0) {
1866+
goto bail;
1867+
}
18691868
}
18701869
else {
18711870
res = 0;
@@ -3690,10 +3689,127 @@ solid_base(PyTypeObject *type)
36903689
}
36913690
}
36923691

3692+
#ifdef Py_GIL_DISABLED
3693+
3694+
// The structures and functions below are used in the free-threaded build
3695+
// to safely make updates to type slots, when __bases__ is re-assigned. Since
3696+
// the slots are read without atomic operations and without locking, we can
3697+
// only safely update them while the world is stopped. However, with the
3698+
// world stopped, we are very limited on which APIs can be safely used. For
3699+
// example, calling _PyObject_HashFast() or _PyDict_GetItemRef_KnownHash() are
3700+
// not safe and can potentially cause deadlocks. Hashing can be re-entrant
3701+
// and _PyDict_GetItemRef_KnownHash can acquire a lock if the dictionary is
3702+
// not owned by the current thread, to mark it shared on reading.
3703+
//
3704+
// We do the slot updates in two steps. First, with TYPE_LOCK held, we lookup
3705+
// the descriptor for each slot, for each subclass. We build a queue of
3706+
// updates to perform but don't actually update the type structures. After we
3707+
// are finished the lookups, we stop-the-world and apply all of the updates.
3708+
// The apply_slot_updates() code is simple and easy to confirm that it is
3709+
// safe.
3710+
3711+
typedef struct {
3712+
void **slot_ptr;
3713+
void *slot_value;
3714+
} slot_update_item_t;
3715+
3716+
// The number of slot updates performed is based on the number of changed
3717+
// slots and the number of subclasses. It's possible there are many updates
3718+
// required if there are many subclasses (potentially an unbounded amount).
3719+
// Usually the number of slot updates is small, most often zero or one. When
3720+
// running the unit tests, we don't exceed 20. The chunk size is set to
3721+
// handle the common case with a single chunk and to not require too many
3722+
// chunk allocations if there are many subclasses.
3723+
#define SLOT_UPDATE_CHUNK_SIZE 30
3724+
3725+
typedef struct _slot_update {
3726+
struct _slot_update *prev;
3727+
Py_ssize_t n;
3728+
slot_update_item_t updates[SLOT_UPDATE_CHUNK_SIZE];
3729+
} slot_update_chunk_t;
3730+
3731+
// a queue of updates to be performed
3732+
typedef struct {
3733+
slot_update_chunk_t *head;
3734+
} slot_update_t;
3735+
3736+
static slot_update_chunk_t *
3737+
slot_update_new_chunk(void)
3738+
{
3739+
slot_update_chunk_t *chunk = PyMem_Malloc(sizeof(slot_update_chunk_t));
3740+
if (chunk == NULL) {
3741+
PyErr_NoMemory();
3742+
return NULL;
3743+
}
3744+
chunk->prev = NULL;
3745+
chunk->n = 0;
3746+
return chunk;
3747+
}
3748+
3749+
static void
3750+
slot_update_free_chunks(slot_update_t *updates)
3751+
{
3752+
slot_update_chunk_t *chunk = updates->head;
3753+
while (chunk != NULL) {
3754+
slot_update_chunk_t *prev = chunk->prev;
3755+
PyMem_Free(chunk);
3756+
chunk = prev;
3757+
}
3758+
}
3759+
3760+
static int
3761+
queue_slot_update(slot_update_t *updates, void **slot_ptr, void *slot_value)
3762+
{
3763+
if (*slot_ptr == slot_value) {
3764+
return 0; // slot pointer not actually changed, don't queue update
3765+
}
3766+
if (updates->head == NULL || updates->head->n == SLOT_UPDATE_CHUNK_SIZE) {
3767+
slot_update_chunk_t *chunk = slot_update_new_chunk();
3768+
if (chunk == NULL) {
3769+
return -1; // out-of-memory
3770+
}
3771+
chunk->prev = updates->head;
3772+
updates->head = chunk;
3773+
}
3774+
slot_update_item_t *item = &updates->head->updates[updates->head->n];
3775+
item->slot_ptr = slot_ptr;
3776+
item->slot_value = slot_value;
3777+
updates->head->n++;
3778+
assert(updates->head->n <= SLOT_UPDATE_CHUNK_SIZE);
3779+
return 0;
3780+
}
3781+
3782+
static void
3783+
apply_slot_updates(slot_update_t *updates)
3784+
{
3785+
assert(types_world_is_stopped());
3786+
slot_update_chunk_t *chunk = updates->head;
3787+
while (chunk != NULL) {
3788+
for (Py_ssize_t i = 0; i < chunk->n; i++) {
3789+
slot_update_item_t *item = &chunk->updates[i];
3790+
*(item->slot_ptr) = item->slot_value;
3791+
}
3792+
chunk = chunk->prev;
3793+
}
3794+
}
3795+
3796+
#else
3797+
3798+
// not used, slot updates are applied immediately
3799+
typedef struct {} slot_update_t;
3800+
3801+
#endif
3802+
3803+
/// data passed to update_slots_callback()
3804+
typedef struct {
3805+
slot_update_t *queued_updates;
3806+
pytype_slotdef **defs;
3807+
} update_callback_data_t;
3808+
36933809
static void object_dealloc(PyObject *);
36943810
static PyObject *object_new(PyTypeObject *, PyObject *, PyObject *);
36953811
static int object_init(PyObject *, PyObject *, PyObject *);
3696-
static int update_slot(PyTypeObject *, PyObject *);
3812+
static int update_slot(PyTypeObject *, PyObject *, slot_update_t *update);
36973813
static void fixup_slot_dispatchers(PyTypeObject *);
36983814
static int type_new_set_names(PyTypeObject *);
36993815
static int type_new_init_subclass(PyTypeObject *, PyObject *);
@@ -6274,7 +6390,7 @@ type_setattro(PyObject *self, PyObject *name, PyObject *value)
62746390
if (is_dunder_name(name) && has_slotdef(name)) {
62756391
// The name corresponds to a type slot.
62766392
types_stop_world();
6277-
res = update_slot(type, name);
6393+
res = update_slot(type, name, NULL);
62786394
types_start_world();
62796395
ASSERT_TYPE_LOCK_HELD();
62806396
}
@@ -11254,13 +11370,22 @@ has_slotdef(PyObject *name)
1125411370
* There are some further special cases for specific slots, like supporting
1125511371
* __hash__ = None for tp_hash and special code for tp_new.
1125611372
*
11257-
* When done, return a pointer to the next slotdef with a different offset,
11258-
* because that's convenient for fixup_slot_dispatchers(). This function never
11259-
* sets an exception: if an internal error happens (unlikely), it's ignored. */
11260-
static pytype_slotdef *
11261-
update_one_slot(PyTypeObject *type, pytype_slotdef *p)
11373+
* When done, next_p is set to the next slotdef with a different offset,
11374+
* because that's convenient for fixup_slot_dispatchers().
11375+
*
11376+
* If the queued_updates pointer is provided, the actual updates to the slot
11377+
* pointers are queued, rather than being immediately performed. That argument
11378+
* is only used for the free-threaded build since those updates need to be
11379+
* done while the world is stopped.
11380+
*
11381+
* This function will only return an error if the queued_updates argument is
11382+
* provided and allocating memory for the queue fails. Other exceptions that
11383+
* occur internally are ignored, such as when looking up descriptors. */
11384+
static int
11385+
update_one_slot(PyTypeObject *type, pytype_slotdef *p, pytype_slotdef **next_p,
11386+
slot_update_t *queued_updates)
1126211387
{
11263-
ASSERT_WORLD_STOPPED_OR_NEW_TYPE(type);
11388+
ASSERT_NEW_TYPE_OR_LOCKED(type);
1126411389

1126511390
PyObject *descr;
1126611391
PyWrapperDescrObject *d;
@@ -11283,7 +11408,10 @@ update_one_slot(PyTypeObject *type, pytype_slotdef *p)
1128311408
do {
1128411409
++p;
1128511410
} while (p->offset == offset);
11286-
return p;
11411+
if (next_p != NULL) {
11412+
*next_p = p;
11413+
}
11414+
return 0;
1128711415
}
1128811416
/* We may end up clearing live exceptions below, so make sure it's ours. */
1128911417
assert(!PyErr_Occurred());
@@ -11371,37 +11499,63 @@ update_one_slot(PyTypeObject *type, pytype_slotdef *p)
1137111499
}
1137211500
Py_DECREF(descr);
1137311501
} while ((++p)->offset == offset);
11374-
if (specific && !use_generic)
11375-
*ptr = specific;
11376-
else
11377-
*ptr = generic;
11378-
return p;
11502+
11503+
void *slot_value;
11504+
if (specific && !use_generic) {
11505+
slot_value = specific;
11506+
} else {
11507+
slot_value = generic;
11508+
}
11509+
11510+
#ifdef Py_GIL_DISABLED
11511+
if (queued_updates != NULL) {
11512+
// queue the update to perform later, while world is stopped
11513+
if (queue_slot_update(queued_updates, ptr, slot_value) < 0) {
11514+
return -1;
11515+
}
11516+
} else {
11517+
// do the update to the type structure now
11518+
*ptr = slot_value;
11519+
}
11520+
#else
11521+
// always do the update immediately
11522+
assert(queued_updates == NULL);
11523+
*ptr = slot_value;
11524+
#endif
11525+
11526+
if (next_p != NULL) {
11527+
*next_p = p;
11528+
}
11529+
return 0;
1137911530
}
1138011531

1138111532
/* In the type, update the slots whose slotdefs are gathered in the pp array.
1138211533
This is a callback for update_subclasses(). */
1138311534
static int
1138411535
update_slots_callback(PyTypeObject *type, void *data)
1138511536
{
11386-
ASSERT_WORLD_STOPPED_OR_NEW_TYPE(type);
11537+
ASSERT_NEW_TYPE_OR_LOCKED(type);
1138711538

11388-
pytype_slotdef **pp = (pytype_slotdef **)data;
11539+
update_callback_data_t *update_data = (update_callback_data_t *)data;
11540+
pytype_slotdef **pp = update_data->defs;
1138911541
for (; *pp; pp++) {
11390-
update_one_slot(type, *pp);
11542+
if (update_one_slot(type, *pp, NULL, update_data->queued_updates) < 0) {
11543+
return -1;
11544+
}
1139111545
}
1139211546
return 0;
1139311547
}
1139411548

1139511549
/* Update the slots after assignment to a class (type) attribute. */
1139611550
static int
11397-
update_slot(PyTypeObject *type, PyObject *name)
11551+
update_slot(PyTypeObject *type, PyObject *name, slot_update_t *queued_updates)
1139811552
{
1139911553
pytype_slotdef *ptrs[MAX_EQUIV];
1140011554
pytype_slotdef *p;
1140111555
pytype_slotdef **pp;
1140211556
int offset;
1140311557

11404-
assert(types_world_is_stopped());
11558+
ASSERT_TYPE_LOCK_HELD();
1140511559
assert(PyUnicode_CheckExact(name));
1140611560
assert(PyUnicode_CHECK_INTERNED(name));
1140711561

@@ -11425,8 +11579,12 @@ update_slot(PyTypeObject *type, PyObject *name)
1142511579
}
1142611580
if (ptrs[0] == NULL)
1142711581
return 0; /* Not an attribute that affects any slots */
11582+
11583+
update_callback_data_t callback_data;
11584+
callback_data.defs = ptrs;
11585+
callback_data.queued_updates = queued_updates;
1142811586
return update_subclasses(type, name,
11429-
update_slots_callback, (void *)ptrs);
11587+
update_slots_callback, (void *)&callback_data);
1143011588
}
1143111589

1143211590
/* Store the proper functions in the slot dispatches at class (type)
@@ -11437,27 +11595,64 @@ fixup_slot_dispatchers(PyTypeObject *type)
1143711595
{
1143811596
assert(!PyErr_Occurred());
1143911597
for (pytype_slotdef *p = slotdefs; p->name; ) {
11440-
p = update_one_slot(type, p);
11598+
update_one_slot(type, p, &p, NULL);
1144111599
}
1144211600
}
1144311601

11602+
#ifdef Py_GIL_DISABLED
11603+
1144411604
// Called when __bases__ is re-assigned.
11445-
static void
11605+
static int
1144611606
update_all_slots(PyTypeObject* type)
1144711607
{
11448-
pytype_slotdef *p;
11608+
// Note that update_slot() can fail due to out-of-memory when allocating
11609+
// the queue chunks to hold the updates. That's unlikely since the number
11610+
// of updates is normally small but we handle that case. update_slot()
11611+
// can fail internally for other reasons (a lookup fails) but those
11612+
// errors are suppressed.
11613+
slot_update_t queued_updates = {0};
11614+
for (pytype_slotdef *p = slotdefs; p->name; p++) {
11615+
if (update_slot(type, p->name_strobj, &queued_updates) < 0) {
11616+
if (queued_updates.head) {
11617+
slot_update_free_chunks(&queued_updates);
11618+
}
11619+
return -1;
11620+
}
11621+
}
11622+
if (queued_updates.head != NULL) {
11623+
types_stop_world();
11624+
apply_slot_updates(&queued_updates);
11625+
types_start_world();
11626+
ASSERT_TYPE_LOCK_HELD();
1144911627

11450-
assert(types_world_is_stopped());
11628+
slot_update_free_chunks(&queued_updates);
11629+
11630+
/* Clear the VALID_VERSION flag of 'type' and all its subclasses. */
11631+
type_modified_unlocked(type);
11632+
}
11633+
return 0;
11634+
}
11635+
11636+
#else
11637+
11638+
// Called when __bases__ is re-assigned.
11639+
static int
11640+
update_all_slots(PyTypeObject* type)
11641+
{
11642+
pytype_slotdef *p;
1145111643

1145211644
for (p = slotdefs; p->name; p++) {
11453-
/* update_slot returns int but can't actually fail */
11454-
update_slot(type, p->name_strobj);
11645+
/* update_slot returns int but can't actually fail in this case*/
11646+
update_slot(type, p->name_strobj, NULL);
1145511647
}
1145611648

1145711649
/* Clear the VALID_VERSION flag of 'type' and all its subclasses. */
1145811650
type_modified_unlocked(type);
11651+
return 0;
1145911652
}
1146011653

11654+
#endif
11655+
1146111656

1146211657
PyObject *
1146311658
_PyType_GetSlotWrapperNames(void)

0 commit comments

Comments
 (0)