Skip to content

Commit 273d9f5

Browse files
committed
implemented lock file for windows
1 parent 4ab9cde commit 273d9f5

File tree

3 files changed

+67
-72
lines changed

3 files changed

+67
-72
lines changed

tmc-langs-framework/Cargo.toml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,6 @@ tempfile = "3"
2323
anyhow = "1"
2424
fd-lock = "2"
2525

26-
[target.'cfg(windows)'.dependencies]
27-
winapi = "0.3"
28-
2926
[dev-dependencies]
3027
tempfile = "3"
3128
mockall = "0.9"

tmc-langs-framework/src/error.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,9 @@ pub enum FileIo {
144144
#[error("Directory walk error")]
145145
Walkdir(#[from] walkdir::Error),
146146

147+
#[error("Failed to lock {0}: not a file or directory")]
148+
InvalidLockPath(PathBuf),
149+
147150
// when there is no meaningful data that can be added to an error
148151
#[error("transparent")]
149152
Generic(#[from] std::io::Error),

tmc-langs-framework/src/file_util/lock_windows.rs

Lines changed: 64 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
11
//! File locking utilities on Windows.
22
//!
33
//! Windows directories can't be locked with fd-lock, so a different solution is needed.
4-
//! Currently, regular files are locked with fd-lock, but directories are opened in exclusive mode.
5-
//! This probably means the lock needs to be used with more care; deleting a locked directory is possible on Unix but not on Windows(?).
4+
//! Currently, regular files are locked with fd-lock, but for directories a .tmc.lock file is created.
65
76
use crate::error::FileIo;
87
use crate::file_util::*;
98
use fd_lock::{FdLock, FdLockGuard};
10-
use std::fs::OpenOptions;
11-
use std::os::windows::fs::OpenOptionsExt;
129
use std::path::PathBuf;
13-
use winapi::{
14-
shared::winerror::ERROR_SHARING_VIOLATION,
15-
um::{winbase::FILE_FLAG_BACKUP_SEMANTICS, winnt::GENERIC_READ},
10+
use std::{borrow::Cow, io::ErrorKind};
11+
use std::{
12+
fs::OpenOptions,
13+
time::{Duration, Instant},
1614
};
1715

1816
#[macro_export]
@@ -47,55 +45,90 @@ impl FileLock {
4745
/// On Windows, directories cannot be locked, so we use a lock file instead.
4846
pub fn lock(&mut self) -> Result<FileLockGuard, FileIo> {
4947
log::debug!("locking {}", self.path.display());
48+
let start_time = Instant::now();
49+
let mut warning_timer = Instant::now();
5050

5151
if self.path.is_file() {
5252
// for files, just use the path
5353
let file = open_file(&self.path)?;
5454
let lock = FdLock::new(file);
5555
self.lock = Some(lock);
56-
let guard = self.lock.as_mut().unwrap().lock().unwrap();
57-
Ok(FileLockGuard::File(guard, &self.path))
56+
let lock = self.lock.as_mut().unwrap();
57+
let guard = lock.lock().unwrap();
58+
Ok(FileLockGuard {
59+
_guard: guard,
60+
path: Cow::Borrowed(&self.path),
61+
is_lock_file: false,
62+
})
5863
} else if self.path.is_dir() {
59-
// for directories, we'll continuously try opening it in exclusive access mode
64+
// for directories, we'll create/open a .tmc.lock file
65+
let lock_path = self.path.join(".tmc.lock");
6066
loop {
61-
// try to create lock file
67+
// try to create a new lock file
6268
match OpenOptions::new()
63-
.access_mode(GENERIC_READ)
64-
.share_mode(0) // exclusive access = fail if another process has the file open (locked)
65-
.custom_flags(FILE_FLAG_BACKUP_SEMANTICS)
66-
.open(&self.path)
69+
.write(true)
70+
.create_new(true)
71+
.open(&lock_path)
6772
{
68-
Ok(file) => return Ok(FileLockGuard::Dir(file, &self.path)), // succeeded in "locking" the dir
73+
Ok(file) => {
74+
// was able to create a new lock file
75+
let lock = FdLock::new(file);
76+
self.lock = Some(lock);
77+
let lock = self.lock.as_mut().unwrap();
78+
let guard = lock.lock().unwrap();
79+
return Ok(FileLockGuard {
80+
_guard: guard,
81+
path: Cow::Owned(lock_path),
82+
is_lock_file: true,
83+
});
84+
}
6985
Err(err) => {
70-
let code = err.raw_os_error().unwrap();
71-
72-
if code as u32 == ERROR_SHARING_VIOLATION {
73-
// file already opened in exclusive mode, wait for the other process
74-
std::thread::sleep(std::time::Duration::from_secs(2));
86+
if err.kind() == ErrorKind::AlreadyExists {
87+
// lock file already exists, let's wait a little and try again
88+
if start_time.elapsed() > Duration::from_secs(30)
89+
&& warning_timer.elapsed() > Duration::from_secs(10)
90+
{
91+
warning_timer = Instant::now();
92+
log::warn!(
93+
"The program has been waiting for lock file {} to be deleted for {} seconds,
94+
the lock file might have been left over from a previous run due to an error.",
95+
lock_path.display(),
96+
start_time.elapsed().as_secs()
97+
);
98+
}
99+
std::thread::sleep(Duration::from_millis(500));
75100
} else {
76-
todo!()
101+
// something else went wrong, propagate error
102+
return Err(FileIo::FileCreate(lock_path, err));
77103
}
78104
}
79105
}
80106
}
81107
} else {
82-
panic!("invalid path");
108+
return Err(FileIo::InvalidLockPath(self.path.to_path_buf()));
83109
}
84110
}
85111
}
86112

87-
pub enum FileLockGuard<'a> {
88-
File(FdLockGuard<'a, File>, &'a Path), // file locked with fd-lock
89-
Dir(File, &'a Path), // directory opened in exclusive access mode
113+
pub struct FileLockGuard<'a> {
114+
_guard: FdLockGuard<'a, File>,
115+
path: Cow<'a, PathBuf>,
116+
is_lock_file: bool,
90117
}
91118

92119
impl Drop for FileLockGuard<'_> {
93120
fn drop(&mut self) {
94-
let path = match self {
95-
Self::File(_, path) => path,
96-
Self::Dir(_, path) => path,
97-
};
98-
log::debug!("unlocking {}", path.display());
121+
log::debug!("unlocking {}", self.path.display());
122+
if self.is_lock_file {
123+
log::debug!("removing lock file");
124+
if let Err(err) = remove_file(self.path.as_ref()) {
125+
log::error!(
126+
"failed to remove lock file at {}: {}",
127+
self.path.display(),
128+
err
129+
);
130+
}
131+
}
99132
}
100133
}
101134

@@ -183,42 +216,4 @@ mod test {
183216
// wait for thread, if it panicked, it tried to lock the mutex without the file lock
184217
handle.join().unwrap();
185218
}
186-
187-
/// on windows locking the directory means we open the directory in exclusive mode
188-
/// this test is just to make sure it doesn't matter for the files inside the dir
189-
#[test]
190-
fn locking_dir_doesnt_lock_files() {
191-
init();
192-
193-
let temp = tempfile::tempdir().unwrap();
194-
let temp_path = temp.path();
195-
let file_path = temp_path.join("some file");
196-
std::fs::write(&file_path, "some contents").unwrap();
197-
let mut lock = FileLock::new(temp_path.to_path_buf()).unwrap();
198-
let mutex = Arc::new(Mutex::new(vec![]));
199-
200-
// take file lock and mutex
201-
let guard = lock.lock().unwrap();
202-
let mut mguard = mutex.try_lock().unwrap();
203-
204-
let handle = {
205-
let file_path = file_path.clone();
206-
std::thread::spawn(move || {
207-
// we try to rewrite the file from another thread
208-
std::fs::write(file_path, "new contents").unwrap();
209-
})
210-
};
211-
212-
// release locks and allow the thread to proceed
213-
std::thread::sleep(std::time::Duration::from_millis(200));
214-
mguard.push(1);
215-
216-
// release locks and allow the thread to proceed
217-
drop(mguard);
218-
drop(guard);
219-
// wait for thread, if it panicked, it tried to lock the mutex without the file lock
220-
handle.join().unwrap();
221-
222-
assert_eq!("new contents", read_file_to_string(file_path).unwrap());
223-
}
224219
}

0 commit comments

Comments
 (0)