mirror of
https://github.com/akitaonrails/ai-memory.git
synced 2026-10-02 03:24:46 +08:00
fix(wiki): retry a commit whose walk read a file that vanished mid-scan
is_racy_read only recognized libgit2's Filesystem-class "file changed before we could read it", so the Os-class "failed to read file into stream" (ENOENT: listed by the walk, gone when streamed in — e.g. an atomic writer's temp file renamed away) failed the checkpoint outright instead of taking the bounded racy-read retry. Seen as an intermittent concurrent_commits_queue_instead_of_failing failure on a loaded CI runner (v2.5.1 RC dispatch run). Classifier unit test covers both racy shapes and two non-racy controls; it fails with the old single-branch check. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
695805eb88
commit
af8ed952e7
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
- Fixed a wiki checkpoint failing instead of retrying when a full walk listed a
|
||||
file that was gone by the time it was read (libgit2's `Os`-class "failed to
|
||||
read file into stream", e.g. an atomic writer's temp file renamed away
|
||||
mid-walk). It now takes the same bounded racy-read retry as a file changed
|
||||
mid-write; unrelated I/O errors still fail fast. This was also the source of
|
||||
an intermittent `concurrent_commits_queue_instead_of_failing` CI failure.
|
||||
|
||||
## [2.5.1] - 2026-10-01
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -79,9 +79,18 @@ const UNREPORTED_SAMPLE: usize = 5;
|
||||
const RACY_READ_ATTEMPTS: u32 = 4;
|
||||
const RACY_READ_BACKOFF: Duration = Duration::from_millis(25);
|
||||
|
||||
/// libgit2's "file changed before we could read it": a writer was mid-write.
|
||||
/// A staging read that raced a writer outside the commit lock: libgit2's
|
||||
/// "file changed before we could read it" (mid-write), or a file that was
|
||||
/// listed by the walk but gone by the time libgit2 streamed it in (an `Os`
|
||||
/// "failed to read file into stream" — e.g. an atomic writer's temp file
|
||||
/// renamed away between the scan and the read). Both settle within
|
||||
/// milliseconds; a rescan no longer sees a vanished file.
|
||||
fn is_racy_read(e: &git2::Error) -> bool {
|
||||
e.class() == git2::ErrorClass::Filesystem && e.message().contains("changed before")
|
||||
match e.class() {
|
||||
git2::ErrorClass::Filesystem => e.message().contains("changed before"),
|
||||
git2::ErrorClass::Os => e.message().contains("failed to read file into stream"),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes that reached the tree without a report: a writer bypassed the wiki.
|
||||
@@ -1436,6 +1445,33 @@ mod tests {
|
||||
|
||||
/// Two session ends at once used to collide on libgit2's index lock and
|
||||
/// one of them lost its snapshot; now they queue.
|
||||
/// The CI flake behind `concurrent_commits_queue_instead_of_failing`: a
|
||||
/// walk listed a file that was gone when libgit2 read it, and libgit2
|
||||
/// reports that as an `Os`-class "failed to read file into stream" — not
|
||||
/// the "changed before" the retry recognized — so the commit failed
|
||||
/// instead of retrying. Unrelated errors must still fail fast.
|
||||
#[test]
|
||||
fn a_file_vanishing_mid_walk_is_a_racy_read_but_other_errors_are_not() {
|
||||
let err =
|
||||
|class, message: &str| git2::Error::new(git2::ErrorCode::GenericError, class, message);
|
||||
assert!(is_racy_read(&err(
|
||||
git2::ErrorClass::Os,
|
||||
"failed to read file into stream: "
|
||||
)));
|
||||
assert!(is_racy_read(&err(
|
||||
git2::ErrorClass::Filesystem,
|
||||
"file changed before we could read it"
|
||||
)));
|
||||
assert!(!is_racy_read(&err(
|
||||
git2::ErrorClass::Os,
|
||||
"failed to open file"
|
||||
)));
|
||||
assert!(!is_racy_read(&err(
|
||||
git2::ErrorClass::Index,
|
||||
"failed to read file into stream: "
|
||||
)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn concurrent_commits_queue_instead_of_failing() {
|
||||
let tmp = tempdir();
|
||||
|
||||
Reference in New Issue
Block a user