Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit f447ce4

Browse filesBrowse 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
Copy full SHA for f447ce4

File tree

1 file changed

+226
-31
lines changed
Filter options

1 file changed

+226
-31
lines changed

‎Objects/typeobject.c

Copy file name to clipboardExpand all lines: Objects/typeobject.c
+226-31Lines 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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.