From 15d9f6c6fe3a2f806ee151455d9044e80801c346 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Sun, 13 Sep 2026 20:08:01 -0700 Subject: [PATCH] rust-volume: fix Windows build of find_needle_from_ecx (#11298) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * rust-volume: fix Windows build of find_needle_from_ecx The .ecx binary-search fallback path used on non-Unix targets (Seek + Read, both &mut self receivers) requires the ecx_file binding to be mutable. On Unix the read_exact_at path takes &self, so the mut would be unused there — gate that warning with #[cfg_attr(unix, allow(unused_mut))]. Without this the build-rust-volume-windows CI job fails with E0596 at ec_volume.rs:1033, breaking the weed-volume_windows_amd64 release asset. * rust-volume: use positional seek_read for .ecx lookups on Windows The previous fix (making ecx_file mut) compiled but left the Windows fallback using Seek + Read on the shared .ecx file cursor. Concurrent find_needle_from_ecx calls could interleave seek/read and read the wrong index entry, corrupting the binary search (raised by Devin and Greptile review on the PR). Switch the Windows path to std::os::windows::fs::FileExt::seek_read, which is positional (offset passed via OVERLAPPED, cursor untouched) and takes &self — so the binding no longer needs mut, and concurrent callers on the cached handle can't interfere. Mirrors the existing read_exact_at helper in storage::volume. Add a compile_error fallback for non-unix/non-windows targets to match the convention in storage::volume. --- .../src/storage/erasure_coding/ec_volume.rs | 41 +++++++++++++++---- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/seaweed-volume/src/storage/erasure_coding/ec_volume.rs b/seaweed-volume/src/storage/erasure_coding/ec_volume.rs index 595ae2328..2b951fec0 100644 --- a/seaweed-volume/src/storage/erasure_coding/ec_volume.rs +++ b/seaweed-volume/src/storage/erasure_coding/ec_volume.rs @@ -1057,18 +1057,21 @@ impl EcVolume { return Err(e); } } - #[cfg(not(unix))] + #[cfg(windows)] { - use std::io::{Read, Seek, SeekFrom}; - if let Err(e) = ecx_file.seek(SeekFrom::Start(file_offset)) { - self.check_read_write_error(Some(&e)); - return Err(e); - } - if let Err(e) = ecx_file.read_exact(&mut entry_buf) { + // Positional read so concurrent find_needle_from_ecx calls on + // the shared .ecx handle don't interleave seek/read and corrupt + // each other's binary search. Mirrors the read_exact_at helper + // in storage::volume. + if let Err(e) = read_exact_at(ecx_file, &mut entry_buf, file_offset) { self.check_read_write_error(Some(&e)); return Err(e); } } + #[cfg(not(any(unix, windows)))] + { + compile_error!("Platform not supported: only unix and windows are supported"); + } let (key, offset, size) = idx_entry_from_bytes(&entry_buf); if key == needle_id { @@ -4407,3 +4410,27 @@ impl EcLocalScrubPlan { (count, broken, errs) } } + +/// Windows helper: loop `seek_read` until the buffer is fully filled. +/// +/// `seek_read` is positional (it passes the offset through `OVERLAPPED` and +/// never touches the shared file cursor), so concurrent callers reading the +/// same `&File` — as `find_needle_from_ecx` does on the cached `.ecx` handle — +/// can't interleave their reads. Mirrors the helper in `storage::volume`. +#[cfg(windows)] +fn read_exact_at(file: &File, buf: &mut [u8], mut offset: u64) -> io::Result<()> { + use std::os::windows::fs::FileExt; + let mut filled = 0; + while filled < buf.len() { + let n = file.seek_read(&mut buf[filled..], offset)?; + if n == 0 { + return Err(io::Error::new( + io::ErrorKind::UnexpectedEof, + "unexpected EOF in seek_read", + )); + } + filled += n; + offset += n as u64; + } + Ok(()) +}