diff --git a/Cargo.toml b/Cargo.toml index c95170b..b7f189b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ walkdir = "2.3.2" # NOTE: we don't depend on this crate but we need to activate this feature otherwise it's super slow walrus = { version = "0.26.1", features = ["parallel"] } wasm-bindgen-cli-support = "0.2.100" -xtask-watch = "0.3.2" +xtask-watch = "0.3.3" [target.'cfg(unix)'.dependencies] libc = "0.2.112" diff --git a/src/dev_server.rs b/src/dev_server.rs index 98fee69..b25b13d 100644 --- a/src/dev_server.rs +++ b/src/dev_server.rs @@ -13,6 +13,7 @@ use std::{ sync::Arc, thread, }; +use xtask_watch::WatchLock; type RequestHandler = Arc Result<()> + Send + Sync + 'static>; @@ -352,6 +353,8 @@ impl DevServer { } let dist_dir = self.dist_dir.clone().unwrap(); + let watch_lock = self.watch.lock(); + let watch_process = { // mem::take so we can pass &self to build_command while the fields are empty. let pre_hooks = std::mem::take(&mut self.pre_hooks); @@ -373,7 +376,8 @@ impl DevServer { format!("cannot create dist directory `{}`", dist_dir.display()) })?; let watch = self.watch.exclude_path(&dist_dir); - let handle = std::thread::spawn(|| match watch.run(commands) { + + let handle = std::thread::spawn(move || match watch.run(commands) { Ok(()) => log::trace!("Starting to watch"), Err(err) => log::error!("an error occurred when starting to watch: {err}"), }); @@ -385,8 +389,15 @@ impl DevServer { }; if let Some(handler) = self.request_handler { - serve(self.ip, self.port, dist_dir, self.not_found_path, handler) - .context("an error occurred when starting to serve")?; + serve( + self.ip, + self.port, + dist_dir, + self.not_found_path, + handler, + watch_lock, + ) + .context("an error occurred when starting to serve")?; } else { serve( self.ip, @@ -394,6 +405,7 @@ impl DevServer { dist_dir, self.not_found_path, Arc::new(default_request_handler), + watch_lock, ) .context("an error occurred when starting to serve")?; } @@ -428,6 +440,7 @@ fn serve( dist_dir: PathBuf, not_found_path: Option, handler: RequestHandler, + watch_lock: WatchLock, ) -> Result<()> { let address = SocketAddr::new(ip, port); let listener = TcpListener::bind(address).context("cannot bind to the given address")?; @@ -450,8 +463,14 @@ fn serve( let handler = handler.clone(); let dist_dir = dist_dir.clone(); let not_found_path = not_found_path.clone(); + let watch_lock = watch_lock.clone(); thread::spawn(move || { + // Read the request header *before* acquiring the watch lock so that connections + // can be accepted and parsed while a rebuild is in progress. This reduces + // perceived latency: the response is dispatched immediately once the build + // finishes rather than having to re-parse the header afterward. let header = warn_not_fail!(read_header(&stream)); + let _guard = watch_lock.acquire(); let request = Request { stream: &mut stream, header: header.as_ref(), diff --git a/src/lib.rs b/src/lib.rs index aa7c260..4e11e17 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -306,6 +306,7 @@ use std::process::Command; #[cfg(not(target_arch = "wasm32"))] pub use xtask_watch::{ anyhow, cargo_metadata, cargo_metadata::camino, clap, metadata, package, xtask_command, Watch, + WatchLock, WatchLockGuard, }; #[cfg(not(target_arch = "wasm32"))]