@@ -181,7 +181,8 @@ pub trait PyDictMethods<'py>: crate::sealed::Sealed {
181181 /// Iterates over the contents of this dictionary while holding a critical section on the dict.
182182 /// This is useful when the GIL is disabled and the dictionary is shared between threads.
183183 /// 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.
185186 ///
186187 /// This method is a small performance optimization over `.iter().try_for_each()` when the
187188 /// nightly feature is not enabled because we cannot implement an optimised version of
@@ -396,19 +397,26 @@ impl<'a, 'py> Borrowed<'a, 'py, PyDict> {
396397 /// Iterates over the contents of this dictionary without incrementing reference counts.
397398 ///
398399 /// # 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.
400402 pub ( crate ) unsafe fn iter_borrowed ( self ) -> BorrowedDictIter < ' a , ' py > {
401403 BorrowedDictIter :: new ( self )
402404 }
403405}
404406
405407fn 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 ) ) ]
407409 unsafe {
408410 ffi:: PyDict_Size ( dict. as_ptr ( ) )
409411 }
410412
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+ ) ) ]
412420 unsafe {
413421 ( * dict. as_ptr ( ) . cast :: < ffi:: PyDictObject > ( ) ) . ma_used
414422 }
@@ -429,8 +437,11 @@ enum DictIterImpl {
429437}
430438
431439impl DictIterImpl {
440+ #[ deny( unsafe_op_in_unsafe_fn) ]
432441 #[ 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 > (
434445 & mut self ,
435446 dict : & Bound < ' py , PyDict > ,
436447 ) -> Option < ( Bound < ' py , PyAny > , Bound < ' py , PyAny > ) > {
@@ -440,7 +451,7 @@ impl DictIterImpl {
440451 remaining,
441452 ppos,
442453 ..
443- } => crate :: sync :: with_critical_section ( dict , || {
454+ } => {
444455 let ma_used = dict_len ( dict) ;
445456
446457 // These checks are similar to what CPython does.
@@ -470,20 +481,20 @@ impl DictIterImpl {
470481 let mut key: * mut ffi:: PyObject = std:: ptr:: null_mut ( ) ;
471482 let mut value: * mut ffi:: PyObject = std:: ptr:: null_mut ( ) ;
472483
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 } {
474485 * remaining -= 1 ;
475486 let py = dict. py ( ) ;
476487 // Safety:
477488 // - PyDict_Next returns borrowed values
478489 // - we have already checked that `PyDict_Next` succeeded, so we can assume these to be non-null
479490 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 ( ) } ,
482493 ) )
483494 } else {
484495 None
485496 }
486- } ) ,
497+ }
487498 }
488499 }
489500
@@ -504,7 +515,17 @@ impl<'py> Iterator for BoundDictIterator<'py> {
504515
505516 #[ inline]
506517 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+ }
508529 }
509530
510531 #[ inline]
@@ -522,7 +543,7 @@ impl<'py> Iterator for BoundDictIterator<'py> {
522543 {
523544 self . inner . with_critical_section ( & self . dict , |inner| {
524545 let mut accum = init;
525- while let Some ( x) = inner. next ( & self . dict ) {
546+ while let Some ( x) = unsafe { inner. next_unchecked ( & self . dict ) } {
526547 accum = f ( accum, x) ;
527548 }
528549 accum
@@ -539,7 +560,7 @@ impl<'py> Iterator for BoundDictIterator<'py> {
539560 {
540561 self . inner . with_critical_section ( & self . dict , |inner| {
541562 let mut accum = init;
542- while let Some ( x) = inner. next ( & self . dict ) {
563+ while let Some ( x) = unsafe { inner. next_unchecked ( & self . dict ) } {
543564 accum = f ( accum, x) ?
544565 }
545566 R :: from_output ( accum)
@@ -554,7 +575,7 @@ impl<'py> Iterator for BoundDictIterator<'py> {
554575 F : FnMut ( Self :: Item ) -> bool ,
555576 {
556577 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 ) } {
558579 if !f ( x) {
559580 return false ;
560581 }
@@ -571,7 +592,7 @@ impl<'py> Iterator for BoundDictIterator<'py> {
571592 F : FnMut ( Self :: Item ) -> bool ,
572593 {
573594 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 ) } {
575596 if f ( x) {
576597 return true ;
577598 }
@@ -588,7 +609,7 @@ impl<'py> Iterator for BoundDictIterator<'py> {
588609 P : FnMut ( & Self :: Item ) -> bool ,
589610 {
590611 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 ) } {
592613 if predicate ( & x) {
593614 return Some ( x) ;
594615 }
@@ -605,7 +626,7 @@ impl<'py> Iterator for BoundDictIterator<'py> {
605626 F : FnMut ( Self :: Item ) -> Option < B > ,
606627 {
607628 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 ) } {
609630 if let found @ Some ( _) = f ( x) {
610631 return found;
611632 }
@@ -623,7 +644,7 @@ impl<'py> Iterator for BoundDictIterator<'py> {
623644 {
624645 self . inner . with_critical_section ( & self . dict , |inner| {
625646 let mut acc = 0 ;
626- while let Some ( x) = inner. next ( & self . dict ) {
647+ while let Some ( x) = unsafe { inner. next_unchecked ( & self . dict ) } {
627648 if predicate ( x) {
628649 return Some ( acc) ;
629650 }
0 commit comments