@@ -18,23 +18,20 @@ use std::{
1818/// meant to be used in a single-threaded context, it's safe to share and copy
1919#[ derive( Debug , Clone , Copy , Eq , PartialEq ) ]
2020pub ( crate ) struct TzPtr {
21- inner : NonNull < Inner > ,
21+ inner : NonNull < TzRefCounted > ,
2222}
2323
2424#[ derive( Debug ) ]
25- struct Inner {
25+ struct TzRefCounted {
2626 value : TimeZone ,
2727 refcnt : AtomicRefCount ,
2828}
2929
3030impl TzPtr {
3131 /// Creates a new instance with specified refcount.
32- fn new ( value : TimeZone ) -> Self {
33- let inner = Box :: new ( Inner {
34- // We start with refcount of 2:
35- // - one to share outside of the tzstore
36- // - one to keep the pointer alive in the LRU
37- refcnt : AtomicRefCount :: new ( 2 ) ,
32+ fn new ( value : TimeZone , refcount : usize ) -> Self {
33+ let inner = Box :: new ( TzRefCounted {
34+ refcnt : AtomicRefCount :: new ( refcount) ,
3835 value,
3936 } ) ;
4037 Self {
@@ -210,7 +207,10 @@ impl Cache {
210207 }
211208
212209 // Cache miss: load outside the lock (may do file I/O)
213- let loaded = TzPtr :: new ( load ( ) ?) ;
210+ // We start with refcount of 2:
211+ // - one to return to the caller
212+ // - one to keep the pointer alive in the LRU
213+ let loaded = TzPtr :: new ( load ( ) ?, 2 ) ;
214214
215215 // Re-acquire lock to insert. Another thread may have loaded the same key.
216216 self . inner . with_mut ( |CacheInner { lookup, lru } | {
@@ -333,6 +333,10 @@ impl Drop for Cache {
333333 // the only strong references are in the LRU.
334334 let mut lru = std:: mem:: take ( lru) ;
335335 for tz in lru. drain ( ..) {
336+ debug_assert ! (
337+ tz. ref_count( ) == 1 ,
338+ "Dropping cache with strong references still present"
339+ ) ;
336340 Self :: decref ( tz, || {
337341 // Remove the weak reference too
338342 lookup. remove ( tz. key . as_ref ( ) . expect ( "LRU entries always have a key" ) ) ;
@@ -387,7 +391,7 @@ pub(crate) struct TzStore {
387391 // We cache the system timezone here, since it's expensive to determine.
388392 // Uses SwapPtr for lock-free reads with atomic swaps for writes.
389393 // This holds a strong reference to a TzPtr.
390- system_tz_cache : SwapPtr < Inner > ,
394+ system_tz_cache : SwapPtr < TzRefCounted > ,
391395 // This reference is borrowed from the module, which outlives this store.
392396 exc_notfound : PyObj ,
393397}
@@ -503,7 +507,7 @@ impl TzStore {
503507 }
504508
505509 /// Get a pointer to what is currently considered the system timezone.
506- /// The pointer is already a strong reference.
510+ /// The pointer represents a strong reference.
507511 fn determine_system_tz ( & self ) -> PyResult < TzPtr > {
508512 const ERR_MSG : & str = "get_tz() gave unexpected result" ;
509513 let tz_tuple = import ( c"whenever._tz.system" ) ?
@@ -540,15 +544,15 @@ impl TzStore {
540544 . ok_or_else_raise ( self . exc_notfound . as_ptr ( ) , || {
541545 format ! ( "No time zone found at path {path:?}" )
542546 } ) ?;
543- Ok ( TzPtr :: new ( tzif) )
547+ Ok ( TzPtr :: new ( tzif, 1 ) )
544548 }
545549 // type 2: zoneinfo key OR posix TZ string (we're unsure which)
546550 2 => {
547551 self . cache
548552 // Try to load it as a zoneinfo key first.
549553 . get_or_insert_with ( tz_value, || self . load_tzif ( tz_value) )
550554 // If this fails, try to parse it as a posix TZ string.
551- . or_else ( || TimeZone :: parse_posix ( tz_value) . map ( TzPtr :: new) )
555+ . or_else ( || TimeZone :: parse_posix ( tz_value) . map ( |tz| TzPtr :: new ( tz , 1 ) ) )
552556 . ok_or_else_raise ( self . exc_notfound . as_ptr ( ) , || {
553557 format ! ( "No time zone found with key or posix TZ string {tz_value}" )
554558 } )
@@ -567,6 +571,20 @@ impl Drop for TzStore {
567571 // Clear the system timezone cache
568572 if let Some ( inner) = self . system_tz_cache . swap ( None ) {
569573 let ptr = TzPtr { inner } ;
574+ // When dropping, ensure the refcount is what we expect.
575+ #[ cfg( debug_assertions) ]
576+ {
577+ let mut expect_refcnt = 1 ; // only held by system_tz_cache
578+ self . cache . inner . with_mut ( |CacheInner { lru, .. } | {
579+ if lru. contains ( & ptr) {
580+ expect_refcnt += 1 ; // also held by LRU
581+ }
582+ } ) ;
583+ assert ! (
584+ ptr. ref_count( ) == expect_refcnt,
585+ "Dangling references to system timezone on TzStore drop"
586+ ) ;
587+ }
570588 ptr. decref_with_cleanup ( || self ) ;
571589 }
572590 // The rest of the fields will be dropped automatically
0 commit comments