@@ -1687,7 +1687,7 @@ static int add_subclass(PyTypeObject*, PyTypeObject*);
1687
1687
static int add_all_subclasses (PyTypeObject * type , PyObject * bases );
1688
1688
static void remove_subclass (PyTypeObject * , PyTypeObject * );
1689
1689
static void remove_all_subclasses (PyTypeObject * type , PyObject * bases );
1690
- static void update_all_slots (PyTypeObject * );
1690
+ static int update_all_slots (PyTypeObject * );
1691
1691
1692
1692
typedef int (* update_callback )(PyTypeObject * , void * );
1693
1693
static int update_subclasses (PyTypeObject * type , PyObject * attr_name ,
@@ -1862,10 +1862,9 @@ type_set_bases_unlocked(PyTypeObject *type, PyObject *new_bases, PyTypeObject *b
1862
1862
add to all new_bases */
1863
1863
remove_all_subclasses (type , old_bases );
1864
1864
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
+ }
1869
1868
}
1870
1869
else {
1871
1870
res = 0 ;
@@ -3690,10 +3689,127 @@ solid_base(PyTypeObject *type)
3690
3689
}
3691
3690
}
3692
3691
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
+
3693
3809
static void object_dealloc (PyObject * );
3694
3810
static PyObject * object_new (PyTypeObject * , PyObject * , PyObject * );
3695
3811
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 );
3697
3813
static void fixup_slot_dispatchers (PyTypeObject * );
3698
3814
static int type_new_set_names (PyTypeObject * );
3699
3815
static int type_new_init_subclass (PyTypeObject * , PyObject * );
@@ -6274,7 +6390,7 @@ type_setattro(PyObject *self, PyObject *name, PyObject *value)
6274
6390
if (is_dunder_name (name ) && has_slotdef (name )) {
6275
6391
// The name corresponds to a type slot.
6276
6392
types_stop_world ();
6277
- res = update_slot (type , name );
6393
+ res = update_slot (type , name , NULL );
6278
6394
types_start_world ();
6279
6395
ASSERT_TYPE_LOCK_HELD ();
6280
6396
}
@@ -11254,13 +11370,22 @@ has_slotdef(PyObject *name)
11254
11370
* There are some further special cases for specific slots, like supporting
11255
11371
* __hash__ = None for tp_hash and special code for tp_new.
11256
11372
*
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 )
11262
11387
{
11263
- ASSERT_WORLD_STOPPED_OR_NEW_TYPE (type );
11388
+ ASSERT_NEW_TYPE_OR_LOCKED (type );
11264
11389
11265
11390
PyObject * descr ;
11266
11391
PyWrapperDescrObject * d ;
@@ -11283,7 +11408,10 @@ update_one_slot(PyTypeObject *type, pytype_slotdef *p)
11283
11408
do {
11284
11409
++ p ;
11285
11410
} while (p -> offset == offset );
11286
- return p ;
11411
+ if (next_p != NULL ) {
11412
+ * next_p = p ;
11413
+ }
11414
+ return 0 ;
11287
11415
}
11288
11416
/* We may end up clearing live exceptions below, so make sure it's ours. */
11289
11417
assert (!PyErr_Occurred ());
@@ -11371,37 +11499,63 @@ update_one_slot(PyTypeObject *type, pytype_slotdef *p)
11371
11499
}
11372
11500
Py_DECREF (descr );
11373
11501
} 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 ;
11379
11530
}
11380
11531
11381
11532
/* In the type, update the slots whose slotdefs are gathered in the pp array.
11382
11533
This is a callback for update_subclasses(). */
11383
11534
static int
11384
11535
update_slots_callback (PyTypeObject * type , void * data )
11385
11536
{
11386
- ASSERT_WORLD_STOPPED_OR_NEW_TYPE (type );
11537
+ ASSERT_NEW_TYPE_OR_LOCKED (type );
11387
11538
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 ;
11389
11541
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
+ }
11391
11545
}
11392
11546
return 0 ;
11393
11547
}
11394
11548
11395
11549
/* Update the slots after assignment to a class (type) attribute. */
11396
11550
static int
11397
- update_slot (PyTypeObject * type , PyObject * name )
11551
+ update_slot (PyTypeObject * type , PyObject * name , slot_update_t * queued_updates )
11398
11552
{
11399
11553
pytype_slotdef * ptrs [MAX_EQUIV ];
11400
11554
pytype_slotdef * p ;
11401
11555
pytype_slotdef * * pp ;
11402
11556
int offset ;
11403
11557
11404
- assert ( types_world_is_stopped () );
11558
+ ASSERT_TYPE_LOCK_HELD ( );
11405
11559
assert (PyUnicode_CheckExact (name ));
11406
11560
assert (PyUnicode_CHECK_INTERNED (name ));
11407
11561
@@ -11425,8 +11579,12 @@ update_slot(PyTypeObject *type, PyObject *name)
11425
11579
}
11426
11580
if (ptrs [0 ] == NULL )
11427
11581
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 ;
11428
11586
return update_subclasses (type , name ,
11429
- update_slots_callback , (void * )ptrs );
11587
+ update_slots_callback , (void * )& callback_data );
11430
11588
}
11431
11589
11432
11590
/* Store the proper functions in the slot dispatches at class (type)
@@ -11437,27 +11595,64 @@ fixup_slot_dispatchers(PyTypeObject *type)
11437
11595
{
11438
11596
assert (!PyErr_Occurred ());
11439
11597
for (pytype_slotdef * p = slotdefs ; p -> name ; ) {
11440
- p = update_one_slot (type , p );
11598
+ update_one_slot (type , p , & p , NULL );
11441
11599
}
11442
11600
}
11443
11601
11602
+ #ifdef Py_GIL_DISABLED
11603
+
11444
11604
// Called when __bases__ is re-assigned.
11445
- static void
11605
+ static int
11446
11606
update_all_slots (PyTypeObject * type )
11447
11607
{
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 ();
11449
11627
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 ;
11451
11643
11452
11644
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 );
11455
11647
}
11456
11648
11457
11649
/* Clear the VALID_VERSION flag of 'type' and all its subclasses. */
11458
11650
type_modified_unlocked (type );
11651
+ return 0 ;
11459
11652
}
11460
11653
11654
+ #endif
11655
+
11461
11656
11462
11657
PyObject *
11463
11658
_PyType_GetSlotWrapperNames (void )
0 commit comments