Skip to content

Commit 955c53e

Browse files
committed
fix hashes for reused interned values
1 parent a232751 commit 955c53e

File tree

1 file changed

+73
-31
lines changed

1 file changed

+73
-31
lines changed

src/interned.rs

Lines changed: 73 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -103,13 +103,14 @@ where
103103
struct ValueShared {
104104
/// The interned ID for this value.
105105
///
106-
/// This is necessary to identify slots in the LRU list.
106+
/// Storing this on the value itself is necessary to identify slots
107+
/// from the LRU list.
107108
id: Id,
108109

109110
/// The revision the value was first interned in.
110111
first_interned_at: Revision,
111112

112-
/// The most recent interned revision.
113+
/// The revision the value was most-recently interned in.
113114
last_interned_at: Revision,
114115

115116
/// The minimum durability of all inputs consumed by the creator
@@ -327,8 +328,16 @@ where
327328
if let Some(value) = cursor.get() {
328329
let is_stale = value.shared.with(|value_shared| {
329330
// SAFETY: We hold the lock.
330-
let last_interned_at = unsafe { (*value_shared).last_interned_at };
331-
self.revision_queue.is_stale(last_interned_at)
331+
let value_shared = unsafe { &*value_shared };
332+
333+
// The value must not have been read in the current revision to be collected
334+
// safely, but we also do not want to collect values that have been read recently.
335+
let is_stale = self.revision_queue.is_stale(value_shared.last_interned_at);
336+
337+
// We can't collect higher durability values until we have a mutable reference to the database.
338+
let is_low_durability = value_shared.durability == Durability::LOW;
339+
340+
is_stale && is_low_durability
332341
});
333342

334343
if is_stale {
@@ -337,34 +346,20 @@ where
337346
.active_query()
338347
.map(|(_, stamp)| (stamp.durability, current_revision))
339348
// If there is no active query this durability does not actually matter.
340-
// `last_interned_at` needs to be `Revision::MAX`, see the intern_access_in_different_revision test.
349+
// `last_interned_at` needs to be `Revision::MAX`, see the `intern_access_in_different_revision` test.
341350
.unwrap_or((Durability::MAX, Revision::max()));
342351

343-
let value = value.shared.get_mut().with(|value_shared| {
352+
let id = value.shared.get_mut().with(|value_shared| {
344353
// SAFETY: We hold the lock.
345354
let value_shared = unsafe { &mut *value_shared };
346355

347356
// Mark the slot as reused.
348357
value_shared.first_interned_at = current_revision;
349358
value_shared.last_interned_at = last_interned_at;
350-
351-
// Remove the value from the LRU list.
352-
//
353-
// SAFETY: The value pointer is valid for the lifetime of the database.
354-
unsafe { &*UnsafeRef::into_raw(cursor.remove().unwrap()) }
355-
});
356-
357-
let id = value.shared.with_mut(|value_shared| {
358-
// SAFETY: We hold the lock.
359-
let value_shared = unsafe { &mut *value_shared };
360-
361-
// Note we need to retain the previous durability here to ensure queries trying
362-
// to read the old value are revalidated.
363-
value_shared.durability = std::cmp::max(value_shared.durability, durability);
364-
365-
let index = self.database_key_index(value_shared.id);
359+
value_shared.durability = durability;
366360

367361
// Record a dependency on the value.
362+
let index = self.database_key_index(value_shared.id);
368363
zalsa_local.report_tracked_read_simple(
369364
index,
370365
value_shared.durability,
@@ -381,12 +376,59 @@ where
381376
value_shared.id
382377
});
383378

379+
// Remove the value from the LRU list.
380+
//
381+
// SAFETY: The value pointer is valid for the lifetime of the database.
382+
let value = unsafe { &*UnsafeRef::into_raw(cursor.remove().unwrap()) };
383+
384384
// Reuse the value slot with the new data.
385385
//
386386
// SAFETY: We hold the lock and marked the value as reused, so any readers in the
387387
// current revision will see it is not valid.
388-
value.fields.with_mut(|fields| unsafe {
389-
*fields = self.to_internal_data(assemble(id, key));
388+
value.fields.with_mut(|old_fields| {
389+
let old_fields = unsafe { &mut *old_fields };
390+
391+
// Remove the old value from the ID map.
392+
let old_fields_ref = &*old_fields;
393+
394+
let eq = |id: &_| {
395+
let data = table.get::<Value<C>>(*id);
396+
397+
data.fields.with(|fields| {
398+
// SAFETY: We hold the lock.
399+
let fields = unsafe { &*fields };
400+
401+
// SAFETY: it's safe to go from Data<'static> to Data<'db>
402+
// shrink lifetime here to use a single lifetime in Lookup::eq(&StructKey<'db>, &C::Data<'db>)
403+
let data = unsafe { Self::from_internal_data(fields) };
404+
405+
data == old_fields_ref
406+
})
407+
};
408+
409+
let old_data_hash = self.hasher.hash_one(old_fields_ref);
410+
shared
411+
.key_map
412+
.find_entry(old_data_hash, eq)
413+
.expect("interned value in LRU so must be in key_map")
414+
.remove();
415+
416+
// Update the fields.
417+
*old_fields = unsafe { self.to_internal_data(assemble(id, key)) };
418+
419+
// Insert the new value into the ID map.
420+
let hasher = |id: &_| {
421+
// This closure is only called if the table is resized. So while it's expensive
422+
// to lookup all values, it will only happen rarely.
423+
let value = zalsa.table().get::<Value<C>>(*id);
424+
425+
// SAFETY: We hold the lock.
426+
value
427+
.fields
428+
.with(|fields| unsafe { self.hasher.hash_one(&*fields) })
429+
};
430+
431+
shared.key_map.insert_unique(data_hash, id, hasher);
390432
});
391433

392434
// TODO: Need to free the memory safely here.
@@ -436,7 +478,7 @@ where
436478
.active_query()
437479
.map(|(_, stamp)| (stamp.durability, current_revision))
438480
// If there is no active query this durability does not actually matter.
439-
// `last_interned_at` needs to be `Revision::MAX`, see the intern_access_in_different_revision test.
481+
// `last_interned_at` needs to be `Revision::MAX`, see the `intern_access_in_different_revision` test.
440482
.unwrap_or((Durability::MAX, Revision::max()));
441483

442484
// Allocate the value slot.
@@ -486,12 +528,8 @@ where
486528

487529
let index = self.database_key_index(id);
488530

489-
// SAFETY: We hold the lock.
490-
value.shared.with_mut(|value_shared| {
491-
// Record a dependency on this value.
492-
let first_interned_at = unsafe { (*value_shared).first_interned_at };
493-
zalsa_local.report_tracked_read_simple(index, durability, first_interned_at);
494-
});
531+
// Record a dependency on the value.
532+
zalsa_local.report_tracked_read_simple(index, durability, current_revision);
495533

496534
zalsa.event(&|| {
497535
Event::new(EventKind::DidInternValue {
@@ -522,6 +560,10 @@ where
522560
// SAFETY: We hold the lock.
523561
let value_shared = unsafe { &*value_shared };
524562

563+
// Record a read dependency on the value.
564+
//
565+
// This is necessary as interned slots may be reused, in which case any queries
566+
// that created or read from the previous value need to be revalidated.
525567
zalsa_local.report_tracked_read_simple(
526568
self.database_key_index(id),
527569
value_shared.durability,

0 commit comments

Comments
 (0)