Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 9d4b516

Browse files
committedApr 4, 2025·
feat(turbo-tasks): Trace transient tasks
1 parent e829a74 commit 9d4b516

File tree

4 files changed

+283
-15
lines changed

4 files changed

+283
-15
lines changed
 

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

+200-15
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ mod storage;
55

66
use std::{
77
borrow::Cow,
8+
fmt::{self, Write},
89
future::Future,
910
hash::BuildHasherDefault,
1011
mem::take,
@@ -18,6 +19,7 @@ use std::{
1819

1920
use anyhow::{bail, Result};
2021
use auto_hash_map::{AutoMap, AutoSet};
22+
use indexmap::IndexSet;
2123
use parking_lot::{Condvar, Mutex};
2224
use rustc_hash::{FxHashMap, FxHashSet, FxHasher};
2325
use smallvec::smallvec;
@@ -28,8 +30,9 @@ use turbo_tasks::{
2830
TransientTaskType, TypedCellContent,
2931
},
3032
event::{Event, EventListener},
31-
registry,
33+
registry::{self, get_value_type_global_name},
3234
task_statistics::TaskStatisticsApi,
35+
trace::TraceRawVcs,
3336
util::IdFactoryWithReuse,
3437
CellId, FunctionId, FxDashMap, RawVc, ReadCellOptions, ReadConsistency, SessionId, TaskId,
3538
TraitTypeId, TurboTasksBackendApi, ValueTypeId, TRANSIENT_TASK_BIT,
@@ -425,7 +428,7 @@ impl<B: BackingStorage> TurboTasksBackendInner<B> {
425428
turbo_tasks: &dyn TurboTasksBackendApi<TurboTasksBackend<B>>,
426429
) -> Result<Result<RawVc, EventListener>> {
427430
if let Some(reader) = reader {
428-
self.assert_not_persistent_calling_transient(reader, task_id);
431+
self.assert_not_persistent_calling_transient(reader, task_id, /* cell_id */ None);
429432
}
430433

431434
let mut ctx = self.execute_context(turbo_tasks);
@@ -625,7 +628,7 @@ impl<B: BackingStorage> TurboTasksBackendInner<B> {
625628
turbo_tasks: &dyn TurboTasksBackendApi<TurboTasksBackend<B>>,
626629
) -> Result<Result<TypedCellContent, EventListener>> {
627630
if let Some(reader) = reader {
628-
self.assert_not_persistent_calling_transient(reader, task_id);
631+
self.assert_not_persistent_calling_transient(reader, task_id, Some(cell));
629632
}
630633

631634
fn add_cell_dependency<B: BackingStorage>(
@@ -985,9 +988,10 @@ impl<B: BackingStorage> TurboTasksBackendInner<B> {
985988
turbo_tasks: &dyn TurboTasksBackendApi<TurboTasksBackend<B>>,
986989
) -> TaskId {
987990
if !parent_task.is_transient() {
988-
panic_persistent_calling_transient(
991+
self.panic_persistent_calling_transient(
989992
self.lookup_task_type(parent_task).as_deref(),
990993
Some(&task_type),
994+
/* cell_id */ None,
991995
);
992996
}
993997
if let Some(task_id) = self.task_cache.lookup_forward(&task_type) {
@@ -1013,6 +1017,84 @@ impl<B: BackingStorage> TurboTasksBackendInner<B> {
10131017
task_id
10141018
}
10151019

1020+
/// Generate an object that implements [`fmt::Display`] explaining why the given
1021+
/// [`CachedTaskType`] is transient.
1022+
fn debug_trace_transient_task(
1023+
&self,
1024+
task_type: &CachedTaskType,
1025+
cell_id: Option<CellId>,
1026+
) -> DebugTraceTransientTask {
1027+
// it shouldn't be possible to have cycles in tasks, but we could have an exponential blowup
1028+
// from tracing the same task many times, so use a visited_set
1029+
fn inner_id(
1030+
backend: &TurboTasksBackendInner<impl BackingStorage>,
1031+
task_id: TaskId,
1032+
cell_type_id: Option<ValueTypeId>,
1033+
visited_set: &mut FxHashSet<TaskId>,
1034+
) -> DebugTraceTransientTask {
1035+
if let Some(task_type) = backend.lookup_task_type(task_id) {
1036+
if visited_set.contains(&task_id) {
1037+
let task_name = task_type.get_name();
1038+
DebugTraceTransientTask::Collapsed {
1039+
task_name,
1040+
cell_type_id,
1041+
}
1042+
} else {
1043+
inner_cached(backend, &task_type, cell_type_id, visited_set)
1044+
}
1045+
} else {
1046+
DebugTraceTransientTask::Uncached { cell_type_id }
1047+
}
1048+
}
1049+
fn inner_cached(
1050+
backend: &TurboTasksBackendInner<impl BackingStorage>,
1051+
task_type: &CachedTaskType,
1052+
cell_type_id: Option<ValueTypeId>,
1053+
visited_set: &mut FxHashSet<TaskId>,
1054+
) -> DebugTraceTransientTask {
1055+
let task_name = task_type.get_name();
1056+
1057+
let cause_self = task_type.this.and_then(|cause_self_raw_vc| {
1058+
let task_id = cause_self_raw_vc.get_task_id();
1059+
if task_id.is_transient() {
1060+
Some(Box::new(inner_id(
1061+
backend,
1062+
cause_self_raw_vc.get_task_id(),
1063+
cause_self_raw_vc.try_get_type_id(),
1064+
visited_set,
1065+
)))
1066+
} else {
1067+
None
1068+
}
1069+
});
1070+
let cause_args = task_type
1071+
.arg
1072+
.get_raw_vcs()
1073+
.into_iter()
1074+
.map(|raw_vc| (raw_vc.get_task_id(), raw_vc.try_get_type_id()))
1075+
.filter(|(task_id, _)| task_id.is_transient())
1076+
.collect::<IndexSet<_>>() // dedupe
1077+
.into_iter()
1078+
.map(|(task_id, cell_type_id)| {
1079+
inner_id(backend, task_id, cell_type_id, visited_set)
1080+
})
1081+
.collect();
1082+
1083+
DebugTraceTransientTask::Cached {
1084+
task_name,
1085+
cell_type_id,
1086+
cause_self,
1087+
cause_args,
1088+
}
1089+
}
1090+
inner_cached(
1091+
self,
1092+
task_type,
1093+
cell_id.map(|c| c.type_id),
1094+
&mut FxHashSet::default(),
1095+
)
1096+
}
1097+
10161098
fn invalidate_task(
10171099
&self,
10181100
task_id: TaskId,
@@ -1987,14 +2069,42 @@ impl<B: BackingStorage> TurboTasksBackendInner<B> {
19872069
}
19882070
}
19892071

1990-
fn assert_not_persistent_calling_transient(&self, parent_id: TaskId, child_id: TaskId) {
2072+
fn assert_not_persistent_calling_transient(
2073+
&self,
2074+
parent_id: TaskId,
2075+
child_id: TaskId,
2076+
cell_id: Option<CellId>,
2077+
) {
19912078
if !parent_id.is_transient() && child_id.is_transient() {
1992-
panic_persistent_calling_transient(
2079+
self.panic_persistent_calling_transient(
19932080
self.lookup_task_type(parent_id).as_deref(),
19942081
self.lookup_task_type(child_id).as_deref(),
2082+
cell_id,
19952083
);
19962084
}
19972085
}
2086+
2087+
fn panic_persistent_calling_transient(
2088+
&self,
2089+
parent: Option<&CachedTaskType>,
2090+
child: Option<&CachedTaskType>,
2091+
cell_id: Option<CellId>,
2092+
) {
2093+
let transient_reason = if let Some(child) = child {
2094+
format!(
2095+
" The callee is transient because it depends on:\n{}",
2096+
self.debug_trace_transient_task(child, cell_id),
2097+
)
2098+
} else {
2099+
String::new()
2100+
};
2101+
panic!(
2102+
"Persistent task {} is not allowed to call or read transient tasks {}.{}",
2103+
parent.map_or("unknown", |t| t.get_name()),
2104+
child.map_or("unknown", |t| t.get_name()),
2105+
transient_reason,
2106+
);
2107+
}
19982108
}
19992109

20002110
impl<B: BackingStorage> Backend for TurboTasksBackend<B> {
@@ -2272,15 +2382,90 @@ impl<B: BackingStorage> Backend for TurboTasksBackend<B> {
22722382
}
22732383
}
22742384

2275-
fn panic_persistent_calling_transient(
2276-
parent: Option<&CachedTaskType>,
2277-
child: Option<&CachedTaskType>,
2278-
) {
2279-
panic!(
2280-
"Persistent task {} is not allowed to call or read transient tasks {}",
2281-
parent.map_or("unknown", |t| t.get_name()),
2282-
child.map_or("unknown", |t| t.get_name()),
2283-
);
2385+
enum DebugTraceTransientTask {
2386+
Cached {
2387+
task_name: &'static str,
2388+
cell_type_id: Option<ValueTypeId>,
2389+
cause_self: Option<Box<DebugTraceTransientTask>>,
2390+
cause_args: Vec<DebugTraceTransientTask>,
2391+
},
2392+
/// This representation is used when this task is a duplicate of one previously shown
2393+
Collapsed {
2394+
task_name: &'static str,
2395+
cell_type_id: Option<ValueTypeId>,
2396+
},
2397+
Uncached {
2398+
cell_type_id: Option<ValueTypeId>,
2399+
},
2400+
}
2401+
2402+
impl DebugTraceTransientTask {
2403+
fn fmt_indented(&self, f: &mut fmt::Formatter<'_>, level: usize) -> fmt::Result {
2404+
let indent = " ".repeat(level);
2405+
f.write_str(&indent)?;
2406+
2407+
fn fmt_cell_type_id(
2408+
f: &mut fmt::Formatter<'_>,
2409+
cell_type_id: Option<ValueTypeId>,
2410+
) -> fmt::Result {
2411+
if let Some(ty) = cell_type_id {
2412+
write!(f, " (read cell of type {})", get_value_type_global_name(ty))
2413+
} else {
2414+
Ok(())
2415+
}
2416+
}
2417+
2418+
// write the name and type
2419+
match self {
2420+
Self::Cached {
2421+
task_name,
2422+
cell_type_id,
2423+
..
2424+
}
2425+
| Self::Collapsed {
2426+
task_name,
2427+
cell_type_id,
2428+
..
2429+
} => {
2430+
f.write_str(task_name)?;
2431+
fmt_cell_type_id(f, *cell_type_id)?;
2432+
if matches!(self, Self::Collapsed { .. }) {
2433+
f.write_str(" (collapsed)")?;
2434+
}
2435+
}
2436+
Self::Uncached { cell_type_id } => {
2437+
f.write_str("unknown transient task")?;
2438+
fmt_cell_type_id(f, *cell_type_id)?;
2439+
}
2440+
}
2441+
f.write_char('\n')?;
2442+
2443+
// write any extra "cause" information we might have
2444+
if let Self::Cached {
2445+
cause_self,
2446+
cause_args,
2447+
..
2448+
} = self
2449+
{
2450+
if let Some(c) = cause_self {
2451+
writeln!(f, "{indent} self:")?;
2452+
c.fmt_indented(f, level + 1)?;
2453+
}
2454+
if !cause_args.is_empty() {
2455+
writeln!(f, "{indent} args:")?;
2456+
for c in cause_args {
2457+
c.fmt_indented(f, level + 1)?;
2458+
}
2459+
}
2460+
}
2461+
Ok(())
2462+
}
2463+
}
2464+
2465+
impl fmt::Display for DebugTraceTransientTask {
2466+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2467+
self.fmt_indented(f, 0)
2468+
}
22842469
}
22852470

22862471
// from https://github.com/tokio-rs/tokio/blob/29cd6ec1ec6f90a7ee1ad641c03e0e00badbcb0e/tokio/src/time/instant.rs#L57-L63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#![feature(arbitrary_self_types)]
2+
#![feature(arbitrary_self_types_pointers)]
3+
4+
use anyhow::Result;
5+
use serde::{Deserialize, Serialize};
6+
use turbo_tasks::{trace::TraceRawVcs, NonLocalValue, ResolvedVc, TaskInput, Vc};
7+
use turbo_tasks_testing::{register, run_without_cache_check, Registration};
8+
9+
static REGISTRATION: Registration = register!();
10+
11+
const EXPECTED_TRACE: &str = "\
12+
Adder::add_method (read cell of type turbo-tasks@TODO::::primitives::u64)
13+
self:
14+
Adder::new (read cell of type turbo-tasks-backend@TODO::::Adder)
15+
args:
16+
unknown transient task (read cell of type turbo-tasks@TODO::::primitives::unit)
17+
args:
18+
unknown transient task (read cell of type turbo-tasks@TODO::::primitives::u16)
19+
unknown transient task (read cell of type turbo-tasks@TODO::::primitives::u32)";
20+
21+
#[tokio::test]
22+
async fn test_trace_transient() {
23+
let result = run_without_cache_check(&REGISTRATION, async {
24+
read_incorrect_task_input_operation(IncorrectTaskInput(
25+
Adder::new(Vc::cell(()))
26+
.add_method(Vc::cell(2), Vc::cell(3))
27+
.to_resolved()
28+
.await?,
29+
))
30+
.read_strongly_consistent()
31+
.await?;
32+
anyhow::Ok(())
33+
})
34+
.await;
35+
assert!(result
36+
.unwrap_err()
37+
.to_string()
38+
.contains(&EXPECTED_TRACE.escape_debug().to_string()));
39+
}
40+
41+
#[turbo_tasks::value]
42+
struct Adder;
43+
44+
#[turbo_tasks::value_impl]
45+
impl Adder {
46+
#[turbo_tasks::function]
47+
fn new(arg: ResolvedVc<()>) -> Vc<Adder> {
48+
let _ = arg; // Make sure unused argument filtering doesn't remove the arg
49+
Adder.cell()
50+
}
51+
52+
#[turbo_tasks::function]
53+
async fn add_method(&self, arg1: ResolvedVc<u16>, arg2: ResolvedVc<u32>) -> Result<Vc<u64>> {
54+
Ok(Vc::cell(u64::from(*arg1.await?) + u64::from(*arg2.await?)))
55+
}
56+
}
57+
58+
#[turbo_tasks::function(operation)]
59+
async fn read_incorrect_task_input_operation(value: IncorrectTaskInput) -> Result<Vc<u64>> {
60+
Ok(Vc::cell(*value.0.await?))
61+
}
62+
63+
/// Has an intentionally incorrect `TaskInput` implementation, representing some code that the debug
64+
/// tracing might be particularly useful with.
65+
#[derive(
66+
Copy, Clone, Debug, PartialEq, Eq, Hash, TraceRawVcs, Serialize, Deserialize, NonLocalValue,
67+
)]
68+
struct IncorrectTaskInput(ResolvedVc<u64>);
69+
70+
impl TaskInput for IncorrectTaskInput {
71+
fn is_transient(&self) -> bool {
72+
false
73+
}
74+
}

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

+2
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ pub struct CachedTaskType {
6868
}
6969

7070
impl CachedTaskType {
71+
/// Get the name of the function from the registry. Equivalent to the
72+
/// [`fmt::Display::to_string`] implementation, but does not allocate a `String`.
7173
pub fn get_name(&self) -> &'static str {
7274
&registry::get_function(self.fn_type).name
7375
}

‎turbopack/crates/turbo-tasks/src/raw_vc.rs

+7
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,13 @@ impl RawVc {
216216
}
217217
}
218218

219+
pub fn try_get_type_id(&self) -> Option<ValueTypeId> {
220+
match self {
221+
RawVc::TaskCell(_, CellId { type_id, .. }) => Some(*type_id),
222+
RawVc::TaskOutput(_) | RawVc::LocalOutput(_, _) => None,
223+
}
224+
}
225+
219226
/// For a cell that's already resolved, synchronously check if it implements a trait using the
220227
/// type information in `RawVc::TaskCell` (we don't actualy need to read the cell!).
221228
pub(crate) fn resolved_has_trait(&self, trait_id: TraitTypeId) -> bool {

0 commit comments

Comments
 (0)
Please sign in to comment.