@@ -64,17 +64,18 @@ static NEXT_TYPE_VERSION: AtomicU32 = AtomicU32::new(1);
6464// Method cache (type_cache / MCACHE): direct-mapped cache keyed by
6565// (tp_version_tag, interned_name_ptr).
6666//
67- // Uses a lock-free SeqLock pattern:
68- // - version acts as both cache key AND sequence counter
69- // - Read: load version (Acquire), read value ptr, re-check version
70- // - Write: set version=0 (invalidate), store value, store version (Release)
67+ // Uses a lock-free SeqLock pattern for the read/write protocol:
68+ // - Readers validate sequence/version/name before and after the value read.
69+ // - Writers bracket updates with sequence odd/even transitions.
7170// No mutex needed on the hot path (cache hit).
7271
7372const TYPE_CACHE_SIZE_EXP : u32 = 12 ;
7473const TYPE_CACHE_SIZE : usize = 1 << TYPE_CACHE_SIZE_EXP ;
7574const TYPE_CACHE_MASK : usize = TYPE_CACHE_SIZE - 1 ;
7675
7776struct TypeCacheEntry {
77+ /// Sequence lock (odd = write in progress, even = quiescent).
78+ sequence : AtomicU32 ,
7879 /// tp_version_tag at cache time. 0 = empty/invalid.
7980 version : AtomicU32 ,
8081 /// Interned attribute name pointer (pointer equality check).
@@ -94,12 +95,39 @@ unsafe impl Sync for TypeCacheEntry {}
9495impl TypeCacheEntry {
9596 fn new ( ) -> Self {
9697 Self {
98+ sequence : AtomicU32 :: new ( 0 ) ,
9799 version : AtomicU32 :: new ( 0 ) ,
98100 name : AtomicPtr :: new ( core:: ptr:: null_mut ( ) ) ,
99101 value : AtomicPtr :: new ( core:: ptr:: null_mut ( ) ) ,
100102 }
101103 }
102104
105+ #[ inline]
106+ fn begin_write ( & self ) {
107+ self . sequence . fetch_add ( 1 , Ordering :: AcqRel ) ;
108+ }
109+
110+ #[ inline]
111+ fn end_write ( & self ) {
112+ self . sequence . fetch_add ( 1 , Ordering :: Release ) ;
113+ }
114+
115+ #[ inline]
116+ fn begin_read ( & self ) -> u32 {
117+ let mut sequence = self . sequence . load ( Ordering :: Acquire ) ;
118+ while ( sequence & 1 ) != 0 {
119+ core:: hint:: spin_loop ( ) ;
120+ sequence = self . sequence . load ( Ordering :: Acquire ) ;
121+ }
122+ sequence
123+ }
124+
125+ #[ inline]
126+ fn end_read ( & self , previous : u32 ) -> bool {
127+ core:: sync:: atomic:: fence ( Ordering :: Acquire ) ;
128+ self . sequence . load ( Ordering :: Relaxed ) == previous
129+ }
130+
103131 /// Take the value out of this entry, returning the owned PyObjectRef.
104132 /// Caller must ensure no concurrent reads can observe this entry
105133 /// (version should be set to 0 first).
@@ -137,10 +165,14 @@ fn type_cache_clear_version(version: u32) {
137165 let mut to_drop = Vec :: new ( ) ;
138166 for entry in TYPE_CACHE . iter ( ) {
139167 if entry. version . load ( Ordering :: Relaxed ) == version {
140- entry. version . store ( 0 , Ordering :: Release ) ;
141- if let Some ( v) = entry. take_value ( ) {
142- to_drop. push ( v) ;
168+ entry. begin_write ( ) ;
169+ if entry. version . load ( Ordering :: Relaxed ) == version {
170+ entry. version . store ( 0 , Ordering :: Release ) ;
171+ if let Some ( v) = entry. take_value ( ) {
172+ to_drop. push ( v) ;
173+ }
143174 }
175+ entry. end_write ( ) ;
144176 }
145177 }
146178 drop ( to_drop) ;
@@ -158,10 +190,12 @@ pub fn type_cache_clear() {
158190 // Invalidate all entries and collect values.
159191 let mut to_drop = Vec :: new ( ) ;
160192 for entry in TYPE_CACHE . iter ( ) {
193+ entry. begin_write ( ) ;
161194 entry. version . store ( 0 , Ordering :: Release ) ;
162195 if let Some ( v) = entry. take_value ( ) {
163196 to_drop. push ( v) ;
164197 }
198+ entry. end_write ( ) ;
165199 }
166200 drop ( to_drop) ;
167201 TYPE_CACHE_CLEARING . store ( false , Ordering :: Release ) ;
@@ -701,8 +735,11 @@ impl PyType {
701735 }
702736
703737 pub fn set_attr ( & self , attr_name : & ' static PyStrInterned , value : PyObjectRef ) {
704- self . attributes . write ( ) . insert ( attr_name, value) ;
738+ // Invalidate caches BEFORE modifying attributes so that cached
739+ // descriptor pointers are still alive when type_cache_clear_version
740+ // drops the cache's strong references.
705741 self . modified ( ) ;
742+ self . attributes . write ( ) . insert ( attr_name, value) ;
706743 }
707744
708745 /// Internal get_attr implementation for fast lookup on a class.
@@ -718,41 +755,44 @@ impl PyType {
718755 /// find_name_in_mro with method cache (MCACHE).
719756 /// Looks in tp_dict of types in MRO, bypasses descriptors.
720757 ///
721- /// Uses a lock-free SeqLock pattern keyed by version:
722- /// Read: load version → check name → load value → clone → re-check version
723- /// Write: version=0 → swap value → set name → version=assigned
758+ /// Uses a lock-free SeqLock-style pattern:
759+ /// Read: load sequence/version/name → load value + try_to_owned →
760+ /// validate value pointer + sequence
761+ /// Write: sequence(begin) → version=0 → swap value/name → version=assigned → sequence(end)
724762 fn find_name_in_mro ( & self , name : & ' static PyStrInterned ) -> Option < PyObjectRef > {
725763 let version = self . tp_version_tag . load ( Ordering :: Acquire ) ;
726764 if version != 0 {
727765 let idx = type_cache_hash ( version, name) ;
728766 let entry = & TYPE_CACHE [ idx] ;
729- let v1 = entry. version . load ( Ordering :: Acquire ) ;
730- if v1 == version
731- && core:: ptr:: eq (
732- entry. name . load ( Ordering :: Relaxed ) ,
733- name as * const _ as * mut _ ,
734- )
735- {
767+ let name_ptr = name as * const _ as * mut _ ;
768+ loop {
769+ let seq1 = entry. begin_read ( ) ;
770+ let v1 = entry. version . load ( Ordering :: Acquire ) ;
771+ let type_version = self . tp_version_tag . load ( Ordering :: Acquire ) ;
772+ if v1 != type_version
773+ || !core:: ptr:: eq ( entry. name . load ( Ordering :: Relaxed ) , name_ptr)
774+ {
775+ break ;
776+ }
736777 let ptr = entry. value . load ( Ordering :: Acquire ) ;
737- if !ptr. is_null ( ) {
738- // SAFETY: The value pointer was stored via PyObjectRef::into_raw
739- // and is valid as long as the version hasn't changed. We create
740- // a temporary reference (ManuallyDrop prevents decrement), clone
741- // it to get our own strong reference, then re-check the version
742- // to confirm the entry wasn't invalidated during our read.
743- let cloned = unsafe {
744- let tmp = core:: mem:: ManuallyDrop :: new ( PyObjectRef :: from_raw (
745- NonNull :: new_unchecked ( ptr) ,
746- ) ) ;
747- ( * tmp) . clone ( )
748- } ;
749- // SeqLock validation: if version changed, discard our clone
750- let v2 = entry. version . load ( Ordering :: Acquire ) ;
751- if v2 == v1 {
778+ if ptr. is_null ( ) {
779+ if entry. end_read ( seq1) {
780+ break ;
781+ }
782+ continue ;
783+ }
784+ // _Py_TryIncrefCompare-style validation:
785+ // safe_inc, then ensure the source pointer is unchanged.
786+ let obj: & PyObject = unsafe { & * ptr } ;
787+ if let Some ( cloned) = obj. try_to_owned ( ) {
788+ let same_ptr = core:: ptr:: eq ( entry. value . load ( Ordering :: Relaxed ) , ptr) ;
789+ if same_ptr && entry. end_read ( seq1) {
752790 return Some ( cloned) ;
753791 }
754792 drop ( cloned) ;
793+ continue ;
755794 }
795+ break ;
756796 }
757797 }
758798
@@ -777,16 +817,17 @@ impl PyType {
777817 {
778818 let idx = type_cache_hash ( assigned, name) ;
779819 let entry = & TYPE_CACHE [ idx] ;
820+ let name_ptr = name as * const _ as * mut _ ;
821+ entry. begin_write ( ) ;
780822 // Invalidate first to prevent readers from seeing partial state
781823 entry. version . store ( 0 , Ordering :: Release ) ;
782824 // Swap in new value (refcount held by cache)
783825 let new_ptr = found. clone ( ) . into_raw ( ) . as_ptr ( ) ;
784826 let old_ptr = entry. value . swap ( new_ptr, Ordering :: Relaxed ) ;
785- entry
786- . name
787- . store ( name as * const _ as * mut _ , Ordering :: Relaxed ) ;
827+ entry. name . store ( name_ptr, Ordering :: Relaxed ) ;
788828 // Activate entry — Release ensures value/name writes are visible
789829 entry. version . store ( assigned, Ordering :: Release ) ;
830+ entry. end_write ( ) ;
790831 // Drop previous occupant (its version was already invalidated)
791832 if !old_ptr. is_null ( ) {
792833 unsafe {
@@ -832,20 +873,24 @@ impl PyType {
832873 if version != 0 {
833874 let idx = type_cache_hash ( version, name) ;
834875 let entry = & TYPE_CACHE [ idx] ;
835- let v1 = entry. version . load ( Ordering :: Acquire ) ;
836- if v1 == version
837- && core:: ptr:: eq (
838- entry. name . load ( Ordering :: Relaxed ) ,
839- name as * const _ as * mut _ ,
840- )
841- {
876+ let name_ptr = name as * const _ as * mut _ ;
877+ loop {
878+ let seq1 = entry. begin_read ( ) ;
879+ let v1 = entry. version . load ( Ordering :: Acquire ) ;
880+ let type_version = self . tp_version_tag . load ( Ordering :: Acquire ) ;
881+ if v1 != type_version
882+ || !core:: ptr:: eq ( entry. name . load ( Ordering :: Relaxed ) , name_ptr)
883+ {
884+ break ;
885+ }
842886 let ptr = entry. value . load ( Ordering :: Acquire ) ;
843- if !ptr. is_null ( ) {
844- let v2 = entry. version . load ( Ordering :: Acquire ) ;
845- if v2 == v1 {
887+ if entry. end_read ( seq1) {
888+ if !ptr. is_null ( ) {
846889 return true ;
847890 }
891+ break ;
848892 }
893+ continue ;
849894 }
850895 }
851896
@@ -1498,8 +1543,8 @@ impl PyType {
14981543 PySetterValue :: Assign ( ref val) => {
14991544 let key = identifier ! ( vm, __type_params__) ;
15001545 self . check_set_special_type_attr ( key, vm) ?;
1501- self . attributes . write ( ) . insert ( key, val. clone ( ) . into ( ) ) ;
15021546 self . modified ( ) ;
1547+ self . attributes . write ( ) . insert ( key, val. clone ( ) . into ( ) ) ;
15031548 }
15041549 PySetterValue :: Delete => {
15051550 // For delete, we still need to check if the type is immutable
@@ -1510,8 +1555,8 @@ impl PyType {
15101555 ) ) ) ;
15111556 }
15121557 let key = identifier ! ( vm, __type_params__) ;
1513- self . attributes . write ( ) . shift_remove ( & key) ;
15141558 self . modified ( ) ;
1559+ self . attributes . write ( ) . shift_remove ( & key) ;
15151560 }
15161561 }
15171562 Ok ( ( ) )
0 commit comments