从规避到根治:object_store fstat-after-rename 在 WSL DrvFs 上的源码级修复
这篇文章围绕一次可复现的 WSL DrvFs 写入故障,给出了从调用栈到补丁落地的完整根因闭环。复现背景与问题定位文章先复盘了此前在 write-images 场景中的 ENOENT,并说明同类问题在 write-music(10MB 级音频写入)中再次出现。通过错误栈把问题从 Lance 上层包装定位到 object…
从规避到根治:object_store fstat-after-rename 在 WSL DrvFs 上的源码级修复 前篇:WSL 挂载盘踩坑复盘:为什么 LanceDB 在 DrvFs 上会触发 metadata ENOENT,而 ext4 正常1. 背景:同一个坑,第二次掉进去 上一篇文章中,我们在 sf-cli write-images 写入大图片时遇到了 LanceDB 在 WSL DrvFs(/mnt/e/)上的 Unable to access metadata ...#1: ENOENT 错误。当时的结论是"把数据库迁到 ext4",属于规避方案。 这次,我们在开发 Music Hub 功能时,需要把 10MB 级别的 MP3 音频文件写入 LanceDB 的 songs 表(audio_data 列为 LargeBinary 类型)。数据库路径仍然在 /mnt/e/static-flow-data/lancedb-music。 毫不意外,同样的错误再次出现:Error: failed to upsert song Caused by: lance error: LanceError(IO): failed to shutdown object writer for mnt/e/static-flow-data/lancedb-music/songs.lance/data/<uuid>.lance: Generic LocalFileSystem error: Unable to access metadata for /mnt/e/static-flow-data/lancedb-music/songs.lance/data/<uuid>.lance#1: No such file or directory (os error 2) 这次我们决定不再绕路,直接追踪源码找到精确根因并修复。2. 错误栈追踪 从错误信息中提取两个关键源码位置:lance-io-1.0.0/src/object_writer.rs:306:17 lance-1.0.0/src/dataset/write/merge_insert.rs:1348:25 2.1 Lance IO 层:只是错误包装 lance-io/src/object_writer.rs:306 的 shutdown() 方法:pub async fn shutdown(&mut self) -> Result<WriteResult> { AsyncWriteExt::shutdown(self).await.map_err(|e| { Error::io( format!("failed to shutdown object writer for {}: {}", self.path, e), location!(), // <- 306行,仅包装错误 ) })?; // ... } 这里只是把底层错误包了一层。真正的失败发生在更下层的 object_store crate。2.2 object_store 层:找到精确根因 object_store-0.12.4/src/local.rs:807-826,LocalUpload::complete() 方法:async fn complete(&mut self) -> Result<PutResult> { let src = self.src.take().ok_or(Error::Aborted)?; let s = Arc::clone(&self.state); maybe_spawn_blocking(move || { let file = s.file.lock(); std::fs::rename(&src, &s.dest)?; // ① rename 临时文件 let metadata = file.metadata()?; // ② fstat(旧fd) ← 这里失败 Ok(PutResult { e_tag: Some(get_etag(&metadata)), version: None, }) }) .await } 2.3 #1 后缀的来源 同文件 local.rs:748-752:fn staged_upload_path(dest: &std::path::Path, suffix: &str) -> PathBuf { let mut staging_path = dest.as_os_str().to_owned(); staging_path.push("#"); // 添加 # 分隔符 staging_path.push(suffix); // 添加递增数字(从 1 开始…
正在初始化 WebAssembly 引擎…
首次编译原生模块可能需要数秒
就绪后,页面交互将以接近原生的速度运行