fix(ecstore): roll back failed CAS directory fsync

Restore the previous control-file bytes, or remove a newly created file, when the Unix compare-and-update path reaches the rename but then fails to fsync the parent directory. This keeps failed metadata CAS publications from advancing recovery anchors.

Co-Authored-By: heihutu <heihutu@gmail.com>

Co-Authored-By: zhi22915 <qiuzgang@gmail.com>
This commit is contained in:
houseme
2026-09-07 11:12:12 +08:00
parent 672087ec0d
commit 022f1ecce5
2 changed files with 161 additions and 4 deletions
+20
View File
@@ -92,6 +92,9 @@ pub(crate) mod fsync_dir_recorder {
static LIMITED: Mutex<Vec<PathBuf>> = Mutex::new(Vec::new());
static GROUPED: Mutex<Vec<(PathBuf, usize)>> = Mutex::new(Vec::new());
#[cfg(unix)]
static FAILURES: std::sync::LazyLock<Mutex<HashMap<PathBuf, io::ErrorKind>>> =
std::sync::LazyLock::new(|| Mutex::new(HashMap::new()));
#[cfg(unix)]
static BEFORE_LIMITED: std::sync::LazyLock<Mutex<HashMap<PathBuf, Hook>>> =
std::sync::LazyLock::new(|| Mutex::new(HashMap::new()));
static BEFORE_GROUP_BATCH: std::sync::LazyLock<Mutex<HashMap<PathBuf, Hook>>> =
@@ -151,6 +154,19 @@ pub(crate) mod fsync_dir_recorder {
contains_path(&RECORDED.lock().expect("fsync dir recorder poisoned"), dir)
}
#[cfg(unix)]
pub(crate) fn set_failure(dir: &Path, kind: io::ErrorKind) {
FAILURES
.lock()
.expect("fsync dir failure hook poisoned")
.insert(dir.to_path_buf(), kind);
}
#[cfg(unix)]
pub(crate) fn take_failure(dir: &Path) -> Option<io::ErrorKind> {
remove_path_keyed(&FAILURES, dir, "fsync dir failure hook poisoned")
}
#[cfg(unix)]
pub(crate) fn record_limited(dir: &Path) {
record_path(&LIMITED, dir, "limited fsync dir recorder");
@@ -426,6 +442,10 @@ pub fn fsync_dir_std(dir: impl AsRef<Path>) -> io::Result<()> {
fsync_dir_recorder::record(dir.as_ref());
#[cfg(unix)]
{
#[cfg(test)]
if let Some(kind) = fsync_dir_recorder::take_failure(dir.as_ref()) {
return Err(io::Error::from(kind));
}
std::fs::File::open(dir.as_ref())?.sync_all()?;
}
#[cfg(not(unix))]