From 8b347b864e650eae06685e0436b94ada655357fe Mon Sep 17 00:00:00 2001 From: willpartcl Date: Sun, 26 Apr 2026 14:06:34 -0700 Subject: [PATCH] fix: tolerate transient NotFound from concurrent build scripts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit wait_for_other_builds() walks the entire build dir looking for stable state. Cargo runs build scripts in parallel; sibling crates create and delete tempfiles inside the same dir while we walk. WalkDir surfaces those as Err(NotFound) at iteration time, and entry.unwrap() panics. The fix: treat WalkDir errors as evidence the directory is still in flux, so loop again instead of crashing. The function's invariant is 'wait until directory is stable' — a NotFound mid-walk is precisely the case where it isn't yet. locate_manifest_paths() has the same TOCTOU pattern between .exists() and .read_to_string().expect(). Replaced with match-on-read so a disappeared file becomes a graceful skip instead of a panic. Observed via partcl CI: thread 'main' panicked at inwelling-0.5.5/src/lib.rs:207:31: called `Result::unwrap()` on an `Err` value: Error { depth: 2, inner: Io { path: Some(".../partser-.../rmetanFrez5"), err: Os { code: 2, kind: NotFound, ... }}} Affects all consumers; race is non-deterministic and depends on filesystem timing. --- src/lib.rs | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7a82cfa..4454381 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -204,7 +204,19 @@ fn wait_for_other_builds( build_dir: &Path ) { thread::sleep( Duration::from_secs(5) ); waiting = false; for entry in WalkDir::new( build_dir ) { - let entry = entry.unwrap(); + // Sibling crates' build scripts can create and delete tempfiles + // inside this directory while we're walking. WalkDir surfaces + // those as Err(NotFound) at iteration time. Don't panic — the + // directory is still in flux, so just keep waiting another + // round. A NotFound here means we definitely shouldn't declare + // the build "stable" yet. + let entry = match entry { + Ok( e ) => e, + Err( _ ) => { + waiting = true; + continue; + } + }; let path = entry.path(); if generated.insert( path.to_owned() ) { waiting = true; @@ -231,15 +243,17 @@ fn locate_manifest_paths() -> HashMap> { let path = entry.path(); if path.is_dir() { let inwelling_file_path = path.join("out").join( MANIFEST_DIR_INWELLING ); - if inwelling_file_path.exists() { - let contents = fs::read_to_string( &inwelling_file_path ) - .expect( &format!( "to read {:?} to get one manifest path", inwelling_file_path )); + // The .exists() check and the subsequent read are not + // atomic — sibling builds can clean up the file between + // them. Use match-on-read instead of expect so a TOCTOU + // race becomes a graceful skip instead of a panic. + if let Ok( contents ) = fs::read_to_string( &inwelling_file_path ) { let mut lines = contents.lines(); - let manifest_dir = lines.next() - .expect( &format!( "{:?} should contain the line of manifest dir.", inwelling_file_path )); - path_bufs - .entry( PathBuf::from( manifest_dir ).join( "Cargo.toml" )) - .or_insert_with( || lines.map( ToOwned::to_owned ).collect() ); + if let Some( manifest_dir ) = lines.next() { + path_bufs + .entry( PathBuf::from( manifest_dir ).join( "Cargo.toml" )) + .or_insert_with( || lines.map( ToOwned::to_owned ).collect() ); + } }}}}} path_bufs }