@@ -81,7 +81,12 @@ struct TypeCacheEntry {
8181 /// Interned attribute name pointer (pointer equality check).
8282 name : AtomicPtr < PyStrInterned > ,
8383 /// Cached lookup result as raw pointer. null = empty.
84- /// The cache holds a strong reference (refcount incremented).
84+ /// The cache holds a **borrowed** pointer (no refcount increment).
85+ /// Safety: `type_cache_clear()` nullifies all entries during GC,
86+ /// and `type_cache_clear_version()` nullifies entries when a type
87+ /// is modified — both before the source dict entry is removed.
88+ /// Types are always part of reference cycles (via `mro` self-reference)
89+ /// so they are always collected by the cyclic GC (never refcount-freed).
8590 value : AtomicPtr < PyObject > ,
8691}
8792
@@ -149,13 +154,11 @@ impl TypeCacheEntry {
149154 self . sequence . load ( Ordering :: Relaxed ) == previous
150155 }
151156
152- /// Take the value out of this entry, returning the owned PyObjectRef .
157+ /// Null out the cached value pointer .
153158 /// Caller must ensure no concurrent reads can observe this entry
154159 /// (version should be set to 0 first).
155- fn take_value ( & self ) -> Option < PyObjectRef > {
156- let ptr = self . value . swap ( core:: ptr:: null_mut ( ) , Ordering :: Relaxed ) ;
157- // SAFETY: non-null ptr was stored via PyObjectRef::into_raw
158- NonNull :: new ( ptr) . map ( |nn| unsafe { PyObjectRef :: from_raw ( nn) } )
160+ fn clear_value ( & self ) {
161+ self . value . store ( core:: ptr:: null_mut ( ) , Ordering :: Relaxed ) ;
159162 }
160163}
161164
@@ -180,45 +183,36 @@ fn type_cache_hash(version: u32, name: &'static PyStrInterned) -> usize {
180183 ( ( version ^ name_hash) as usize ) & TYPE_CACHE_MASK
181184}
182185
183- /// Invalidate cache entries for a specific version tag and release values .
186+ /// Invalidate cache entries for a specific version tag.
184187/// Called from modified() when a type is changed.
185188fn type_cache_clear_version ( version : u32 ) {
186- let mut to_drop = Vec :: new ( ) ;
187189 for entry in TYPE_CACHE . iter ( ) {
188190 if entry. version . load ( Ordering :: Relaxed ) == version {
189191 entry. begin_write ( ) ;
190192 if entry. version . load ( Ordering :: Relaxed ) == version {
191193 entry. version . store ( 0 , Ordering :: Release ) ;
192- if let Some ( v) = entry. take_value ( ) {
193- to_drop. push ( v) ;
194- }
194+ entry. clear_value ( ) ;
195195 }
196196 entry. end_write ( ) ;
197197 }
198198 }
199- drop ( to_drop) ;
200199}
201200
202201/// Clear all method cache entries (_PyType_ClearCache).
203- /// Called during GC collection to release strong references that might
204- /// prevent cycle collection .
202+ /// Called during GC collection to nullify borrowed pointers before
203+ /// the collector breaks cycles .
205204///
206205/// Sets TYPE_CACHE_CLEARING to suppress cache re-population during the
207206/// entire operation, preventing concurrent lookups from repopulating
208207/// entries while we're clearing them.
209208pub fn type_cache_clear ( ) {
210209 TYPE_CACHE_CLEARING . store ( true , Ordering :: Release ) ;
211- // Invalidate all entries and collect values.
212- let mut to_drop = Vec :: new ( ) ;
213210 for entry in TYPE_CACHE . iter ( ) {
214211 entry. begin_write ( ) ;
215212 entry. version . store ( 0 , Ordering :: Release ) ;
216- if let Some ( v) = entry. take_value ( ) {
217- to_drop. push ( v) ;
218- }
213+ entry. clear_value ( ) ;
219214 entry. end_write ( ) ;
220215 }
221- drop ( to_drop) ;
222216 TYPE_CACHE_CLEARING . store ( false , Ordering :: Release ) ;
223217}
224218
@@ -430,9 +424,8 @@ impl PyType {
430424 return ;
431425 }
432426 self . tp_version_tag . store ( 0 , Ordering :: SeqCst ) ;
433- // Release strong references held by cache entries for this version.
434- // We hold owned refs that would prevent GC of class attributes after
435- // type deletion.
427+ // Nullify borrowed pointers in cache entries for this version
428+ // so they don't dangle after the dict is modified.
436429 type_cache_clear_version ( old_version) ;
437430 let subclasses = self . subclasses . read ( ) ;
438431 for weak_ref in subclasses. iter ( ) {
@@ -809,9 +802,9 @@ impl PyType {
809802 }
810803
811804 pub fn set_attr ( & self , attr_name : & ' static PyStrInterned , value : PyObjectRef ) {
812- // Invalidate caches BEFORE modifying attributes so that cached
813- // descriptor pointers are still alive when type_cache_clear_version
814- // drops the cache's strong references .
805+ // Invalidate caches BEFORE modifying attributes so that borrowed
806+ // pointers in cache entries are nullified while the source objects
807+ // are still alive .
815808 self . modified ( ) ;
816809 self . attributes . write ( ) . insert ( attr_name, value) ;
817810 }
@@ -974,19 +967,13 @@ impl PyType {
974967 entry. begin_write ( ) ;
975968 // Invalidate first to prevent readers from seeing partial state
976969 entry. version . store ( 0 , Ordering :: Release ) ;
977- // Swap in new value ( refcount held by cache)
978- let new_ptr = found. clone ( ) . into_raw ( ) . as_ptr ( ) ;
979- let old_ptr = entry. value . swap ( new_ptr, Ordering :: Relaxed ) ;
970+ // Store borrowed pointer (no refcount increment).
971+ let new_ptr = & * * found as * const PyObject as * mut PyObject ;
972+ entry. value . store ( new_ptr, Ordering :: Relaxed ) ;
980973 entry. name . store ( name_ptr, Ordering :: Relaxed ) ;
981974 // Activate entry — Release ensures value/name writes are visible
982975 entry. version . store ( assigned, Ordering :: Release ) ;
983976 entry. end_write ( ) ;
984- // Drop previous occupant (its version was already invalidated)
985- if !old_ptr. is_null ( ) {
986- unsafe {
987- drop ( PyObjectRef :: from_raw ( NonNull :: new_unchecked ( old_ptr) ) ) ;
988- }
989- }
990977 }
991978
992979 result
0 commit comments