WEB Manager Session + Generation Fence tests

This commit is contained in:
Alexey
2026-09-21 21:08:34 +03:00
parent d706b3f3ba
commit 51e706770c
19 changed files with 727 additions and 578 deletions
+12 -2
View File
@@ -11,6 +11,8 @@ use nix::fcntl::{Flock, FlockArg, OFlag, openat, renameat};
use nix::sys::stat::Mode;
#[cfg(unix)]
use nix::unistd::{UnlinkatFlags, fsync, unlinkat};
#[cfg(unix)]
use tracing::warn;
use super::compute_source_revision;
use crate::api::model::ApiFailure;
@@ -75,7 +77,7 @@ impl ConfigWriteLock {
}
}
/// Replaces one config source through a durable same-directory rename.
/// Replaces one config source through a same-directory rename after syncing file data.
pub(in crate::api) async fn write_atomic(
path: PathBuf,
contents: String,
@@ -362,7 +364,15 @@ fn write_and_publish(
anchored.name(),
)
.map_err(errno_to_io)?;
fsync(anchored.parent()).map_err(errno_to_io)?;
// Rename is the commit boundary. A later directory-sync error cannot be reported as an
// uncommitted mutation because mandatory in-process publication must still run.
if let Err(error) = fsync(anchored.parent()).map_err(errno_to_io) {
warn!(
path = %path.display(),
%error,
"Config rename committed but directory durability sync failed"
);
}
Ok(committed_revision)
}
+5 -5
View File
@@ -346,13 +346,13 @@ async fn config_sidecar_lock_serializes_competing_revision_writers() {
let second = second.await.unwrap();
assert_ne!(first.is_ok(), second.is_ok());
let conflict = if let Err(error) = first {
error
} else {
second.unwrap_err()
let (winner_revision, conflict) = match (first, second) {
(Ok(revision), Err(error)) | (Err(error), Ok(revision)) => (revision, error),
_ => unreachable!("exactly one cooperative writer must commit"),
};
assert_eq!(conflict.code, "revision_conflict");
let persisted = tokio::fs::read_to_string(path).await.unwrap();
assert_eq!(winner_revision, current_revision(&path).await.unwrap());
let persisted = tokio::fs::read_to_string(&path).await.unwrap();
assert!(persisted.contains("first.example") || persisted.contains("second.example"));
}