@@ -160,7 +160,7 @@ where
160
160
ingredient_index : IngredientIndex ,
161
161
162
162
/// Phantom data: we fetch `Value<C>` out from `Table`
163
- phantom : PhantomData < fn ( ) -> Value < C > > ,
163
+ phantom : PhantomData < fn ( ) -> ValueWithLock < C > > ,
164
164
165
165
/// Store freed ids
166
166
free_list : SegQueue < Id > ,
@@ -255,26 +255,10 @@ impl IdentityMap {
255
255
}
256
256
257
257
// ANCHOR: ValueStruct
258
- #[ derive( Debug ) ]
259
- pub struct Value < C >
258
+ pub struct ValueWithLock < C >
260
259
where
261
260
C : Configuration ,
262
261
{
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
-
278
262
/// The revision when this tracked struct was last updated.
279
263
/// This field also acts as a kind of "lock". Once it is equal
280
264
/// to `Some(current_revision)`, the fields are locked and
@@ -295,6 +279,27 @@ where
295
279
/// This `None` value should never be observable by users unless they have
296
280
/// leaked a reference across threads somehow.
297
281
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 ,
298
303
299
304
/// Fields of this tracked struct. They can change across revisions,
300
305
/// but they do not change within a particular revision.
@@ -427,14 +432,16 @@ where
427
432
fields : C :: Fields < ' db > ,
428
433
) -> Id {
429
434
let current_revision = zalsa. current_revision ( ) ;
430
- let value = |_| Value {
431
- created_at : current_revision,
435
+ let value = |_| ValueWithLock {
432
436
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
+ } ,
438
445
} ;
439
446
440
447
if let Some ( id) = self . free_list . pop ( ) {
@@ -450,7 +457,7 @@ where
450
457
451
458
id
452
459
} 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)
454
461
}
455
462
}
456
463
@@ -484,8 +491,9 @@ where
484
491
// In that case we should not modify or touch it because there may be
485
492
// `&`-references to its contents floating around.
486
493
//
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):
489
497
//
490
498
// * Revision 1:
491
499
// * Tracked function F creates tracked struct S
@@ -505,9 +513,6 @@ where
505
513
// that is still live.
506
514
507
515
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.
511
516
let last_updated_at = unsafe { ( * data_raw) . updated_at . load ( ) } ;
512
517
assert ! (
513
518
last_updated_at. is_some( ) ,
@@ -528,39 +533,36 @@ where
528
533
"failed to acquire write lock, id `{id:?}` must have been leaked across threads"
529
534
) ;
530
535
}
531
-
532
536
// SAFETY: Marking as mut requires exclusive access for the duration of
533
537
// 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 } ;
537
540
538
541
// SAFETY: We assert that the pointer to `data.revisions`
539
542
// is a pointer into the database referencing a value
540
543
// from a previous revision. As such, it continues to meet
541
544
// its validity invariant and any owned content also continues
542
545
// to meet its safety invariant.
543
- unsafe {
544
- if C :: update_fields (
546
+ let updated = unsafe {
547
+ C :: update_fields (
545
548
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) ) ,
548
551
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;
563
563
}
564
+ data. durability = current_deps. durability ;
565
+ // release the lock
564
566
let swapped_out = unsafe { ( * data_raw) . updated_at . swap_mut ( Some ( current_revision) ) } ;
565
567
assert ! (
566
568
swapped_out. is_none( ) ,
@@ -574,10 +576,10 @@ where
574
576
let val = Self :: data_raw ( table, id) ;
575
577
acquire_read_lock ( unsafe { & ( * val) . updated_at } , current_revision) ;
576
578
// We have acquired the read lock, so it is safe to return a reference to the data.
577
- unsafe { & * val }
579
+ unsafe { & ( * val) . value }
578
580
}
579
581
580
- fn data_raw ( table : & Table , id : Id ) -> * mut Value < C > {
582
+ fn data_raw ( table : & Table , id : Id ) -> * mut ValueWithLock < C > {
581
583
table. get_raw ( id)
582
584
}
583
585
@@ -615,7 +617,7 @@ where
615
617
616
618
// Take the memo table. This is safe because we have modified `data_ref.updated_at` to `None`
617
619
// 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 } ) ;
619
621
620
622
// SAFETY: We have verified that no more references to these memos exist and so we are good
621
623
// to drop them.
@@ -712,12 +714,12 @@ where
712
714
pub fn entries < ' db > (
713
715
& ' db self ,
714
716
db : & ' db dyn crate :: Database ,
715
- ) -> impl Iterator < Item = & ' db Value < C > > {
717
+ ) -> impl Iterator < Item = & ' db ValueWithLock < C > > {
716
718
db. zalsa ( )
717
719
. table ( )
718
720
. pages
719
721
. 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 > > > ( ) )
721
723
. flat_map ( |page| page. slots ( ) )
722
724
}
723
725
}
@@ -792,7 +794,7 @@ where
792
794
}
793
795
}
794
796
795
- impl < C > Value < C >
797
+ impl < C > ValueWithLock < C >
796
798
where
797
799
C : Configuration ,
798
800
{
@@ -802,7 +804,7 @@ where
802
804
/// a particular revision.
803
805
#[ cfg( feature = "salsa_unstable" ) ]
804
806
pub fn fields ( & self ) -> & C :: Fields < ' static > {
805
- & self . fields
807
+ & self . value . fields
806
808
}
807
809
}
808
810
@@ -826,7 +828,7 @@ fn acquire_read_lock(updated_at: &OptionalAtomicRevision, current_revision: Revi
826
828
}
827
829
}
828
830
829
- impl < C > Slot for Value < C >
831
+ impl < C > Slot for ValueWithLock < C >
830
832
where
831
833
C : Configuration ,
832
834
{
@@ -836,11 +838,11 @@ where
836
838
// ensures that there is no danger of a race
837
839
// when deleting a tracked struct.
838
840
acquire_read_lock ( & self . updated_at , current_revision) ;
839
- & self . memos
841
+ & self . value . memos
840
842
}
841
843
842
844
fn memos_mut ( & mut self ) -> & mut crate :: table:: memo:: MemoTable {
843
- & mut self . memos
845
+ & mut self . value . memos
844
846
}
845
847
846
848
// FIXME: `&self` may alias here?
@@ -849,7 +851,7 @@ where
849
851
// ensures that there is no danger of a race
850
852
// when deleting a tracked struct.
851
853
acquire_read_lock ( & self . updated_at , current_revision) ;
852
- & self . syncs
854
+ & self . value . syncs
853
855
}
854
856
}
855
857
0 commit comments