Skip to content

Commit da87628

Browse files
committed
testing
1 parent e5eb40f commit da87628

8 files changed

Lines changed: 145 additions & 52 deletions

File tree

turbopack/crates/turbo-persistence/src/db.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,10 @@ impl<S: ParallelScheduler, const FAMILIES: usize> TurboPersistence<S, FAMILIES>
469469
operations is allowed at a time)"
470470
);
471471
}
472+
// Free decompressed block caches before the write cycle begins. Writes are
473+
// memory-intensive (building new SST files), and cached blocks from old SSTs
474+
// will be superseded by new sequence numbers after commit anyway.
475+
self.clear_block_caches();
472476
let current = self.inner.read().current_sequence_number;
473477
Ok(WriteBatch::new(
474478
self.path.clone(),
@@ -480,8 +484,7 @@ impl<S: ParallelScheduler, const FAMILIES: usize> TurboPersistence<S, FAMILIES>
480484

481485
/// Clears all caches of the database.
482486
pub fn clear_cache(&self) {
483-
self.key_block_cache.clear();
484-
self.value_block_cache.clear();
487+
self.clear_block_caches();
485488
for meta in self.inner.write().meta_files.iter_mut() {
486489
meta.clear_cache();
487490
}

turbopack/crates/turbo-tasks-backend/src/backend/mod.rs

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ static IDLE_TIMEOUT: LazyLock<Duration> = LazyLock::new(|| {
9191
.ok()
9292
.and_then(|v| v.parse::<u64>().ok())
9393
.map(Duration::from_millis)
94-
.unwrap_or(Duration::from_secs(10))
94+
.unwrap_or(Duration::from_secs(2))
9595
});
9696

9797
struct SnapshotRequest {
@@ -382,7 +382,7 @@ impl<B: BackingStorage> TurboTasksBackendInner<B> {
382382
return (false, EvictionCounts::default());
383383
}
384384
};
385-
let counts = self.storage.evict_after_snapshot();
385+
let counts = self.storage.evict_after_snapshot(None);
386386
(had_new_data, counts)
387387
}
388388

@@ -1578,19 +1578,19 @@ impl<B: BackingStorage> TurboTasksBackendInner<B> {
15781578
let mut ctx = self.execute_context(turbo_tasks);
15791579

15801580
let mut is_new = false;
1581-
let (task_id, task_type) = if let Some(task_id) = ctx.task_by_type(&task_type) {
1582-
// Task exists in backing storage
1583-
// So we only need to insert it into the in-memory cache
1581+
let (task_id, task_type) = if let Some((task_id, arc)) = ctx.task_by_type(&task_type) {
1582+
// Task exists in backing storage — re-insert into task_cache using the task's
1583+
// own Arc so task_cache and persistent_task_type share the same allocation.
1584+
// This maintains the invariant that Arc::strong_count on persistent_task_type
1585+
// reflects whether a task_cache entry holds a reference.
15841586
self.track_cache_hit(&task_type);
1585-
let task_type = match raw_entry(&self.storage.task_cache, &task_type) {
1586-
RawEntry::Occupied(_) => ArcOrOwned::Owned(task_type),
1587+
match raw_entry(&self.storage.task_cache, &task_type) {
1588+
RawEntry::Occupied(_) => {}
15871589
RawEntry::Vacant(e) => {
1588-
let task_type = Arc::new(task_type);
1589-
e.insert(task_type.clone(), task_id);
1590-
ArcOrOwned::Arc(task_type)
1590+
e.insert(arc.clone(), task_id);
15911591
}
1592-
};
1593-
(task_id, task_type)
1592+
}
1593+
(task_id, ArcOrOwned::Arc(arc))
15941594
} else {
15951595
// Task doesn't exist in memory cache or backing storage
15961596
// So we might need to create a new task
@@ -2889,18 +2889,6 @@ impl<B: BackingStorage> TurboTasksBackendInner<B> {
28892889
if let Some((snapshot_start, new_data)) = snapshot {
28902890
last_snapshot = snapshot_start;
28912891

2892-
// Evict persisted tasks from memory to reclaim space.
2893-
// Like compaction, this runs after snapshot_and_persist
2894-
// as a separate concern.
2895-
//
2896-
// TODO: improve eviction policy — current approach is a full sweep
2897-
// after every snapshot. Better strategies to consider:
2898-
// - Memory pressure signals: only evict when RSS exceeds a threshold
2899-
// rather than unconditionally.
2900-
// - Recency data: track last-access time per task and evict
2901-
// least-recently-used entries first rather than all at once.
2902-
// - Eviction intensity: partial sweeps (evict a fraction of eligible
2903-
// tasks per cycle) to reduce latency spikes.
29042892
// Polls the idle-end event without blocking. Returns
29052893
// `true` and refreshes the listener if idle has ended,
29062894
// `false` if we are still idle.
@@ -2916,10 +2904,21 @@ impl<B: BackingStorage> TurboTasksBackendInner<B> {
29162904
}
29172905
}};
29182906
}
2919-
2907+
// Evict persisted tasks from memory to reclaim space.
2908+
// Like compaction, this runs after snapshot_and_persist
2909+
// as a separate concern.
2910+
//
2911+
// TODO: improve eviction policy — current approach is a full sweep
2912+
// after every snapshot. Better strategies to consider:
2913+
// - Memory pressure signals: only evict when RSS exceeds a threshold
2914+
// rather than unconditionally.
2915+
// - Recency data: track last-access time per task and evict
2916+
// least-recently-used entries first rather than all at once.
2917+
// - Eviction intensity: partial sweeps (evict a fraction of eligible
2918+
// tasks per cycle) to reduce latency spikes.
29202919
if this.should_evict() {
29212920
if !check_idle_ended!() {
2922-
this.storage.evict_after_snapshot();
2921+
this.storage.evict_after_snapshot(background_span.id());
29232922
}
29242923
}
29252924

turbopack/crates/turbo-tasks-backend/src/backend/operation/mod.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,10 @@ pub trait ExecuteContext<'e>: Sized {
9999
///
100100
/// Uses hash-based lookup which may return multiple candidates due to hash collisions,
101101
/// then verifies each candidate by comparing the stored `persistent_task_type`.
102-
/// Returns `Some(task_id)` if a matching task is found, `None` otherwise.
103-
fn task_by_type(&mut self, task_type: &CachedTaskType) -> Option<TaskId>;
102+
/// Returns `Some((task_id, arc))` where `arc` is the task's own `Arc<CachedTaskType>`
103+
/// (same allocation as `TaskStorage.persistent_task_type`), or `None` if not found.
104+
fn task_by_type(&mut self, task_type: &CachedTaskType)
105+
-> Option<(TaskId, Arc<CachedTaskType>)>;
104106
}
105107

106108
pub trait ChildExecuteContext<'e>: Send + Sized {
@@ -964,7 +966,10 @@ impl<'e, B: BackingStorage> ExecuteContext<'e> for ExecuteContextImpl<'e, B> {
964966
self.turbo_tasks.pin()
965967
}
966968

967-
fn task_by_type(&mut self, task_type: &CachedTaskType) -> Option<TaskId> {
969+
fn task_by_type(
970+
&mut self,
971+
task_type: &CachedTaskType,
972+
) -> Option<(TaskId, Arc<CachedTaskType>)> {
968973
if !self.backend.should_restore() {
969974
return None;
970975
}
@@ -983,7 +988,11 @@ impl<'e, B: BackingStorage> ExecuteContext<'e> for ExecuteContextImpl<'e, B> {
983988
if let Some(stored_type) = task.get_persistent_task_type()
984989
&& stored_type.as_ref() == task_type
985990
{
986-
return Some(candidate_id);
991+
// Clone the Arc from the task's own persistent_task_type so the caller
992+
// can re-insert into task_cache using the same allocation, maintaining
993+
// the invariant that task_cache and persistent_task_type share one Arc.
994+
let arc = stored_type.clone();
995+
return Some((candidate_id, arc));
987996
}
988997
}
989998
None

turbopack/crates/turbo-tasks-backend/src/backend/storage.rs

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use std::{
1010

1111
use rustc_hash::{FxHashMap, FxHasher};
1212
use thread_local::ThreadLocal;
13+
use tracing::span::Id;
1314
use turbo_bincode::TurboBincodeBuffer;
1415
use turbo_tasks::{FxDashMap, TaskId, backend::CachedTaskType, event::Event, parallel};
1516

@@ -388,15 +389,23 @@ impl Storage {
388389
/// - `No`: skip
389390
///
390391
/// Must be called when NOT in snapshot mode (i.e., after `end_snapshot()`).
391-
pub fn evict_after_snapshot(&self) {
392+
pub fn evict_after_snapshot(&self, parent_span: Option<Id>) -> EvictionCounts {
392393
let span = tracing::trace_span!(
394+
parent: parent_span,
393395
"evict_after_snapshot",
394-
task_cache = tracing::field::Empty,
396+
total_task_cache_keys = self.task_cache.len(),
397+
total_map_keys = self.map.len(),
398+
task_cache_evictions = tracing::field::Empty,
395399
full = tracing::field::Empty,
396400
data_and_meta = tracing::field::Empty,
397401
data_only = tracing::field::Empty,
398402
meta_only = tracing::field::Empty,
399403
skipped = tracing::field::Empty,
404+
skipped_in_progress = tracing::field::Empty,
405+
skipped_restoring = tracing::field::Empty,
406+
skipped_modified = tracing::field::Empty,
407+
skipped_transient_or_stateful = tracing::field::Empty,
408+
skipped_nothing_to_evict = tracing::field::Empty,
400409
)
401410
.entered();
402411
debug_assert!(
@@ -418,14 +427,20 @@ impl Storage {
418427
}
419428
let (key, data) = task.get().evictability();
420429
if matches!(key, KeyEvictability::Evictable) {
421-
evicted.key_evictions += 1;
422430
// The task type is persisted to backing storage (new_task = false),
423431
// so task_cache is a pure perf cache. Remove it now; it will be
424432
// re-populated by task_by_type() on the next cache miss.
425-
if let Some(task_type) = task.get().get_persistent_task_type() {
426-
self.task_cache.remove(task_type.as_ref());
433+
434+
if self
435+
.task_cache
436+
.remove(task.get().get_persistent_task_type().unwrap().as_ref())
437+
.is_some()
438+
{
439+
evicted.key_evictions += 1;
427440
}
428441
}
442+
// KeyEvictability::AlreadyEvicted: strong_count == 1 means no
443+
// task_cache entry holds a reference — skip the hash lookup.
429444
match data {
430445
DataEvictability::Full => {
431446
unsafe {
@@ -464,6 +479,7 @@ impl Storage {
464479
}
465480
(evicted, reason_counts)
466481
});
482+
467483
let mut totals = EvictionCounts::default();
468484
let mut reasons: FxHashMap<UnevictableReason, usize> = FxHashMap::default();
469485
for (evicted, r) in counts {
@@ -483,12 +499,49 @@ impl Storage {
483499
self.task_cache.shrink_to_fit();
484500
}
485501
let skipped: usize = reasons.values().sum();
486-
span.record("task_cache", totals.key_evictions);
502+
span.record("task_cache_evictions", totals.key_evictions);
487503
span.record("full", totals.full);
488504
span.record("data_and_meta", totals.data_and_meta);
489505
span.record("data_only", totals.data_only);
490506
span.record("meta_only", totals.meta_only);
491507
span.record("skipped", skipped);
508+
span.record(
509+
"skipped_in_progress",
510+
reasons
511+
.get(&UnevictableReason::InProgress)
512+
.copied()
513+
.unwrap_or(0),
514+
);
515+
span.record(
516+
"skipped_restoring",
517+
reasons
518+
.get(&UnevictableReason::Restoring)
519+
.copied()
520+
.unwrap_or(0),
521+
);
522+
span.record(
523+
"skipped_modified",
524+
reasons
525+
.get(&UnevictableReason::Modified)
526+
.copied()
527+
.unwrap_or(0),
528+
);
529+
span.record(
530+
"skipped_transient_or_stateful",
531+
reasons
532+
.get(&UnevictableReason::TransientOrStateful)
533+
.copied()
534+
.unwrap_or(0),
535+
);
536+
span.record(
537+
"skipped_nothing_to_evict",
538+
reasons
539+
.get(&UnevictableReason::NothingToEvict)
540+
.copied()
541+
.unwrap_or(0),
542+
);
543+
544+
totals
492545
}
493546
}
494547

turbopack/crates/turbo-tasks-backend/src/backend/storage_schema.rs

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -436,12 +436,11 @@ impl TaskFlags {
436436
pub enum UnevictableReason {
437437
InProgress,
438438
Restoring,
439-
TransientDependents,
440-
TransientData,
441-
TransientUppers,
442-
SessionState,
443-
SessionStateful,
439+
/// Modified flags are set, or data/meta has not been restored yet.
444440
Modified,
441+
/// Transient references or session-stateful cells are present (detected without
442+
/// re-running the expensive per-field checks).
443+
TransientOrStateful,
445444
NothingToEvict,
446445
}
447446

@@ -464,6 +463,9 @@ pub enum DataEvictability {
464463
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
465464
pub enum KeyEvictability {
466465
Evictable,
466+
/// The task was already removed from `task_cache` in a prior eviction cycle.
467+
/// No hash lookup is needed; the caller can skip the remove entirely.
468+
AlreadyEvicted,
467469
/// This means the task is new, so we cannot evict it
468470
Unevictable,
469471
}
@@ -482,10 +484,20 @@ impl TaskStorage {
482484
pub fn evictability(&self) -> (KeyEvictability, DataEvictability) {
483485
let flags = &self.flags;
484486

485-
let key_evictability = if flags.new_task() || self.persistent_task_type.is_none() {
487+
let key_evictability = if flags.new_task() {
486488
KeyEvictability::Unevictable
487489
} else {
488-
KeyEvictability::Evictable
490+
match &self.persistent_task_type {
491+
None => KeyEvictability::Unevictable,
492+
// strong_count == 1: only TaskStorage holds this Arc, so no task_cache
493+
// entry references it — already evicted in a prior cycle. This covers
494+
// tasks that are key-evictable but not data-evictable (data stays in
495+
// the shard, persistent_task_type is never dropped).
496+
Some(arc) if std::sync::Arc::strong_count(arc) == 1 => {
497+
KeyEvictability::AlreadyEvicted
498+
}
499+
Some(_) => KeyEvictability::Evictable,
500+
}
489501
};
490502
// === Absolute blockers ===
491503
if flags.new_task()
@@ -588,7 +600,22 @@ impl TaskStorage {
588600
}
589601
(true, false) => DataEvictability::DataOnly,
590602
(false, true) => DataEvictability::MetaOnly,
591-
(false, false) => DataEvictability::No(UnevictableReason::Modified),
603+
(false, false) => {
604+
// Cheap flags checked first; if those are clear the blocker must
605+
// be one of the expensive transient/stateful checks above.
606+
let reason = if flags.data_modified()
607+
|| flags.data_modified_during_snapshot()
608+
|| flags.meta_modified()
609+
|| flags.meta_modified_during_snapshot()
610+
{
611+
UnevictableReason::Modified
612+
} else {
613+
// Transient references, transient cell data, or session-stateful
614+
// cells are blocking — don't repeat the expensive checks.
615+
UnevictableReason::TransientOrStateful
616+
};
617+
DataEvictability::No(reason)
618+
}
592619
},
593620
)
594621
}

turbopack/crates/turbo-tasks-backend/src/database/key_value_database.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,4 +119,8 @@ pub trait KeyValueDatabase {
119119
fn shutdown(&self) -> Result<()> {
120120
Ok(())
121121
}
122+
123+
fn flush_caches(&self) -> Result<()> {
124+
Ok(())
125+
}
122126
}

turbopack/crates/turbo-tasks-backend/src/database/turbo/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,10 @@ impl KeyValueDatabase for TurboKeyValueDatabase {
156156
// Shutdown the database
157157
self.db.shutdown()
158158
}
159+
160+
fn flush_caches(&self) -> Result<()> {
161+
Ok(())
162+
}
159163
}
160164

161165
fn do_compact(

turbopack/crates/turbo-tasks-macros/src/derive/task_storage_macro.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2590,10 +2590,6 @@ fn generate_drop_methods(grouped_fields: &GroupedFields) -> TokenStream {
25902590
let mut drop_data_inline = Vec::new();
25912591
for field in grouped_fields.persistent_inline(Category::Data) {
25922592
let field_name = &field.field_name;
2593-
// Skip persistent_task_type — needed for task_cache mappings
2594-
if field_name == "persistent_task_type" {
2595-
continue;
2596-
}
25972593
drop_data_inline.push(quote! {
25982594
self.#field_name = Default::default();
25992595
});
@@ -2621,7 +2617,6 @@ fn generate_drop_methods(grouped_fields: &GroupedFields) -> TokenStream {
26212617
/// so the next access triggers a restore from the backing storage.
26222618
pub fn drop_data(&mut self) {
26232619
// Reset data-category inline fields to defaults
2624-
// (persistent_task_type is intentionally preserved)
26252620
#(#drop_data_inline)*
26262621

26272622
// Remove all persistent data-category lazy fields
@@ -2664,7 +2659,6 @@ fn generate_drop_methods(grouped_fields: &GroupedFields) -> TokenStream {
26642659
/// are cleared, along with `prefetched`.
26652660
pub fn drop_data_and_meta(&mut self) {
26662661
// Reset all persistent inline fields to defaults
2667-
// (persistent_task_type is intentionally preserved)
26682662
#(#drop_data_inline)*
26692663
#(#drop_meta_inline)*
26702664

0 commit comments

Comments
 (0)