@@ -271,15 +271,6 @@ where
271
271
/// with different values.
272
272
durability : Durability ,
273
273
274
- /// The revision in which the tracked struct was first created.
275
- ///
276
- /// Unlike `updated_at`, which gets bumped on every read,
277
- /// `created_at` is updated whenever an untracked field is updated.
278
- /// This is necessary to detect reused tracked struct ids _after_
279
- /// they've been freed in a prior revision or tracked structs that have been updated
280
- /// in-place because of a bad `Hash` implementation.
281
- created_at : Revision ,
282
-
283
274
/// The revision when this tracked struct was last updated.
284
275
/// This field also acts as a kind of "lock". Once it is equal
285
276
/// to `Some(current_revision)`, the fields are locked and
@@ -400,7 +391,7 @@ where
400
391
tracing:: trace!( "Reuse tracked struct {id:?}" , id = index) ;
401
392
// The struct already exists in the intern map.
402
393
zalsa_local. add_output ( index) ;
403
- self . update ( zalsa, current_revision, id, & current_deps, fields) ;
394
+ let id = self . update ( zalsa, current_revision, id, & current_deps, fields) ;
404
395
FromId :: from_id ( id)
405
396
}
406
397
@@ -425,7 +416,6 @@ where
425
416
fields : C :: Fields < ' db > ,
426
417
) -> Id {
427
418
let value = |_| Value {
428
- created_at : current_revision,
429
419
updated_at : OptionalAtomicRevision :: new ( Some ( current_revision) ) ,
430
420
durability : current_deps. durability ,
431
421
// lifetime erase for storage
@@ -434,17 +424,11 @@ where
434
424
memos : Default :: default ( ) ,
435
425
} ;
436
426
437
- while let Some ( id) = self . free_list . pop ( ) {
427
+ if let Some ( id) = self . free_list . pop ( ) {
438
428
// Increment the ID generation before reusing it, as if we have allocated a new
439
429
// slot in the table.
440
- //
441
- // If the generation will wrap, we are forced to leak the slot. We reserve enough
442
- // bits for the generation that this should not be a problem in practice.
443
- let Some ( generation) = id. generation ( ) . checked_add ( 1 ) else {
444
- continue ;
445
- } ;
446
-
447
- let id = id. with_generation ( generation) ;
430
+ let id = id. with_generation ( id. generation ( ) + 1 ) ;
431
+
448
432
return Self :: data_raw ( zalsa. table ( ) , id) . with_mut ( |data_raw| {
449
433
let data_raw = data_raw. cast :: < Value < C > > ( ) ;
450
434
@@ -477,10 +461,10 @@ where
477
461
& ' db self ,
478
462
zalsa : & ' db Zalsa ,
479
463
current_revision : Revision ,
480
- id : Id ,
464
+ mut id : Id ,
481
465
current_deps : & StampedValue < ( ) > ,
482
466
fields : C :: Fields < ' db > ,
483
- ) {
467
+ ) -> Id {
484
468
let data_raw = Self :: data_raw ( zalsa. table ( ) , id) ;
485
469
486
470
// The protocol is:
@@ -547,7 +531,7 @@ where
547
531
} ) ;
548
532
549
533
if !locked {
550
- return ;
534
+ return id ;
551
535
}
552
536
553
537
data_raw. with_mut ( |data| {
@@ -570,22 +554,27 @@ where
570
554
) ,
571
555
fields,
572
556
) {
573
- // Consider this a new tracked-struct (even though it still uses the same id)
574
- // when any non-tracked field got updated.
575
- // This should be rare and only ever happen if there's a hash collision
576
- // which makes Salsa consider two tracked structs to still be the same
577
- // even though the fields are different.
578
- // See `tracked-struct-id-field-bad-hash` for more details.
579
- data. created_at = current_revision;
557
+ // Consider this a new tracked-struct when any non-tracked field got updated.
558
+ // This should be rare and only ever happen if there's a hash collision.
559
+ //
560
+ // Note that we hold the lock and have exclusive access to the tracked struct data,
561
+ // so there should be no live instances of IDs from the previous generation. We clear
562
+ // the memos and return a new ID here as if we have allocated a new slot.
563
+
564
+ // SAFETY: We already set `updated_at` to `None` to acquire the lock.
565
+ self . delete_entity ( zalsa, id, true ) ;
566
+
567
+ id = id. with_generation ( id. generation ( ) + 1 ) ;
580
568
}
581
569
}
582
570
if current_deps. durability < data. durability {
583
571
data. revisions = C :: new_revisions ( current_deps. changed_at ) ;
584
- data. created_at = current_revision;
585
572
}
586
573
data. durability = current_deps. durability ;
587
574
let swapped_out = data. updated_at . swap ( Some ( current_revision) ) ;
588
575
assert ! ( swapped_out. is_none( ) ) ;
576
+
577
+ id
589
578
} )
590
579
}
591
580
@@ -609,7 +598,11 @@ where
609
598
/// Using this method on an entity id that MAY be used in the current revision will lead to
610
599
/// unspecified results (but not UB). See [`InternedIngredient::delete_index`] for more
611
600
/// discussion and important considerations.
612
- pub ( crate ) fn delete_entity ( & self , zalsa : & Zalsa , id : Id ) {
601
+ ///
602
+ /// # Safety
603
+ ///
604
+ /// If `locked == true` is passed, the `updated_at` field must already be set to `None`.
605
+ pub ( crate ) unsafe fn delete_entity ( & self , zalsa : & Zalsa , id : Id , locked : bool ) {
613
606
zalsa. event ( & || {
614
607
Event :: new ( crate :: EventKind :: DidDiscard {
615
608
key : self . database_key_index ( id) ,
@@ -619,7 +612,8 @@ where
619
612
let current_revision = zalsa. current_revision ( ) ;
620
613
let data_raw = Self :: data_raw ( zalsa. table ( ) , id) ;
621
614
622
- data_raw. with ( |data| {
615
+ if !locked {
616
+ data_raw. with ( |data| {
623
617
let data = data. cast :: < Value < C > > ( ) ;
624
618
625
619
// We want to set `updated_at` to `None`, signalling that other field values
@@ -639,6 +633,7 @@ where
639
633
}
640
634
}
641
635
} ) ;
636
+ }
642
637
643
638
let mut memo_table = data_raw. with_mut ( |data| {
644
639
// SAFETY: We have acquired the write lock
@@ -767,15 +762,13 @@ where
767
762
768
763
unsafe fn maybe_changed_after (
769
764
& self ,
770
- db : & dyn Database ,
771
- input : Id ,
772
- revision : Revision ,
765
+ _db : & dyn Database ,
766
+ _input : Id ,
767
+ _revision : Revision ,
773
768
_cycle_heads : & mut CycleHeads ,
774
769
) -> VerifyResult {
775
- let zalsa = db. zalsa ( ) ;
776
- let data = Self :: data ( zalsa. table ( ) , input) ;
777
-
778
- VerifyResult :: changed_if ( data. created_at > revision)
770
+ // Any change to a tracked struct results in a new ID generation.
771
+ VerifyResult :: unchanged ( )
779
772
}
780
773
781
774
fn mark_validated_output (
@@ -799,7 +792,9 @@ where
799
792
// `executor` creates a tracked struct `salsa_output_key`,
800
793
// but it did not in the current revision.
801
794
// In that case, we can delete `stale_output_key` and any data associated with it.
802
- self . delete_entity ( zalsa, stale_output_key) ;
795
+ //
796
+ // SAFETY: `delete_entity` is always sound to call with `locked == false`.
797
+ unsafe { self . delete_entity ( zalsa, stale_output_key, false ) } ;
803
798
}
804
799
805
800
fn debug_name ( & self ) -> & ' static str {
0 commit comments