@@ -3,6 +3,7 @@ use std::cell::{Cell, UnsafeCell};
33use std:: fmt;
44use std:: hash:: { BuildHasher , Hash , Hasher } ;
55use std:: marker:: PhantomData ;
6+ use std:: num:: NonZeroUsize ;
67use std:: path:: { Path , PathBuf } ;
78
89use crossbeam_utils:: CachePadded ;
@@ -31,6 +32,13 @@ pub trait Configuration: Sized + 'static {
3132
3233 const DEBUG_NAME : & ' static str ;
3334
35+ // The minimum number of revisions that must pass before a stale value is garbage collected.
36+ #[ cfg( test) ]
37+ const REVISIONS : NonZeroUsize = NonZeroUsize :: new ( 3 ) . unwrap ( ) ;
38+
39+ #[ cfg( not( test) ) ] // More aggressive garbage collection by default when testing.
40+ const REVISIONS : NonZeroUsize = NonZeroUsize :: new ( 1 ) . unwrap ( ) ;
41+
3442 /// The fields of the struct being interned.
3543 type Fields < ' db > : InternedData ;
3644
@@ -63,7 +71,7 @@ pub struct IngredientImpl<C: Configuration> {
6371 shards : Box < [ CachePadded < Mutex < IngredientShard < C > > > ] > ,
6472
6573 /// A queue of recent revisions in which values were interned.
66- revision_queue : RevisionQueue ,
74+ revision_queue : RevisionQueue < C > ,
6775
6876 memo_table_types : Arc < MemoTableTypes > ,
6977
@@ -158,7 +166,12 @@ struct ValueShared {
158166
159167impl ValueShared {
160168 /// Returns `true` if this value slot can be reused when interning, and should be added to the LRU.
161- fn is_reusable ( & self ) -> bool {
169+ fn is_reusable < C : Configuration > ( & self ) -> bool {
170+ // Garbage collection is disabled.
171+ if C :: REVISIONS == IMMORTAL {
172+ return false ;
173+ }
174+
162175 // Collecting higher durability values requires invalidating the revision for their
163176 // durability (see `Database::synthetic_write`, which requires a mutable reference to
164177 // the database) to avoid short-circuiting calls to `maybe_changed_after`. This is
@@ -337,7 +350,7 @@ where
337350 } )
338351 } ) ;
339352
340- if value_shared. is_reusable ( ) {
353+ if value_shared. is_reusable :: < C > ( ) {
341354 // Move the value to the front of the LRU list.
342355 //
343356 // SAFETY: We hold the lock for the shard containing the value, and `value` is
@@ -351,14 +364,14 @@ where
351364 }
352365
353366 if let Some ( ( _, stamp) ) = zalsa_local. active_query ( ) {
354- let was_reusable = value_shared. is_reusable ( ) ;
367+ let was_reusable = value_shared. is_reusable :: < C > ( ) ;
355368
356369 // Record the maximum durability across all queries that intern this value.
357370 value_shared. durability = std:: cmp:: max ( value_shared. durability , stamp. durability ) ;
358371
359372 // If the value is no longer reusable, i.e. the durability increased, remove it
360373 // from the LRU.
361- if was_reusable && !value_shared. is_reusable ( ) {
374+ if was_reusable && !value_shared. is_reusable :: < C > ( ) {
362375 // SAFETY: We hold the lock for the shard containing the value, and `value`
363376 // was previously reusable, so is in the list.
364377 unsafe { shard. lru . cursor_mut_from_ptr ( value) . remove ( ) } ;
@@ -507,7 +520,7 @@ where
507520 // correct type.
508521 unsafe { self . clear_memos ( zalsa, & mut memo_table, new_id) } ;
509522
510- if value_shared. is_reusable ( ) {
523+ if value_shared. is_reusable :: < C > ( ) {
511524 // Move the value to the front of the LRU list.
512525 //
513526 // SAFETY: The value pointer is valid for the lifetime of the database.
@@ -580,7 +593,7 @@ where
580593 // SAFETY: We hold the lock for the shard containing the value.
581594 let value_shared = unsafe { & mut * value. shared . get ( ) } ;
582595
583- if value_shared. is_reusable ( ) {
596+ if value_shared. is_reusable :: < C > ( ) {
584597 // Add the value to the front of the LRU list.
585598 //
586599 // SAFETY: The value pointer is valid for the lifetime of the database
@@ -855,35 +868,48 @@ impl<Fields: Send + Sync + 'static> Slot for Value<Fields> {
855868 }
856869}
857870
858- #[ cfg( not( test) ) ]
859- const REVS : usize = 3 ;
860-
861- #[ cfg( test) ] // Aggressively reuse slots in tests.
862- const REVS : usize = 1 ;
863-
864871/// Keep track of revisions in which interned values were read, to determine staleness.
865872///
866873/// An interned value is considered stale if it has not been read in the past `REVS`
867874/// revisions. However, we only consider revisions in which interned values were actually
868875/// read, as revisions may be created in bursts.
869- struct RevisionQueue {
876+ struct RevisionQueue < C > {
870877 lock : Mutex < ( ) > ,
871- revisions : [ AtomicRevision ; REVS ] ,
878+ // Once `feature(generic_const_exprs)` is stable this can just be an array.
879+ revisions : Box < [ AtomicRevision ] > ,
880+ _configuration : PhantomData < fn ( ) -> C > ,
872881}
873882
874- impl Default for RevisionQueue {
875- fn default ( ) -> RevisionQueue {
883+ // `#[salsa::interned(revisions = usize::MAX)]` disables garbage collection.
884+ const IMMORTAL : NonZeroUsize = NonZeroUsize :: MAX ;
885+
886+ impl < C : Configuration > Default for RevisionQueue < C > {
887+ fn default ( ) -> RevisionQueue < C > {
888+ let revisions = if C :: REVISIONS == IMMORTAL {
889+ Box :: default ( )
890+ } else {
891+ ( 0 ..C :: REVISIONS . get ( ) )
892+ . map ( |_| AtomicRevision :: start ( ) )
893+ . collect ( )
894+ } ;
895+
876896 RevisionQueue {
877897 lock : Mutex :: new ( ( ) ) ,
878- revisions : [ const { AtomicRevision :: start ( ) } ; REVS ] ,
898+ revisions,
899+ _configuration : PhantomData ,
879900 }
880901 }
881902}
882903
883- impl RevisionQueue {
904+ impl < C : Configuration > RevisionQueue < C > {
884905 /// Record the given revision as active.
885906 #[ inline]
886907 fn record ( & self , revision : Revision ) {
908+ // Garbage collection is disabled.
909+ if C :: REVISIONS == IMMORTAL {
910+ return ;
911+ }
912+
887913 // Fast-path: We already recorded this revision.
888914 if self . revisions [ 0 ] . load ( ) >= revision {
889915 return ;
@@ -899,7 +925,7 @@ impl RevisionQueue {
899925 // Otherwise, update the queue, maintaining sorted order.
900926 //
901927 // Note that this should only happen once per revision.
902- for i in ( 1 ..REVS ) . rev ( ) {
928+ for i in ( 1 ..C :: REVISIONS . get ( ) ) . rev ( ) {
903929 self . revisions [ i] . store ( self . revisions [ i - 1 ] . load ( ) ) ;
904930 }
905931
@@ -909,7 +935,12 @@ impl RevisionQueue {
909935 /// Returns `true` if the given revision is old enough to be considered stale.
910936 #[ inline]
911937 fn is_stale ( & self , revision : Revision ) -> bool {
912- let oldest = self . revisions [ REVS - 1 ] . load ( ) ;
938+ // Garbage collection is disabled.
939+ if C :: REVISIONS == IMMORTAL {
940+ return false ;
941+ }
942+
943+ let oldest = self . revisions [ C :: REVISIONS . get ( ) - 1 ] . load ( ) ;
913944
914945 // If we have not recorded `REVS` revisions yet, nothing can be stale.
915946 if oldest == Revision :: start ( ) {
@@ -919,10 +950,16 @@ impl RevisionQueue {
919950 revision < oldest
920951 }
921952
922- /// Returns `true` if `REVS` revisions have been recorded as active.
953+ /// Returns `true` if `C::REVISIONS` revisions have been recorded as active,
954+ /// i.e. enough data has been recorded to start garbage collection.
923955 #[ inline]
924956 fn is_primed ( & self ) -> bool {
925- self . revisions [ REVS - 1 ] . load ( ) > Revision :: start ( )
957+ // Garbage collection is disabled.
958+ if C :: REVISIONS == IMMORTAL {
959+ return false ;
960+ }
961+
962+ self . revisions [ C :: REVISIONS . get ( ) - 1 ] . load ( ) > Revision :: start ( )
926963 }
927964}
928965
0 commit comments