Skip to content

Commit c149c60

Browse files
authored
Merge pull request GitoxideLabs#2419 from GitoxideLabs/copilot/fix-retry-logic-windows
Add Windows retry logic for tempfile persist operations
2 parents f4f9e02 + 816d45f commit c149c60

1 file changed

Lines changed: 60 additions & 0 deletions

File tree

gix-tempfile/src/forksafe.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,66 @@ impl ForksafeTempfile {
5151
self.persist_inner(path.as_ref())
5252
}
5353

54+
#[cfg(windows)]
55+
fn persist_inner(mut self, path: &Path) -> Result<Option<std::fs::File>, (std::io::Error, Self)> {
56+
/// Maximum number of attempts for Windows file locking issues.
57+
/// Matches libgit2's default retry count.
58+
const MAX_ATTEMPTS: usize = 10;
59+
/// Delay between retry attempts in milliseconds.
60+
/// Matches libgit2's retry delay.
61+
const RETRY_DELAY_MS: u64 = 5;
62+
63+
fn should_retry(err: &std::io::Error) -> bool {
64+
use std::io::ErrorKind;
65+
// Access denied (ERROR_ACCESS_DENIED = 5) or sharing violation (ERROR_SHARING_VIOLATION = 32)
66+
// are the common errors when external processes like antivirus or file watchers hold the file.
67+
matches!(err.kind(), ErrorKind::PermissionDenied) || err.raw_os_error() == Some(32)
68+
// ERROR_SHARING_VIOLATION
69+
}
70+
71+
match self.inner {
72+
TempfileOrTemppath::Tempfile(file) => {
73+
let mut current_file = file;
74+
for attempt in 0..MAX_ATTEMPTS {
75+
match current_file.persist(path) {
76+
Ok(file) => return Ok(Some(file)),
77+
Err(err) if attempt + 1 < MAX_ATTEMPTS && should_retry(&err.error) => {
78+
std::thread::sleep(std::time::Duration::from_millis(RETRY_DELAY_MS));
79+
current_file = err.file;
80+
}
81+
Err(err) => {
82+
return Err((err.error, {
83+
self.inner = TempfileOrTemppath::Tempfile(err.file);
84+
self
85+
}))
86+
}
87+
}
88+
}
89+
unreachable!("loop always returns")
90+
}
91+
TempfileOrTemppath::Temppath(temppath) => {
92+
let mut current_path = temppath;
93+
for attempt in 0..MAX_ATTEMPTS {
94+
match current_path.persist(path) {
95+
Ok(_) => return Ok(None),
96+
Err(err) if attempt + 1 < MAX_ATTEMPTS && should_retry(&err.error) => {
97+
std::thread::sleep(std::time::Duration::from_millis(RETRY_DELAY_MS));
98+
current_path = err.path;
99+
}
100+
Err(err) => {
101+
return Err((err.error, {
102+
self.inner = TempfileOrTemppath::Temppath(err.path);
103+
self
104+
}))
105+
}
106+
}
107+
}
108+
unreachable!("loop always returns")
109+
}
110+
}
111+
}
112+
113+
#[cfg(not(windows))]
54114
fn persist_inner(mut self, path: &Path) -> Result<Option<std::fs::File>, (std::io::Error, Self)> {
55115
match self.inner {
56116
TempfileOrTemppath::Tempfile(file) => match file.persist(path) {

0 commit comments

Comments
 (0)