#[cfg(windows)]
use std::path::Path;
#[cfg(windows)]
use anyhow::Result;
#[cfg(windows)]
use tempfile::NamedTempFile;
#[cfg(windows)]
use crate::error::AtomwriteError;
#[cfg(windows)]
use crate::retry::{RetryFailure, RetryPolicy, RetryStop, Step, retry};
#[cfg(windows)]
fn is_transient_persist_error(e: &std::io::Error) -> bool {
e.kind() == std::io::ErrorKind::PermissionDenied
|| e.raw_os_error() == Some(crate::constants::WINDOWS_ERROR_SHARING_VIOLATION)
}
#[cfg(windows)]
pub(crate) fn persist_with_retry(temp: NamedTempFile, target: &Path) -> Result<()> {
let policy = RetryPolicy::new(
crate::constants::PERSIST_RETRY_BASE_MS,
crate::constants::PERSIST_RETRY_CAP_MS,
crate::constants::PERSIST_RETRY_MAX_ELAPSED_MS,
crate::constants::PERSIST_RETRY_MAX_ATTEMPTS,
);
let mut slot = Some(temp);
let outcome = retry(policy, || {
let Some(file) = slot.take() else {
return Step::Fail(std::io::Error::other(
"tempfile lost between persist attempts",
));
};
match file.persist(target) {
Ok(_) => Step::Done(()),
Err(e) => {
let transient = is_transient_persist_error(&e.error);
slot = Some(e.file);
if transient {
Step::Retry(e.error)
} else {
Step::Fail(e.error)
}
}
}
});
match outcome {
Ok(()) => Ok(()),
Err(RetryFailure::Permanent(e)) => Err(crate::error::AtomwriteError::from_io_at(
target,
std::io::Error::new(
e.kind(),
format!("rename error for {}: {e}", target.display()),
),
)
.into()),
Err(RetryFailure::Stopped {
stop: RetryStop::Cancelled,
..
}) => Err(crate::signal::cancelled_error(format!(
"persist wait cancelled for {}",
target.display()
))
.into()),
Err(RetryFailure::Stopped { .. }) => Err(AtomwriteError::PermissionDenied {
path: target.to_path_buf(),
}
.into()),
}
}