Skip to content

Commit 4bc451d

Browse files
authored
Merge pull request #81 from rage/lock-fix
Lock fix
2 parents 626eccb + 599d008 commit 4bc451d

File tree

5 files changed

+412
-63
lines changed

5 files changed

+412
-63
lines changed

tmc-langs-framework/Cargo.toml

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

26+
[target.'cfg(windows)'.dependencies]
27+
winapi = "0.3"
28+
2629
[dev-dependencies]
2730
tempfile = "3"
2831
mockall = "0.9"

tmc-langs-framework/src/error.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,10 +138,15 @@ pub enum FileIo {
138138
NoFileName(PathBuf),
139139
#[error("Expected {0} to be a directory, but it was a file")]
140140
UnexpectedFile(PathBuf),
141+
#[error("Expected {0} to be a file")]
142+
UnexpectedNonFile(PathBuf),
141143

142144
#[error("Directory walk error")]
143145
Walkdir(#[from] walkdir::Error),
144146

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

tmc-langs-framework/src/file_util.rs

Lines changed: 11 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
//! Various utility functions, primarily wrapping the standard library's IO and filesystem functions
22
33
use crate::error::FileIo;
4-
use fd_lock::{FdLock, FdLockGuard};
4+
use std::fs::{self, File};
55
use std::io::{Read, Write};
66
use std::path::Path;
7-
use std::{
8-
fs::{self, File},
9-
path::PathBuf,
10-
};
117
use walkdir::WalkDir;
128

9+
#[cfg(unix)]
10+
mod lock_unix;
11+
#[cfg(unix)]
12+
pub use lock_unix::*;
13+
14+
#[cfg(windows)]
15+
mod lock_windows;
16+
#[cfg(windows)]
17+
pub use lock_windows::*;
18+
1319
pub fn temp_file() -> Result<File, FileIo> {
1420
tempfile::tempfile().map_err(FileIo::TempFile)
1521
}
@@ -167,64 +173,6 @@ pub fn copy<P: AsRef<Path>, Q: AsRef<Path>>(source: P, target: Q) -> Result<(),
167173
Ok(())
168174
}
169175

170-
#[macro_export]
171-
macro_rules! lock {
172-
( $( $path: expr ),+ ) => {
173-
$(
174-
let path_buf: PathBuf = $path.into();
175-
let mut fl = $crate::file_util::FileLock::new(path_buf)?;
176-
let _lock = fl.lock()?;
177-
)*
178-
};
179-
}
180-
// macros always live at the top-level, re-export here
181-
pub use crate::lock;
182-
183-
/// Wrapper for fd_lock::FdLock. Used to lock files/directories to prevent concurrent access
184-
/// from multiple instances of tmc-langs.
185-
// TODO: should this be in file_util or in the frontend (CLI)?
186-
pub struct FileLock {
187-
path: PathBuf,
188-
fd_lock: FdLock<File>,
189-
}
190-
191-
impl FileLock {
192-
pub fn new(path: PathBuf) -> Result<FileLock, FileIo> {
193-
let file = open_file(&path)?;
194-
Ok(Self {
195-
path,
196-
fd_lock: FdLock::new(file),
197-
})
198-
}
199-
200-
/// Blocks until the lock can be acquired.
201-
pub fn lock(&mut self) -> Result<FileLockGuard, FileIo> {
202-
log::debug!("locking {}", self.path.display());
203-
let path = &self.path;
204-
let fd_lock = &mut self.fd_lock;
205-
let guard = fd_lock
206-
.lock()
207-
.map_err(|e| FileIo::FdLock(path.clone(), e))?;
208-
log::debug!("locked {}", self.path.display());
209-
Ok(FileLockGuard {
210-
path,
211-
_guard: guard,
212-
})
213-
}
214-
}
215-
216-
#[derive(Debug)]
217-
pub struct FileLockGuard<'a> {
218-
path: &'a Path,
219-
_guard: FdLockGuard<'a, File>,
220-
}
221-
222-
impl Drop for FileLockGuard<'_> {
223-
fn drop(&mut self) {
224-
log::debug!("unlocking {}", self.path.display());
225-
}
226-
}
227-
228176
#[cfg(test)]
229177
mod test {
230178
use super::*;
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
//! File locking utilities on Unix platforms.
2+
3+
use crate::error::FileIo;
4+
use crate::file_util::*;
5+
use fd_lock::{FdLock, FdLockGuard};
6+
use std::fs::File;
7+
use std::path::PathBuf;
8+
9+
#[macro_export]
10+
macro_rules! lock {
11+
( $( $path: expr ),+ ) => {
12+
$(
13+
let path_buf: PathBuf = $path.into();
14+
let mut fl = $crate::file_util::FileLock::new(path_buf)?;
15+
let _lock = fl.lock()?;
16+
)*
17+
};
18+
}
19+
// macros always live at the top-level, re-export here
20+
pub use crate::lock;
21+
22+
/// Wrapper for fd_lock::FdLock. Used to lock files/directories to prevent concurrent access
23+
/// from multiple instances of tmc-langs.
24+
// TODO: should this be in file_util or in the frontend (CLI)?
25+
pub struct FileLock {
26+
path: PathBuf,
27+
fd_lock: FdLock<File>,
28+
}
29+
30+
impl FileLock {
31+
pub fn new(path: PathBuf) -> Result<FileLock, FileIo> {
32+
let file = open_file(&path)?;
33+
Ok(Self {
34+
path,
35+
fd_lock: FdLock::new(file),
36+
})
37+
}
38+
39+
/// Blocks until the lock can be acquired.
40+
pub fn lock(&mut self) -> Result<FileLockGuard, FileIo> {
41+
log::debug!("locking {}", self.path.display());
42+
let path = &self.path;
43+
let fd_lock = &mut self.fd_lock;
44+
let guard = fd_lock
45+
.lock()
46+
.map_err(|e| FileIo::FdLock(path.clone(), e))?;
47+
log::debug!("locked {}", self.path.display());
48+
Ok(FileLockGuard {
49+
path,
50+
_guard: guard,
51+
})
52+
}
53+
}
54+
55+
#[derive(Debug)]
56+
pub struct FileLockGuard<'a> {
57+
path: &'a Path,
58+
_guard: FdLockGuard<'a, File>,
59+
}
60+
61+
impl Drop for FileLockGuard<'_> {
62+
fn drop(&mut self) {
63+
log::debug!("unlocking {}", self.path.display());
64+
}
65+
}
66+
67+
#[cfg(test)]
68+
mod test {
69+
use super::*;
70+
use std::sync::Arc;
71+
use std::sync::Mutex;
72+
use tempfile::NamedTempFile;
73+
74+
fn init() {
75+
use log::*;
76+
use simple_logger::*;
77+
let _ = SimpleLogger::new().with_level(LevelFilter::Debug).init();
78+
}
79+
80+
#[test]
81+
fn locks_file() {
82+
init();
83+
84+
let temp = NamedTempFile::new().unwrap();
85+
let temp_path = temp.path();
86+
let mut lock = FileLock::new(temp_path.to_path_buf()).unwrap();
87+
let mutex = Arc::new(Mutex::new(vec![]));
88+
89+
// take file lock and then refcell
90+
let guard = lock.lock().unwrap();
91+
let mut mguard = mutex.try_lock().unwrap();
92+
93+
let handle = {
94+
let temp_path = temp_path.to_path_buf();
95+
let mutex = mutex.clone();
96+
97+
std::thread::spawn(move || {
98+
let mut lock = FileLock::new(temp_path).unwrap();
99+
let _guard = lock.lock().unwrap();
100+
mutex.try_lock().unwrap().push(1);
101+
})
102+
};
103+
104+
std::thread::sleep(std::time::Duration::from_millis(100));
105+
mguard.push(1);
106+
107+
drop(mguard);
108+
drop(guard);
109+
handle.join().unwrap();
110+
}
111+
112+
#[test]
113+
fn locks_dir() {
114+
init();
115+
116+
let temp = tempfile::tempdir().unwrap();
117+
let temp_path = temp.path();
118+
let mut lock = FileLock::new(temp_path.to_path_buf()).unwrap();
119+
let mutex = Arc::new(Mutex::new(vec![]));
120+
121+
// take file lock and then refcell
122+
let guard = lock.lock().unwrap();
123+
let mut mguard = mutex.try_lock().unwrap();
124+
125+
let handle = {
126+
let temp_path = temp_path.to_path_buf();
127+
let refcell = mutex.clone();
128+
129+
std::thread::spawn(move || {
130+
let mut lock = FileLock::new(temp_path).unwrap();
131+
// block on file lock and use refcell
132+
let _guard = lock.lock().unwrap();
133+
refcell.try_lock().unwrap().push(1);
134+
})
135+
};
136+
137+
// wait for the other thread to actually lock
138+
std::thread::sleep(std::time::Duration::from_millis(100));
139+
mguard.push(1);
140+
141+
// drop mutex guard then file lock
142+
drop(mguard);
143+
drop(guard);
144+
handle.join().unwrap();
145+
}
146+
}

0 commit comments

Comments
 (0)