@@ -160,7 +160,7 @@ where
160160 ingredient_index : IngredientIndex ,
161161
162162 /// Phantom data: we fetch `Value<C>` out from `Table`
163- phantom : PhantomData < fn ( ) -> Value < C > > ,
163+ phantom : PhantomData < fn ( ) -> ValueWithLock < C > > ,
164164
165165 /// Store freed ids
166166 free_list : SegQueue < Id > ,
@@ -255,26 +255,10 @@ impl IdentityMap {
255255}
256256
257257// ANCHOR: ValueStruct
258- #[ derive( Debug ) ]
259- pub struct Value < C >
258+ pub struct ValueWithLock < C >
260259where
261260 C : Configuration ,
262261{
263- /// The durability minimum durability of all inputs consumed
264- /// by the creator query prior to creating this tracked struct.
265- /// If any of those inputs changes, then the creator query may
266- /// create this struct with different values.
267- durability : Durability ,
268-
269- /// The revision in which the tracked struct was first created.
270- ///
271- /// Unlike `updated_at`, which gets bumped on every read,
272- /// `created_at` is updated whenever an untracked field is updated.
273- /// This is necessary to detect reused tracked struct ids _after_
274- /// they've been freed in a prior revision or tracked structs that have been updated
275- /// in-place because of a bad `Hash` implementation.
276- created_at : Revision ,
277-
278262 /// The revision when this tracked struct was last updated.
279263 /// This field also acts as a kind of "lock". Once it is equal
280264 /// to `Some(current_revision)`, the fields are locked and
@@ -295,6 +279,27 @@ where
295279 /// This `None` value should never be observable by users unless they have
296280 /// leaked a reference across threads somehow.
297281 updated_at : OptionalAtomicRevision ,
282+ value : Value < C > ,
283+ }
284+
285+ pub struct Value < C >
286+ where
287+ C : Configuration ,
288+ {
289+ /// The durability minimum durability of all inputs consumed
290+ /// by the creator query prior to creating this tracked struct.
291+ /// If any of those inputs changes, then the creator query may
292+ /// create this struct with different values.
293+ durability : Durability ,
294+
295+ /// The revision in which the tracked struct was first created.
296+ ///
297+ /// Unlike `updated_at`, which gets bumped on every read,
298+ /// `created_at` is updated whenever an untracked field is updated.
299+ /// This is necessary to detect reused tracked struct ids _after_
300+ /// they've been freed in a prior revision or tracked structs that have been updated
301+ /// in-place because of a bad `Hash` implementation.
302+ created_at : Revision ,
298303
299304 /// Fields of this tracked struct. They can change across revisions,
300305 /// but they do not change within a particular revision.
@@ -427,14 +432,16 @@ where
427432 fields : C :: Fields < ' db > ,
428433 ) -> Id {
429434 let current_revision = zalsa. current_revision ( ) ;
430- let value = |_| Value {
431- created_at : current_revision,
435+ let value = |_| ValueWithLock {
432436 updated_at : OptionalAtomicRevision :: new ( Some ( current_revision) ) ,
433- durability : current_deps. durability ,
434- fields : unsafe { self . to_static ( fields) } ,
435- revisions : C :: new_revisions ( current_deps. changed_at ) ,
436- memos : Default :: default ( ) ,
437- syncs : Default :: default ( ) ,
437+ value : Value {
438+ created_at : current_revision,
439+ durability : current_deps. durability ,
440+ fields : unsafe { self . to_static ( fields) } ,
441+ revisions : C :: new_revisions ( current_deps. changed_at ) ,
442+ memos : Default :: default ( ) ,
443+ syncs : Default :: default ( ) ,
444+ } ,
438445 } ;
439446
440447 if let Some ( id) = self . free_list . pop ( ) {
@@ -450,7 +457,7 @@ where
450457
451458 id
452459 } else {
453- zalsa_local. allocate :: < Value < C > > ( zalsa. table ( ) , self . ingredient_index , value)
460+ zalsa_local. allocate :: < ValueWithLock < C > > ( zalsa. table ( ) , self . ingredient_index , value)
454461 }
455462 }
456463
@@ -484,8 +491,9 @@ where
484491 // In that case we should not modify or touch it because there may be
485492 // `&`-references to its contents floating around.
486493 //
487- // Observing `Some(current_revision)` can happen in two scenarios: leaks (tsk tsk)
488- // but also the scenario embodied by the test test `test_run_5_then_20` in `specify_tracked_fn_in_rev_1_but_not_2.rs`:
494+ // Observing `Some(current_revision)` can happen in two scenarios:
495+ // - leaks, see tests\preverify-struct-with-leaked-data.rs and tests\preverify-struct-with-leaked-data-2.rs
496+ // - or the following scenario (FIXME verify this? There is no test that covers this behavior):
489497 //
490498 // * Revision 1:
491499 // * Tracked function F creates tracked struct S
@@ -505,9 +513,6 @@ where
505513 // that is still live.
506514
507515 let current_revision = zalsa. current_revision ( ) ;
508- // UNSAFE: Marking as mut requires exclusive access for the duration of
509- // the `mut`. We have now *claimed* this data by swapping in `None`,
510- // any attempt to read concurrently will panic.
511516 let last_updated_at = unsafe { ( * data_raw) . updated_at . load ( ) } ;
512517 assert ! (
513518 last_updated_at. is_some( ) ,
@@ -528,39 +533,36 @@ where
528533 "failed to acquire write lock, id `{id:?}` must have been leaked across threads"
529534 ) ;
530535 }
531-
532536 // SAFETY: Marking as mut requires exclusive access for the duration of
533537 // the `mut`. We have now *claimed* this data by swapping in `None`,
534- // any attempt to read concurrently will panic. Note that we cannot create
535- // a `&mut` reference to the full `Value` though because
536- // another thread may access `updated_at` concurrently.
538+ // any attempt to read concurrently will panic.
539+ let data = unsafe { & mut ( * data_raw) . value } ;
537540
538541 // SAFETY: We assert that the pointer to `data.revisions`
539542 // is a pointer into the database referencing a value
540543 // from a previous revision. As such, it continues to meet
541544 // its validity invariant and any owned content also continues
542545 // to meet its safety invariant.
543- unsafe {
544- if C :: update_fields (
546+ let updated = unsafe {
547+ C :: update_fields (
545548 current_revision,
546- & mut ( * data_raw ) . revisions ,
547- self . to_self_ptr ( std:: ptr:: addr_of_mut!( ( * data_raw ) . fields) ) ,
549+ & mut data . revisions ,
550+ self . to_self_ptr ( std:: ptr:: addr_of_mut!( data . fields) ) ,
548551 fields,
549- ) {
550- // Consider this a new tracked-struct (even though it still uses the same id)
551- // when any non-tracked field got updated.
552- // This should be rare and only ever happen if there's a hash collision
553- // which makes Salsa consider two tracked structs to still be the same
554- // even though the fields are different.
555- // See `tracked-struct-id-field-bad-hash` for more details.
556- ( * data_raw) . revisions = C :: new_revisions ( current_revision) ;
557- ( * data_raw) . created_at = current_revision;
558- } else if current_deps. durability < ( * data_raw) . durability {
559- ( * data_raw) . revisions = C :: new_revisions ( current_revision) ;
560- ( * data_raw) . created_at = current_revision;
561- }
562- ( * data_raw) . durability = current_deps. durability ;
552+ )
553+ } ;
554+ if updated || current_deps. durability < data. durability {
555+ // If `updated`, consider this a new tracked-struct (even though it still uses the same id)
556+ // when any non-tracked field got updated.
557+ // This should be rare and only ever happen if there's a hash collision
558+ // which makes Salsa consider two tracked structs to still be the same
559+ // even though the fields are different.
560+ // See `tracked-struct-id-field-bad-hash` for more details.
561+ data. revisions = C :: new_revisions ( current_revision) ;
562+ data. created_at = current_revision;
563563 }
564+ data. durability = current_deps. durability ;
565+ // release the lock
564566 let swapped_out = unsafe { ( * data_raw) . updated_at . swap_mut ( Some ( current_revision) ) } ;
565567 assert ! (
566568 swapped_out. is_none( ) ,
@@ -574,10 +576,10 @@ where
574576 let val = Self :: data_raw ( table, id) ;
575577 acquire_read_lock ( unsafe { & ( * val) . updated_at } , current_revision) ;
576578 // We have acquired the read lock, so it is safe to return a reference to the data.
577- unsafe { & * val }
579+ unsafe { & ( * val) . value }
578580 }
579581
580- fn data_raw ( table : & Table , id : Id ) -> * mut Value < C > {
582+ fn data_raw ( table : & Table , id : Id ) -> * mut ValueWithLock < C > {
581583 table. get_raw ( id)
582584 }
583585
@@ -615,7 +617,7 @@ where
615617
616618 // Take the memo table. This is safe because we have modified `data_ref.updated_at` to `None`
617619 // signalling that we have acquired the write lock
618- let memo_table = std:: mem:: take ( unsafe { & mut ( * data) . memos } ) ;
620+ let memo_table = std:: mem:: take ( unsafe { & mut ( * data) . value . memos } ) ;
619621
620622 // SAFETY: We have verified that no more references to these memos exist and so we are good
621623 // to drop them.
@@ -712,12 +714,12 @@ where
712714 pub fn entries < ' db > (
713715 & ' db self ,
714716 db : & ' db dyn crate :: Database ,
715- ) -> impl Iterator < Item = & ' db Value < C > > {
717+ ) -> impl Iterator < Item = & ' db ValueWithLock < C > > {
716718 db. zalsa ( )
717719 . table ( )
718720 . pages
719721 . iter ( )
720- . filter_map ( |( _, page) | page. cast_type :: < crate :: table:: Page < Value < C > > > ( ) )
722+ . filter_map ( |( _, page) | page. cast_type :: < crate :: table:: Page < ValueWithLock < C > > > ( ) )
721723 . flat_map ( |page| page. slots ( ) )
722724 }
723725}
@@ -792,7 +794,7 @@ where
792794 }
793795}
794796
795- impl < C > Value < C >
797+ impl < C > ValueWithLock < C >
796798where
797799 C : Configuration ,
798800{
@@ -802,7 +804,7 @@ where
802804 /// a particular revision.
803805 #[ cfg( feature = "salsa_unstable" ) ]
804806 pub fn fields ( & self ) -> & C :: Fields < ' static > {
805- & self . fields
807+ & self . value . fields
806808 }
807809}
808810
@@ -826,7 +828,7 @@ fn acquire_read_lock(updated_at: &OptionalAtomicRevision, current_revision: Revi
826828 }
827829}
828830
829- impl < C > Slot for Value < C >
831+ impl < C > Slot for ValueWithLock < C >
830832where
831833 C : Configuration ,
832834{
@@ -836,11 +838,11 @@ where
836838 // ensures that there is no danger of a race
837839 // when deleting a tracked struct.
838840 acquire_read_lock ( & self . updated_at , current_revision) ;
839- & self . memos
841+ & self . value . memos
840842 }
841843
842844 fn memos_mut ( & mut self ) -> & mut crate :: table:: memo:: MemoTable {
843- & mut self . memos
845+ & mut self . value . memos
844846 }
845847
846848 // FIXME: `&self` may alias here?
@@ -849,7 +851,7 @@ where
849851 // ensures that there is no danger of a race
850852 // when deleting a tracked struct.
851853 acquire_read_lock ( & self . updated_at , current_revision) ;
852- & self . syncs
854+ & self . value . syncs
853855 }
854856}
855857
0 commit comments