@@ -181,7 +181,8 @@ pub trait PyDictMethods<'py>: crate::sealed::Sealed {
181
181
/// Iterates over the contents of this dictionary while holding a critical section on the dict.
182
182
/// This is useful when the GIL is disabled and the dictionary is shared between threads.
183
183
/// It is not guaranteed that the dictionary will not be modified during iteration when the
184
- /// closure calls arbitrary Python code that releases the current critical section.
184
+ /// closure calls arbitrary Python code that releases the critical section held by the
185
+ /// iterator. Otherwise, the dictionary will not be modified during iteration.
185
186
///
186
187
/// This method is a small performance optimization over `.iter().try_for_each()` when the
187
188
/// nightly feature is not enabled because we cannot implement an optimised version of
@@ -396,19 +397,26 @@ impl<'a, 'py> Borrowed<'a, 'py, PyDict> {
396
397
/// Iterates over the contents of this dictionary without incrementing reference counts.
397
398
///
398
399
/// # Safety
399
- /// It must be known that this dictionary will not be modified during iteration.
400
+ /// It must be known that this dictionary will not be modified during iteration,
401
+ /// for example, when parsing arguments in a keyword arguments dictionary.
400
402
pub ( crate ) unsafe fn iter_borrowed ( self ) -> BorrowedDictIter < ' a , ' py > {
401
403
BorrowedDictIter :: new ( self )
402
404
}
403
405
}
404
406
405
407
fn dict_len ( dict : & Bound < ' _ , PyDict > ) -> Py_ssize_t {
406
- #[ cfg( any( not( Py_3_8 ) , PyPy , GraalPy , Py_LIMITED_API ) ) ]
408
+ #[ cfg( any( not( Py_3_8 ) , PyPy , GraalPy , Py_LIMITED_API , Py_GIL_DISABLED ) ) ]
407
409
unsafe {
408
410
ffi:: PyDict_Size ( dict. as_ptr ( ) )
409
411
}
410
412
411
- #[ cfg( all( Py_3_8 , not( PyPy ) , not( GraalPy ) , not( Py_LIMITED_API ) ) ) ]
413
+ #[ cfg( all(
414
+ Py_3_8 ,
415
+ not( PyPy ) ,
416
+ not( GraalPy ) ,
417
+ not( Py_LIMITED_API ) ,
418
+ not( Py_GIL_DISABLED )
419
+ ) ) ]
412
420
unsafe {
413
421
( * dict. as_ptr ( ) . cast :: < ffi:: PyDictObject > ( ) ) . ma_used
414
422
}
@@ -429,8 +437,11 @@ enum DictIterImpl {
429
437
}
430
438
431
439
impl DictIterImpl {
440
+ #[ deny( unsafe_op_in_unsafe_fn) ]
432
441
#[ inline]
433
- fn next < ' py > (
442
+ /// Safety: the dict should be locked with a critical section on the free-threaded build
443
+ /// and otherwise not shared between threads in code that releases the GIL.
444
+ unsafe fn next_unchecked < ' py > (
434
445
& mut self ,
435
446
dict : & Bound < ' py , PyDict > ,
436
447
) -> Option < ( Bound < ' py , PyAny > , Bound < ' py , PyAny > ) > {
@@ -440,7 +451,7 @@ impl DictIterImpl {
440
451
remaining,
441
452
ppos,
442
453
..
443
- } => crate :: sync :: with_critical_section ( dict , || {
454
+ } => {
444
455
let ma_used = dict_len ( dict) ;
445
456
446
457
// These checks are similar to what CPython does.
@@ -470,20 +481,20 @@ impl DictIterImpl {
470
481
let mut key: * mut ffi:: PyObject = std:: ptr:: null_mut ( ) ;
471
482
let mut value: * mut ffi:: PyObject = std:: ptr:: null_mut ( ) ;
472
483
473
- if unsafe { ffi:: PyDict_Next ( dict. as_ptr ( ) , ppos, & mut key, & mut value) } != 0 {
484
+ if unsafe { ffi:: PyDict_Next ( dict. as_ptr ( ) , ppos, & mut key, & mut value) != 0 } {
474
485
* remaining -= 1 ;
475
486
let py = dict. py ( ) ;
476
487
// Safety:
477
488
// - PyDict_Next returns borrowed values
478
489
// - we have already checked that `PyDict_Next` succeeded, so we can assume these to be non-null
479
490
Some ( (
480
- unsafe { key. assume_borrowed_unchecked ( py) } . to_owned ( ) ,
481
- unsafe { value. assume_borrowed_unchecked ( py) } . to_owned ( ) ,
491
+ unsafe { key. assume_borrowed_unchecked ( py) . to_owned ( ) } ,
492
+ unsafe { value. assume_borrowed_unchecked ( py) . to_owned ( ) } ,
482
493
) )
483
494
} else {
484
495
None
485
496
}
486
- } ) ,
497
+ }
487
498
}
488
499
}
489
500
@@ -504,7 +515,17 @@ impl<'py> Iterator for BoundDictIterator<'py> {
504
515
505
516
#[ inline]
506
517
fn next ( & mut self ) -> Option < Self :: Item > {
507
- self . inner . next ( & self . dict )
518
+ #[ cfg( Py_GIL_DISABLED ) ]
519
+ {
520
+ self . inner
521
+ . with_critical_section ( & self . dict , |inner| unsafe {
522
+ inner. next_unchecked ( & self . dict )
523
+ } )
524
+ }
525
+ #[ cfg( not( Py_GIL_DISABLED ) ) ]
526
+ {
527
+ unsafe { self . inner . next_unchecked ( & self . dict ) }
528
+ }
508
529
}
509
530
510
531
#[ inline]
@@ -522,7 +543,7 @@ impl<'py> Iterator for BoundDictIterator<'py> {
522
543
{
523
544
self . inner . with_critical_section ( & self . dict , |inner| {
524
545
let mut accum = init;
525
- while let Some ( x) = inner. next ( & self . dict ) {
546
+ while let Some ( x) = unsafe { inner. next_unchecked ( & self . dict ) } {
526
547
accum = f ( accum, x) ;
527
548
}
528
549
accum
@@ -539,7 +560,7 @@ impl<'py> Iterator for BoundDictIterator<'py> {
539
560
{
540
561
self . inner . with_critical_section ( & self . dict , |inner| {
541
562
let mut accum = init;
542
- while let Some ( x) = inner. next ( & self . dict ) {
563
+ while let Some ( x) = unsafe { inner. next_unchecked ( & self . dict ) } {
543
564
accum = f ( accum, x) ?
544
565
}
545
566
R :: from_output ( accum)
@@ -554,7 +575,7 @@ impl<'py> Iterator for BoundDictIterator<'py> {
554
575
F : FnMut ( Self :: Item ) -> bool ,
555
576
{
556
577
self . inner . with_critical_section ( & self . dict , |inner| {
557
- while let Some ( x) = inner. next ( & self . dict ) {
578
+ while let Some ( x) = unsafe { inner. next_unchecked ( & self . dict ) } {
558
579
if !f ( x) {
559
580
return false ;
560
581
}
@@ -571,7 +592,7 @@ impl<'py> Iterator for BoundDictIterator<'py> {
571
592
F : FnMut ( Self :: Item ) -> bool ,
572
593
{
573
594
self . inner . with_critical_section ( & self . dict , |inner| {
574
- while let Some ( x) = inner. next ( & self . dict ) {
595
+ while let Some ( x) = unsafe { inner. next_unchecked ( & self . dict ) } {
575
596
if f ( x) {
576
597
return true ;
577
598
}
@@ -588,7 +609,7 @@ impl<'py> Iterator for BoundDictIterator<'py> {
588
609
P : FnMut ( & Self :: Item ) -> bool ,
589
610
{
590
611
self . inner . with_critical_section ( & self . dict , |inner| {
591
- while let Some ( x) = inner. next ( & self . dict ) {
612
+ while let Some ( x) = unsafe { inner. next_unchecked ( & self . dict ) } {
592
613
if predicate ( & x) {
593
614
return Some ( x) ;
594
615
}
@@ -605,7 +626,7 @@ impl<'py> Iterator for BoundDictIterator<'py> {
605
626
F : FnMut ( Self :: Item ) -> Option < B > ,
606
627
{
607
628
self . inner . with_critical_section ( & self . dict , |inner| {
608
- while let Some ( x) = inner. next ( & self . dict ) {
629
+ while let Some ( x) = unsafe { inner. next_unchecked ( & self . dict ) } {
609
630
if let found @ Some ( _) = f ( x) {
610
631
return found;
611
632
}
@@ -623,7 +644,7 @@ impl<'py> Iterator for BoundDictIterator<'py> {
623
644
{
624
645
self . inner . with_critical_section ( & self . dict , |inner| {
625
646
let mut acc = 0 ;
626
- while let Some ( x) = inner. next ( & self . dict ) {
647
+ while let Some ( x) = unsafe { inner. next_unchecked ( & self . dict ) } {
627
648
if predicate ( x) {
628
649
return Some ( acc) ;
629
650
}
0 commit comments