Skip to content

Commit ed6f396

Browse files
committed
GraphCollectingMode
1 parent 87433d9 commit ed6f396

File tree

3 files changed

+224
-7
lines changed

3 files changed

+224
-7
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

turbopack/crates/turbopack-core/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ bytes-str = { workspace = true }
2323
const_format = { workspace = true }
2424
data-encoding = { workspace = true }
2525
either = { workspace = true }
26+
futures = { workspace = true }
2627
indexmap = { workspace = true }
2728
num-bigint = "0.4"
2829
once_cell = { workspace = true }

turbopack/crates/turbopack-core/src/module_graph/mod.rs

Lines changed: 222 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ use std::{
22
collections::{BTreeSet, BinaryHeap, VecDeque},
33
future::Future,
44
ops::Deref,
5+
pin::Pin,
56
};
67

78
use anyhow::{Context, Result, bail};
89
use bincode::{Decode, Encode};
10+
use futures::{StreamExt, stream::FuturesUnordered};
911
use petgraph::{
1012
Direction,
1113
graph::{DiGraph, EdgeIndex, NodeIndex},
@@ -19,13 +21,14 @@ use turbo_tasks::{
1921
CollectiblesSource, FxIndexMap, NonLocalValue, OperationVc, ReadRef, ResolvedVc, TaskInput,
2022
TryFlatJoinIterExt, TryJoinIterExt, ValueToString, Vc,
2123
debug::ValueDebugFormat,
22-
graph::{AdjacencyMap, GraphTraversal, Visit, VisitControlFlow},
24+
graph::{AdjacencyMap, GraphStore, Visit, VisitControlFlow},
2325
trace::TraceRawVcs,
2426
};
2527
use turbo_tasks_fs::FileSystemPath;
2628

2729
use crate::{
2830
chunk::{AsyncModuleInfo, ChunkingContext, ChunkingType},
31+
emit_collect::CollectingModule,
2932
issue::{ImportTracer, ImportTraces, Issue},
3033
module::Module,
3134
module_graph::{
@@ -317,19 +320,17 @@ impl SingleModuleGraph {
317320
.try_join()
318321
.await?;
319322

320-
let children_nodes_iter = AdjacencyMap::new()
323+
let children_nodes_iter = DeferringAdjacencyMap::new(collecting_mode)
321324
.visit(
322325
root_nodes,
323326
SingleModuleGraphBuilder {
324327
visited_modules,
325328
emit_spans,
326329
include_traced,
327330
include_binding_usage,
328-
collecting_mode,
329331
},
330332
)
331-
.await
332-
.completed()?;
333+
.await?;
333334
let node_count = children_nodes_iter.len();
334335

335336
let mut graph: DiGraph<SingleModuleGraphNode, RefData> = DiGraph::with_capacity(
@@ -1618,6 +1619,222 @@ impl SingleModuleGraphBuilderNode {
16181619
}
16191620
}
16201621

1622+
type EdgesFutureOutput = (
1623+
SingleModuleGraphBuilderNode,
1624+
Span,
1625+
Result<Vec<(SingleModuleGraphBuilderNode, RefData)>>,
1626+
);
1627+
1628+
/// A graph traversal wrapper that defers visiting `ChunkingType::Emitted` references based on
1629+
/// [`GraphCollectingMode`]:
1630+
/// - `CompleteGraph`: defers emitted references until a [`CollectingModule`] with matching
1631+
/// `namespace()` is discovered. Unmatched emitted references are discarded.
1632+
/// - `IncompleteGraph`: excludes emitted references whose `merge_tag` is in
1633+
/// `ignored_collected_namespace`. Other emitted references are traversed normally.
1634+
struct DeferringAdjacencyMap {
1635+
collecting_mode: GraphCollectingMode,
1636+
}
1637+
1638+
impl DeferringAdjacencyMap {
1639+
fn new(collecting_mode: GraphCollectingMode) -> Self {
1640+
Self { collecting_mode }
1641+
}
1642+
1643+
async fn visit<'a>(
1644+
self,
1645+
root_nodes: impl IntoIterator<Item = SingleModuleGraphBuilderNode>,
1646+
mut visit: SingleModuleGraphBuilder<'a>,
1647+
) -> Result<AdjacencyMap<SingleModuleGraphBuilderNode, RefData>> {
1648+
use turbo_tasks::graph::{GraphStore, VisitControlFlow};
1649+
1650+
let mut store = AdjacencyMap::<SingleModuleGraphBuilderNode, RefData>::new();
1651+
let mut futures: FuturesUnordered<
1652+
Pin<Box<dyn Future<Output = EdgesFutureOutput> + Send + 'a>>,
1653+
> = FuturesUnordered::new();
1654+
1655+
// Deferred emitted references: (parent_handle, target_node, edge_data)
1656+
let mut deferred: Vec<(
1657+
SingleModuleGraphBuilderNode,
1658+
SingleModuleGraphBuilderNode,
1659+
RefData,
1660+
)> = Vec::new();
1661+
let mut discovered_namespaces: FxHashSet<RcStr> = FxHashSet::default();
1662+
1663+
// Process root nodes
1664+
for node in root_nodes {
1665+
match visit.visit(&node, None) {
1666+
VisitControlFlow::Continue => {
1667+
if let Some(handle) = store.try_enter(&node) {
1668+
let span = visit.span(&node, None);
1669+
let edges_future = visit.edges(&node);
1670+
futures.push(Box::pin(async move {
1671+
let result = edges_future.await;
1672+
(handle, span, result)
1673+
}));
1674+
}
1675+
self.check_collecting_module(
1676+
&node,
1677+
&mut discovered_namespaces,
1678+
&mut deferred,
1679+
&mut store,
1680+
&mut visit,
1681+
&mut futures,
1682+
)
1683+
.await?;
1684+
store.insert(None, node);
1685+
}
1686+
VisitControlFlow::Skip => {
1687+
store.insert(None, node);
1688+
}
1689+
VisitControlFlow::Exclude => {
1690+
// do nothing
1691+
}
1692+
}
1693+
}
1694+
1695+
let mut result = Ok(());
1696+
loop {
1697+
match futures.next().await {
1698+
Some((parent_handle, span, Ok(edges))) => {
1699+
let _guard = span.enter();
1700+
for (node, edge) in edges {
1701+
match visit.visit(&node, Some(&edge)) {
1702+
VisitControlFlow::Continue => {
1703+
// Potentially defer visiting emitted references
1704+
if let ChunkingType::Emitted { merge_tag, .. } = &edge.chunking_type
1705+
{
1706+
match &self.collecting_mode {
1707+
GraphCollectingMode::CompleteGraph => {
1708+
if !discovered_namespaces.contains(merge_tag) {
1709+
deferred.push((parent_handle.clone(), node, edge));
1710+
continue;
1711+
}
1712+
}
1713+
GraphCollectingMode::IncompleteGraph {
1714+
ignored_collected_namespace,
1715+
} => {
1716+
if ignored_collected_namespace.contains(merge_tag) {
1717+
continue;
1718+
}
1719+
}
1720+
}
1721+
}
1722+
1723+
if let Some(handle) = store.try_enter(&node) {
1724+
let span = visit.span(&node, Some(&edge));
1725+
let edges_future = visit.edges(&node);
1726+
futures.push(Box::pin(async move {
1727+
let result = edges_future.await;
1728+
(handle, span, result)
1729+
}));
1730+
}
1731+
self.check_collecting_module(
1732+
&node,
1733+
&mut discovered_namespaces,
1734+
&mut deferred,
1735+
&mut store,
1736+
&mut visit,
1737+
&mut futures,
1738+
)
1739+
.await?;
1740+
store.insert(Some((&parent_handle, edge)), node);
1741+
}
1742+
VisitControlFlow::Skip => {
1743+
store.insert(Some((&parent_handle, edge)), node);
1744+
}
1745+
VisitControlFlow::Exclude => {
1746+
// do nothing
1747+
}
1748+
}
1749+
}
1750+
}
1751+
Some((_, _, Err(err))) => {
1752+
result = Err(err);
1753+
}
1754+
None => break,
1755+
}
1756+
}
1757+
1758+
result.map(|()| store)
1759+
}
1760+
1761+
/// If the node is a [`CollectingModule`], discover its namespace and release any matching
1762+
/// deferred emitted references. Handles cascading: a released deferred node might itself be a
1763+
/// `CollectingModule`.
1764+
#[allow(clippy::too_many_arguments)]
1765+
async fn check_collecting_module<'a>(
1766+
&self,
1767+
node: &SingleModuleGraphBuilderNode,
1768+
discovered_namespaces: &mut FxHashSet<RcStr>,
1769+
deferred: &mut Vec<(
1770+
SingleModuleGraphBuilderNode,
1771+
SingleModuleGraphBuilderNode,
1772+
RefData,
1773+
)>,
1774+
store: &mut AdjacencyMap<SingleModuleGraphBuilderNode, RefData>,
1775+
visit: &mut SingleModuleGraphBuilder<'a>,
1776+
futures: &mut FuturesUnordered<
1777+
Pin<Box<dyn Future<Output = EdgesFutureOutput> + Send + 'a>>,
1778+
>,
1779+
) -> Result<()> {
1780+
let SingleModuleGraphBuilderNode::Module { module, .. } = node else {
1781+
return Ok(());
1782+
};
1783+
let Some(module) = ResolvedVc::try_downcast::<Box<dyn CollectingModule>>(*module) else {
1784+
return Ok(());
1785+
};
1786+
1787+
let mut collecting_modules = vec![module];
1788+
while let Some(module) = collecting_modules.pop() {
1789+
let ns = module.namespace().await?;
1790+
if discovered_namespaces.contains(&*ns) {
1791+
continue;
1792+
}
1793+
discovered_namespaces.insert((*ns).clone());
1794+
1795+
// Drain matching deferred entries
1796+
let mut i = 0;
1797+
while i < deferred.len() {
1798+
if matches!(
1799+
&deferred[i].2.chunking_type,
1800+
ChunkingType::Emitted { merge_tag, .. } if merge_tag == &*ns
1801+
) {
1802+
let (parent_handle, def_node, edge) = deferred.swap_remove(i);
1803+
match visit.visit(&def_node, Some(&edge)) {
1804+
VisitControlFlow::Continue => {
1805+
if let Some(handle) = store.try_enter(&def_node) {
1806+
let span = visit.span(&def_node, Some(&edge));
1807+
let edges_future = visit.edges(&def_node);
1808+
futures.push(Box::pin(async move {
1809+
let result = edges_future.await;
1810+
(handle, span, result)
1811+
}));
1812+
}
1813+
if let SingleModuleGraphBuilderNode::Module { module, .. } = &def_node
1814+
&& let Some(module) =
1815+
ResolvedVc::try_downcast::<Box<dyn CollectingModule>>(*module)
1816+
{
1817+
collecting_modules.push(module);
1818+
}
1819+
store.insert(Some((&parent_handle, edge)), def_node);
1820+
}
1821+
VisitControlFlow::Skip => {
1822+
store.insert(Some((&parent_handle, edge)), def_node);
1823+
}
1824+
VisitControlFlow::Exclude => {
1825+
// do nothing
1826+
}
1827+
}
1828+
} else {
1829+
i += 1;
1830+
}
1831+
}
1832+
}
1833+
1834+
Ok(())
1835+
}
1836+
}
1837+
16211838
struct SingleModuleGraphBuilder<'a> {
16221839
visited_modules: &'a FxIndexMap<ResolvedVc<Box<dyn Module>>, GraphNodeIndex>,
16231840

@@ -1628,8 +1845,6 @@ struct SingleModuleGraphBuilder<'a> {
16281845

16291846
/// Whether to read ModuleReference::binding_usage()
16301847
include_binding_usage: bool,
1631-
1632-
collecting_mode: GraphCollectingMode,
16331848
}
16341849
impl Visit<SingleModuleGraphBuilderNode, RefData> for SingleModuleGraphBuilder<'_> {
16351850
type EdgesIntoIter = Vec<(SingleModuleGraphBuilderNode, RefData)>;

0 commit comments

Comments
 (0)