cultcache-rs: revert the absolute() destination (a relative FileName works with a null RootDirectory); a delete-sharing reader test discriminates POSIX rename; doc names the ruling and where the table has landed
Some checks failed
CultMesh portability and performance / ubuntu-latest reactive semantics and leases (pull_request) Has been cancelled

Soul's Starfire probe (build 26200) showed a relative FileName already works, so the reverted
claim was false. windows_replace_tests are compile-checked only and have never run on Windows.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Libby 2026-11-12 22:26:33 +00:00
commit 1d2ee73ce8

View file

@ -4391,8 +4391,8 @@ const MOVEFILE_REPLACE_WRITE_THROUGH: u32 = 0x9;
/// for the rest of the call; and a bounded retry on access, sharing and lock violations.
/// A concurrent reader (another process, an indexer, an antivirus scan) makes the replace fail
/// transiently, and a daemon that treats that as fatal dies on the first overlap (Muninn serve
/// did, 2026-09-11). The same table ships in cultcache-ts and cultcache-cs
/// (`variants:ruling:windows-replace-posix-first`).
/// did, 2026-09-11). Ruling `variants:ruling:windows-replace-posix-first`: cultcache-ts carries the
/// same table on main; cultcache-cs carries it only once its pending PR lands.
#[cfg_attr(not(windows), allow(dead_code))]
fn replace_with(kernel: &dyn ReplaceKernel, staged: &Path, destination: &Path) -> Result<()> {
const ERROR_INVALID_FUNCTION: u32 = 1;
@ -4457,9 +4457,7 @@ impl ReplaceKernel for WindowsKernel {
};
let staged_wide = wide(staged);
// FileName with a null RootDirectory must be a full path; MoveFileExW takes a relative one.
let destination_wide =
wide(&std::path::absolute(destination).unwrap_or_else(|_| destination.to_path_buf()));
let destination_wide = wide(destination);
// FILE_RENAME_INFO ends in a variable-length name: allocate it as u64s for alignment.
let name_bytes = (destination_wide.len() - 1) * 2;
let header = std::mem::size_of::<FILE_RENAME_INFO>();
@ -4644,6 +4642,35 @@ mod windows_replace_tests {
use super::replace_file_atomically;
use std::os::windows::fs::OpenOptionsExt;
const FILE_SHARE_ALL: u32 = 0x1 | 0x2 | 0x4; // READ | WRITE | DELETE
/// A reader that shares delete holds the old store open. A POSIX-semantics rename
/// replaces the name while the reader keeps the old content; MoveFileExW alone fails
/// there with error 5 (Soul's Starfire probe, build 26200), so this test passes only
/// if the POSIX path is live. Compile-checked only: it has never run on Windows.
#[test]
fn a_replace_succeeds_over_a_delete_sharing_reader_which_keeps_the_old_content() {
use std::io::Read;
let dir = std::env::temp_dir().join(format!("cultcache-posix-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let destination = dir.join("store.cc");
let staged = dir.join("store.cc.tmp");
std::fs::write(&destination, b"old").unwrap();
std::fs::write(&staged, b"new").unwrap();
let mut reader = std::fs::OpenOptions::new()
.read(true)
.share_mode(FILE_SHARE_ALL)
.open(&destination)
.unwrap();
replace_file_atomically(&staged, &destination).expect("POSIX rename over a sharing reader");
let mut seen = Vec::new();
reader.read_to_end(&mut seen).unwrap();
assert_eq!(seen, b"old", "the reader keeps the old content");
assert_eq!(std::fs::read(&destination).unwrap(), b"new");
drop(reader);
let _ = std::fs::remove_dir_all(&dir);
}
/// A reader holding the destination open with no sharing makes the
/// replace fail for as long as it holds on; it must succeed once the
/// reader lets go, not report the first refusal as the outcome.