Skip to content

Commit f654567

Browse files
committed
perf: Parallelize lockfile parsing with workspace discovery
Start reading and parsing the lockfile on a blocking thread concurrently with workspace discovery and package.json parsing. The lockfile read only needs the package manager identity (cheap to resolve from root package.json) and the root package.json itself, both available before the package graph pipeline begins. The existing typestate pipeline was: parse_package_jsons → read_lockfile → connect_internal_deps → ... Now: parse_package_jsons ──┐ ├→ connect_internal_deps → ... read_lockfile ────────┘ This saves min(lockfile_parse, workspace_discovery) off the critical path.
1 parent 6ef1582 commit f654567

File tree

1 file changed

+44
-1
lines changed
  • crates/turborepo-repository/src/package_graph

1 file changed

+44
-1
lines changed

crates/turborepo-repository/src/package_graph/builder.rs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ pub struct PackageGraphBuilder<'a, T> {
2929
package_jsons: Option<HashMap<AbsoluteSystemPathBuf, PackageJson>>,
3030
lockfile: Option<Box<dyn Lockfile>>,
3131
package_discovery: T,
32+
package_manager: Option<PackageManager>,
3233
}
3334

3435
#[derive(Debug, Diagnostic, thiserror::Error)]
@@ -88,6 +89,7 @@ impl<'a> PackageGraphBuilder<'a, LocalPackageDiscoveryBuilder> {
8889
is_single_package: false,
8990
package_jsons: None,
9091
lockfile: None,
92+
package_manager: None,
9193
}
9294
}
9395

@@ -98,6 +100,7 @@ impl<'a> PackageGraphBuilder<'a, LocalPackageDiscoveryBuilder> {
98100
}
99101

100102
pub fn with_package_manager(mut self, package_manager: PackageManager) -> Self {
103+
self.package_manager = Some(package_manager.clone());
101104
self.package_discovery
102105
.with_package_manager(Some(package_manager));
103106
self
@@ -137,6 +140,7 @@ impl<'a, P> PackageGraphBuilder<'a, P> {
137140
package_jsons: self.package_jsons,
138141
lockfile: self.lockfile,
139142
package_discovery: discovery,
143+
package_manager: self.package_manager,
140144
}
141145
}
142146
}
@@ -149,14 +153,47 @@ where
149153
{
150154
/// Build the `PackageGraph`.
151155
#[tracing::instrument(skip(self))]
152-
pub async fn build(self) -> Result<PackageGraph, Error> {
156+
pub async fn build(mut self) -> Result<PackageGraph, Error> {
153157
let is_single_package = self.is_single_package;
158+
159+
// If no pre-supplied lockfile, start reading it on a blocking thread
160+
// concurrently with package discovery + JSON parsing.
161+
let known_pm = self.package_manager.take().or_else(|| {
162+
PackageManager::get_package_manager(self.repo_root, &self.root_package_json).ok()
163+
});
164+
let lockfile_future = if !is_single_package && self.lockfile.is_none() {
165+
if let Some(pm) = known_pm {
166+
let repo_root = self.repo_root.to_owned();
167+
let root_package_json = self.root_package_json.clone();
168+
Some(tokio::task::spawn_blocking(move || -> Option<Box<dyn Lockfile>> {
169+
pm.read_lockfile(&repo_root, &root_package_json).ok()
170+
}))
171+
} else {
172+
None
173+
}
174+
} else {
175+
None
176+
};
177+
154178
let state = BuildState::new(self)?;
155179

156180
match is_single_package {
157181
true => Ok(state.build_single_package_graph().await?),
158182
false => {
159183
let state = state.parse_package_jsons().await?;
184+
185+
// If we started a lockfile read, collect the result before
186+
// entering resolve_lockfile so it becomes a cache hit.
187+
let state = if let Some(handle) = lockfile_future {
188+
if let Ok(Some(lockfile)) = handle.await {
189+
state.with_lockfile(lockfile)
190+
} else {
191+
state
192+
}
193+
} else {
194+
state
195+
};
196+
160197
let state = state.resolve_lockfile().await?;
161198
Ok(state.build_inner().await?)
162199
}
@@ -220,6 +257,7 @@ where
220257
package_jsons,
221258
lockfile,
222259
package_discovery,
260+
package_manager: _,
223261
} = builder;
224262
let mut workspaces = HashMap::new();
225263
workspaces.insert(
@@ -388,6 +426,11 @@ impl<'a, T: PackageDiscovery> BuildState<'a, ResolvedPackageManager, T> {
388426
}
389427

390428
impl<'a, T: PackageDiscovery> BuildState<'a, ResolvedWorkspaces, T> {
429+
fn with_lockfile(mut self, lockfile: Box<dyn Lockfile>) -> Self {
430+
self.lockfile = Some(lockfile);
431+
self
432+
}
433+
391434
#[tracing::instrument(skip(self))]
392435
fn connect_internal_dependencies(
393436
&mut self,

0 commit comments

Comments
 (0)