Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions turbopack/crates/turbo-tasks-backend/src/backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,20 @@ impl TurboTasksBackend {
(had_new_data, counts)
}

/// Opens `task` with the must-exist [`ExecuteContext::task`] and drops the guard. Test-only
/// hook to exercise the non-fabricating existence guarantee: this panics (debug builds) if
/// `task` exists in neither memory nor persistent storage (rather than fabricating a
/// blank).
#[doc(hidden)]
pub fn assert_task_exists_for_testing(
&self,
task: TaskId,
turbo_tasks: &TurboTasks<TurboTasksBackend>,
) {
let mut ctx = self.execute_context(turbo_tasks);
let _ = ctx.task(task, TaskDataCategory::All);
}

fn should_restore(&self) -> bool {
self.options.storage_mode.is_some()
}
Expand Down Expand Up @@ -1244,7 +1258,7 @@ impl TurboTasksBackend {
None
};

SnapshotItem {
SnapshotItem::Put {
task_id,
meta,
data,
Expand Down Expand Up @@ -1793,7 +1807,9 @@ impl TurboTasksBackend {
turbo_tasks: &TurboTasks<TurboTasksBackend>,
) -> String {
let mut ctx = self.execute_context(turbo_tasks);
let task = ctx.task(task_id, TaskDataCategory::Data);
// Diagnostic path: the caller may name any id, including one that no longer exists, so this
// must not assert existence. A nonexistent task falls through to the "unknown" case below.
let task = ctx.open_or_create_task_storage(task_id, TaskDataCategory::Data);
if let Some(value) = task.get_persistent_task_type() {
value.to_string()
} else if let Some(value) = task.get_transient_task_type() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,10 @@ impl ConnectChildOperation {
task: child_task_id,
});
} else {
let mut child_task = ctx.task(child_task_id, TaskDataCategory::Meta);
// First connect of this child: its id is minted but the storage entry may not exist
// yet, and concurrent connects race to be the one that first touches it.
let mut child_task =
ctx.open_or_create_task_storage(child_task_id, TaskDataCategory::Meta);
let has_output = child_task.has_output();
// An already constructed top-level task was made a root when it was first connected.
// It may still be dirty and need to run; this only avoids repeating the idempotent
Expand Down
342 changes: 251 additions & 91 deletions turbopack/crates/turbo-tasks-backend/src/backend/operation/mod.rs

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions turbopack/crates/turbo-tasks-backend/src/backend/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1044,7 +1044,7 @@ mod tests {
_: &super::TaskStorage,
_: &mut TurboBincodeBuffer,
) -> SnapshotItem {
SnapshotItem {
SnapshotItem::Put {
task_id,
meta: Some(TurboBincodeBuffer::default()),
data: None,
Expand Down Expand Up @@ -1112,7 +1112,7 @@ mod tests {

// The pre-snapshot snapshot copy should have been encoded and returned.
assert_eq!(items.len(), 1);
assert_eq!(items[0].task_id, task_id);
assert_eq!(items[0].task_id(), task_id);

{
let guard = storage.access_mut(task_id);
Expand Down Expand Up @@ -1179,7 +1179,7 @@ mod tests {
.collect();

assert_eq!(items.len(), 1);
assert_eq!(items[0].task_id, task_id);
assert_eq!(items[0].task_id(), task_id);

{
let guard = storage.access_mut(task_id);
Expand Down Expand Up @@ -1227,7 +1227,7 @@ mod tests {
.collect();

assert_eq!(items.len(), 1);
assert_eq!(items[0].task_id, task_id);
assert_eq!(items[0].task_id(), task_id);

// The entry must be gone from the map now that it has been persisted.
assert!(
Expand Down Expand Up @@ -1324,7 +1324,7 @@ mod tests {
.flat_map(|shard| shard.into_iter())
.collect();
assert_eq!(items.len(), 1);
assert_eq!(items[0].task_id, modified_id);
assert_eq!(items[0].task_id(), modified_id);
}

#[tokio::test(flavor = "multi_thread")]
Expand Down
43 changes: 34 additions & 9 deletions turbopack/crates/turbo-tasks-backend/src/backing_storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,40 @@ use turbo_tasks_hash::Xxh3Hash64Hasher;

pub type TaskTypeHash = [u8; 8];

/// A single item yielded by the snapshot iterator during persistence.
pub struct SnapshotItem {
pub task_id: TaskId,
/// Serialized task meta data, if modified
pub meta: Option<TurboBincodeBuffer>,
/// Serialized task data, if modified
pub data: Option<TurboBincodeBuffer>,
/// Task type for new tasks that need to be added to the task cache
pub task_type_hash: Option<TaskTypeHash>,
/// A single item yielded by the snapshot iterator during persistence: either a put (persist a
/// modified task's meta/data + optionally register a new task's type) or a delete (tombstone a
/// GC-collected task's on-disk copy). Both ride the one iterator `save_snapshot` consumes, so
/// tombstones are applied in the same commit and batch as the puts.
pub enum SnapshotItem {
Put {
task_id: TaskId,
/// Serialized task meta data, if modified
meta: Option<TurboBincodeBuffer>,
/// Serialized task data, if modified
data: Option<TurboBincodeBuffer>,
/// Task type for new tasks that need to be added to the task cache
task_type_hash: Option<TaskTypeHash>,
},
// Constructed by the GC pass that emits `Delete` for soft-deleted tasks, which lands in a
// later PR in the stack.
#[allow(dead_code)]
Delete {
task_id: TaskId,
/// The deleted task's `TaskCache` key. Always present: only persistent tasks are
/// collected, and those always have a task type.
task_type_hash: TaskTypeHash,
},
}

impl SnapshotItem {
/// The task this item persists or tombstones. (Currently only used by tests, which assert on
/// the id of items yielded by the snapshot iterator.)
#[cfg(test)]
pub fn task_id(&self) -> TaskId {
match self {
SnapshotItem::Put { task_id, .. } | SnapshotItem::Delete { task_id, .. } => *task_id,
}
}
}

/// Computes a deterministic 64-bit hash of a CachedTaskType for use as a TaskCache key.
Expand Down
19 changes: 19 additions & 0 deletions turbopack/crates/turbo-tasks-backend/src/database/turbo/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,25 @@ impl<'a> TurboWriteBatch<'a> {
.put(key_space as u32, key.into_static(), value.into())
}

/// Writes a delete (tombstone) for `key` into the write batch.
///
/// Use [`Self::delete_value`] to remove a single mapping from a MultiValue KeySpace
pub fn delete(&self, key_space: KeySpace, key: WriteBuffer<'_>) -> Result<()> {
self.batch.delete(key_space as u32, key.into_static())
}

/// Writes a tombstone for a single `key` -> `value` mapping, leaving other values under `key`
/// intact. Only valid for `MultiValue` families (`TaskCache`).
pub fn delete_value(
&self,
key_space: KeySpace,
key: WriteBuffer<'_>,
value: WriteBuffer<'_>,
) -> Result<()> {
self.batch
.delete_value(key_space as u32, key.into_static(), value.into())
}

/// Flushes a key space of the write batch, reducing the amount of buffered memory used.
/// Does not commit any data persistently.
///
Expand Down
Loading
Loading