Skip to content

Commit dc2d779

Browse files
authored
fix(bundle): don't panic when esbuild binary is busy or unavailable (#34845)
Fixes #30079
1 parent 3a9c991 commit dc2d779

2 files changed

Lines changed: 39 additions & 14 deletions

File tree

cli/tools/bundle/esbuild.rs

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -95,21 +95,41 @@ pub async fn ensure_esbuild(
9595
package_folder.join("bin").join("esbuild")
9696
};
9797

98-
std::fs::create_dir_all(esbuild_path.parent().unwrap()).with_context(
99-
|| {
100-
format!(
101-
"failed to create directory {}",
102-
esbuild_path.parent().unwrap().display()
103-
)
104-
},
105-
)?;
106-
std::fs::copy(&path, &esbuild_path).with_context(|| {
98+
let esbuild_dir = esbuild_path.parent().unwrap();
99+
std::fs::create_dir_all(esbuild_dir).with_context(|| {
100+
format!("failed to create directory {}", esbuild_dir.display())
101+
})?;
102+
// Install the binary atomically: copy to a temporary file in the same
103+
// directory and then rename it into place. The rename is atomic, so a
104+
// concurrent `deno bundle` process never observes (via the `exists()`
105+
// check above) and tries to execute a half-written binary, which would
106+
// otherwise fail with ETXTBSY ("text file busy") on Linux.
107+
let tmp_path =
108+
esbuild_dir.join(format!(".esbuild-{}.tmp", std::process::id()));
109+
std::fs::copy(&path, &tmp_path).with_context(|| {
107110
format!(
108111
"failed to copy esbuild binary from {} to {}",
109112
path.display(),
110-
esbuild_path.display()
113+
tmp_path.display()
111114
)
112115
})?;
116+
match std::fs::rename(&tmp_path, &esbuild_path) {
117+
Ok(()) => {}
118+
// Another process won the race and installed the binary already; our
119+
// copy is redundant, so just discard it.
120+
Err(_) if esbuild_path.exists() => {
121+
let _ = std::fs::remove_file(&tmp_path);
122+
}
123+
Err(err) => {
124+
let _ = std::fs::remove_file(&tmp_path);
125+
return Err(err).with_context(|| {
126+
format!(
127+
"failed to move esbuild binary into place at {}",
128+
esbuild_path.display()
129+
)
130+
});
131+
}
132+
}
113133

114134
if !existed {
115135
let _ = std::fs::remove_dir_all(&package_folder).inspect_err(|e| {

cli/tools/bundle/mod.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ use deno_bundle_runtime::BundlePlatform;
2525
use deno_bundle_runtime::PackageHandling;
2626
use deno_bundle_runtime::SourceMapType;
2727
use deno_config::workspace::TsTypeLib;
28+
use deno_core::anyhow::Context;
2829
use deno_core::error::AnyError;
2930
use deno_core::futures::FutureExt as _;
3031
use deno_core::parking_lot::Mutex;
@@ -268,7 +269,7 @@ pub async fn bundle_init(
268269
Default::default(),
269270
)
270271
.await
271-
.unwrap();
272+
.context("failed to start esbuild")?;
272273
let client = esbuild.client().clone();
273274

274275
tokio::spawn(async move {
@@ -668,7 +669,7 @@ impl EsbuildBundler {
668669
.client
669670
.send_build_request(self.make_build_request())
670671
.await
671-
.unwrap()
672+
.context("failed to send build request to esbuild")?
672673
.map_err(|e| message_to_error(&e, &self.cwd))?;
673674

674675
Ok(response)
@@ -685,9 +686,13 @@ impl EsbuildBundler {
685686
.client
686687
.send_rebuild_request(0)
687688
.await
688-
.unwrap()
689+
.context("failed to send rebuild request to esbuild")?
689690
.map_err(|e| message_to_error(&e, &self.cwd))?;
690-
let response = self.on_end_rx.recv().await.unwrap();
691+
let response = self.on_end_rx.recv().await.ok_or_else(|| {
692+
deno_core::anyhow::anyhow!(
693+
"esbuild exited before the rebuild completed"
694+
)
695+
})?;
691696
Ok(response.into())
692697
}
693698
}

0 commit comments

Comments
 (0)