Skip to content

Commit 8aa6334

Browse files
committed
Implement file_lock feature
This adds lock(), lock_shared(), try_lock(), try_lock_shared(), and unlock() to File gated behind the file_lock feature flag
1 parent a1fd235 commit 8aa6334

File tree

10 files changed

+458
-0
lines changed

10 files changed

+458
-0
lines changed

library/std/src/fs.rs

+175
Original file line numberDiff line numberDiff line change
@@ -615,6 +615,181 @@ impl File {
615615
self.inner.datasync()
616616
}
617617

618+
/// Acquire an exclusive advisory lock on the file. Blocks until the lock can be acquired.
619+
///
620+
/// This acquires an exclusive advisory lock; no other file handle to this file may acquire
621+
/// another lock.
622+
///
623+
/// If this file handle already holds an advisory lock the exact behavior is
624+
/// unspecified and platform dependent, including the possibility that it will deadlock.
625+
/// However, if this method returns, then an exclusive lock is held.
626+
///
627+
/// If the file not open for writing, it is unspecified whether this function returns an error.
628+
///
629+
/// Note, this is an advisory lock meant to interact with [`lock_shared`], [`try_lock`],
630+
/// [`try_lock_shared`], and [`unlock`]. Its interactions with other methods, such as [`read`]
631+
/// and [`write`] are platform specific, and it may or may not cause non-lockholders to block.
632+
///
633+
/// [`lock_shared`]: File::lock_shared
634+
/// [`try_lock`]: File::try_lock
635+
/// [`try_lock_shared`]: File::try_lock_shared
636+
/// [`unlock`]: File::unlock
637+
/// [`read`]: Read::read
638+
/// [`write`]: Write::write
639+
///
640+
/// # Examples
641+
///
642+
/// ```no_run
643+
/// #![feature(file_lock)]
644+
/// use std::fs::File;
645+
///
646+
/// fn main() -> std::io::Result<()> {
647+
/// let f = File::open("foo.txt")?;
648+
/// f.lock()?;
649+
/// Ok(())
650+
/// }
651+
/// ```
652+
#[unstable(feature = "file_lock", issue = "130994")]
653+
pub fn lock(&self) -> io::Result<()> {
654+
self.inner.lock()
655+
}
656+
657+
/// Acquire a shared advisory lock on the file. Blocks until the lock can be acquired.
658+
///
659+
/// This acquires a shared advisory lock; more than one file handle may hold a shared lock, but
660+
/// none may hold an exclusive lock.
661+
///
662+
/// If this file handle already holds an advisory lock, the exact behavior is unspecified and
663+
/// platform dependent, including the possibility that it will deadlock.
664+
/// However, if this method returns, then a shared lock is held.
665+
///
666+
/// Note, this is an advisory lock meant to interact with [`lock`], [`try_lock`],
667+
/// [`try_lock_shared`], and [`unlock`]. Its interactions with other methods, such as [`read`]
668+
/// and [`write`] are platform specific, and it may or may not cause non-lockholders to block.
669+
///
670+
/// [`lock`]: File::lock
671+
/// [`try_lock`]: File::try_lock
672+
/// [`try_lock_shared`]: File::try_lock_shared
673+
/// [`unlock`]: File::unlock
674+
/// [`read`]: Read::read
675+
/// [`write`]: Write::write
676+
///
677+
/// # Examples
678+
///
679+
/// ```no_run
680+
/// #![feature(file_lock)]
681+
/// use std::fs::File;
682+
///
683+
/// fn main() -> std::io::Result<()> {
684+
/// let f = File::open("foo.txt")?;
685+
/// f.lock_shared()?;
686+
/// Ok(())
687+
/// }
688+
/// ```
689+
#[unstable(feature = "file_lock", issue = "130994")]
690+
pub fn lock_shared(&self) -> io::Result<()> {
691+
self.inner.lock_shared()
692+
}
693+
694+
/// Acquire an exclusive advisory lock on the file. Returns `Ok(false)` if the file is locked.
695+
///
696+
/// This acquires an exclusive advisory lock; no other file handle to this file may acquire
697+
/// another lock.
698+
///
699+
/// If this file handle already holds an advisory lock, the exact behavior is
700+
/// unspecified and platform dependent, including the possibility that it will deadlock.
701+
/// However, if this method returns, then an exclusive lock is held.
702+
///
703+
/// If the file not open for writing, it is unspecified whether this function returns an error.
704+
///
705+
/// Note, this is an advisory lock meant to interact with [`lock`], [`lock_shared`],
706+
/// [`try_lock_shared`], and [`unlock`]. Its interactions with other methods, such as [`read`]
707+
/// and [`write`] are platform specific, and it may or may not cause non-lockholders to block.
708+
///
709+
/// [`lock`]: File::lock
710+
/// [`lock_shared`]: File::lock_shared
711+
/// [`try_lock_shared`]: File::try_lock_shared
712+
/// [`unlock`]: File::unlock
713+
/// [`read`]: Read::read
714+
/// [`write`]: Write::write
715+
///
716+
/// # Examples
717+
///
718+
/// ```no_run
719+
/// #![feature(file_lock)]
720+
/// use std::fs::File;
721+
///
722+
/// fn main() -> std::io::Result<()> {
723+
/// let f = File::open("foo.txt")?;
724+
/// f.try_lock()?;
725+
/// Ok(())
726+
/// }
727+
/// ```
728+
#[unstable(feature = "file_lock", issue = "130994")]
729+
pub fn try_lock(&self) -> io::Result<bool> {
730+
self.inner.try_lock()
731+
}
732+
733+
/// Acquire a shared advisory lock on the file.
734+
/// Returns `Ok(false)` if the file is exclusively locked.
735+
///
736+
/// This acquires a shared advisory lock; more than one file handle may hold a shared lock, but
737+
/// none may hold an exclusive lock.
738+
///
739+
/// If this file handle already holds an advisory lock, the exact behavior is unspecified and
740+
/// platform dependent, including the possibility that it will deadlock.
741+
/// However, if this method returns, then a shared lock is held.
742+
///
743+
/// Note, this is an advisory lock meant to interact with [`lock`], [`try_lock`],
744+
/// [`try_lock`], and [`unlock`]. Its interactions with other methods, such as [`read`]
745+
/// and [`write`] are platform specific, and it may or may not cause non-lockholders to block.
746+
///
747+
/// [`lock`]: File::lock
748+
/// [`lock_shared`]: File::lock_shared
749+
/// [`try_lock`]: File::try_lock
750+
/// [`unlock`]: File::unlock
751+
/// [`read`]: Read::read
752+
/// [`write`]: Write::write
753+
///
754+
/// # Examples
755+
///
756+
/// ```no_run
757+
/// #![feature(file_lock)]
758+
/// use std::fs::File;
759+
///
760+
/// fn main() -> std::io::Result<()> {
761+
/// let f = File::open("foo.txt")?;
762+
/// f.try_lock_shared()?;
763+
/// Ok(())
764+
/// }
765+
/// ```
766+
#[unstable(feature = "file_lock", issue = "130994")]
767+
pub fn try_lock_shared(&self) -> io::Result<bool> {
768+
self.inner.try_lock_shared()
769+
}
770+
771+
/// Release all locks on the file.
772+
///
773+
/// All remaining locks are released when the file handle, and all clones of it, are dropped.
774+
///
775+
/// # Examples
776+
///
777+
/// ```no_run
778+
/// #![feature(file_lock)]
779+
/// use std::fs::File;
780+
///
781+
/// fn main() -> std::io::Result<()> {
782+
/// let f = File::open("foo.txt")?;
783+
/// f.lock()?;
784+
/// f.unlock()?;
785+
/// Ok(())
786+
/// }
787+
/// ```
788+
#[unstable(feature = "file_lock", issue = "130994")]
789+
pub fn unlock(&self) -> io::Result<()> {
790+
self.inner.unlock()
791+
}
792+
618793
/// Truncates or extends the underlying file, updating the size of
619794
/// this file to become `size`.
620795
///

library/std/src/fs/tests.rs

+80
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,86 @@ fn file_test_io_seek_and_write() {
203203
assert!(read_str == final_msg);
204204
}
205205

206+
#[test]
207+
fn file_lock_multiple_shared() {
208+
let tmpdir = tmpdir();
209+
let filename = &tmpdir.join("file_lock_multiple_shared_test.txt");
210+
let f1 = check!(File::create(filename));
211+
let f2 = check!(OpenOptions::new().write(true).open(filename));
212+
213+
// Check that we can acquire concurrent shared locks
214+
check!(f1.lock_shared());
215+
check!(f2.lock_shared());
216+
check!(f1.unlock());
217+
check!(f2.unlock());
218+
assert!(check!(f1.try_lock_shared()));
219+
assert!(check!(f2.try_lock_shared()));
220+
}
221+
222+
#[test]
223+
fn file_lock_blocking() {
224+
let tmpdir = tmpdir();
225+
let filename = &tmpdir.join("file_lock_blocking_test.txt");
226+
let f1 = check!(File::create(filename));
227+
let f2 = check!(OpenOptions::new().write(true).open(filename));
228+
229+
// Check that shared locks block exclusive locks
230+
check!(f1.lock_shared());
231+
assert!(!check!(f2.try_lock()));
232+
check!(f1.unlock());
233+
234+
// Check that exclusive locks block shared locks
235+
check!(f1.lock());
236+
assert!(!check!(f2.try_lock_shared()));
237+
}
238+
239+
#[test]
240+
fn file_lock_drop() {
241+
let tmpdir = tmpdir();
242+
let filename = &tmpdir.join("file_lock_dup_test.txt");
243+
let f1 = check!(File::create(filename));
244+
let f2 = check!(OpenOptions::new().write(true).open(filename));
245+
246+
// Check that locks are released when the File is dropped
247+
check!(f1.lock_shared());
248+
assert!(!check!(f2.try_lock()));
249+
drop(f1);
250+
assert!(check!(f2.try_lock()));
251+
}
252+
253+
#[test]
254+
fn file_lock_dup() {
255+
let tmpdir = tmpdir();
256+
let filename = &tmpdir.join("file_lock_dup_test.txt");
257+
let f1 = check!(File::create(filename));
258+
let f2 = check!(OpenOptions::new().write(true).open(filename));
259+
260+
// Check that locks are not dropped if the File has been cloned
261+
check!(f1.lock_shared());
262+
assert!(!check!(f2.try_lock()));
263+
let cloned = check!(f1.try_clone());
264+
drop(f1);
265+
assert!(!check!(f2.try_lock()));
266+
drop(cloned)
267+
}
268+
269+
#[test]
270+
#[cfg(windows)]
271+
fn file_lock_double_unlock() {
272+
let tmpdir = tmpdir();
273+
let filename = &tmpdir.join("file_lock_double_unlock_test.txt");
274+
let f1 = check!(File::create(filename));
275+
let f2 = check!(OpenOptions::new().write(true).open(filename));
276+
277+
// On Windows a file handle may acquire both a shared and exclusive lock.
278+
// Check that both are released by unlock()
279+
check!(f1.lock());
280+
check!(f1.lock_shared());
281+
assert!(!check!(f2.try_lock()));
282+
check!(f1.unlock());
283+
assert!(check!(f2.try_lock()));
284+
}
285+
206286
#[test]
207287
fn file_test_io_seek_shakedown() {
208288
// 01234567890123

library/std/src/sys/pal/hermit/fs.rs

+20
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,26 @@ impl File {
364364
self.fsync()
365365
}
366366

367+
pub fn lock(&self) -> io::Result<()> {
368+
unsupported()
369+
}
370+
371+
pub fn lock_shared(&self) -> io::Result<()> {
372+
unsupported()
373+
}
374+
375+
pub fn try_lock(&self) -> io::Result<bool> {
376+
unsupported()
377+
}
378+
379+
pub fn try_lock_shared(&self) -> io::Result<bool> {
380+
unsupported()
381+
}
382+
383+
pub fn unlock(&self) -> io::Result<()> {
384+
unsupported()
385+
}
386+
367387
pub fn truncate(&self, _size: u64) -> io::Result<()> {
368388
Err(Error::from_raw_os_error(22))
369389
}

library/std/src/sys/pal/solid/fs.rs

+20
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,26 @@ impl File {
350350
self.flush()
351351
}
352352

353+
pub fn lock(&self) -> io::Result<()> {
354+
unsupported()
355+
}
356+
357+
pub fn lock_shared(&self) -> io::Result<()> {
358+
unsupported()
359+
}
360+
361+
pub fn try_lock(&self) -> io::Result<bool> {
362+
unsupported()
363+
}
364+
365+
pub fn try_lock_shared(&self) -> io::Result<bool> {
366+
unsupported()
367+
}
368+
369+
pub fn unlock(&self) -> io::Result<()> {
370+
unsupported()
371+
}
372+
353373
pub fn truncate(&self, _size: u64) -> io::Result<()> {
354374
unsupported()
355375
}

library/std/src/sys/pal/unix/fs.rs

+37
Original file line numberDiff line numberDiff line change
@@ -1254,6 +1254,43 @@ impl File {
12541254
}
12551255
}
12561256

1257+
pub fn lock(&self) -> io::Result<()> {
1258+
cvt(unsafe { libc::flock(self.as_raw_fd(), libc::LOCK_EX) })?;
1259+
return Ok(());
1260+
}
1261+
1262+
pub fn lock_shared(&self) -> io::Result<()> {
1263+
cvt(unsafe { libc::flock(self.as_raw_fd(), libc::LOCK_SH) })?;
1264+
return Ok(());
1265+
}
1266+
1267+
pub fn try_lock(&self) -> io::Result<bool> {
1268+
let result = cvt(unsafe { libc::flock(self.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB) });
1269+
if let Err(ref err) = result {
1270+
if err.kind() == io::ErrorKind::WouldBlock {
1271+
return Ok(false);
1272+
}
1273+
}
1274+
result?;
1275+
return Ok(true);
1276+
}
1277+
1278+
pub fn try_lock_shared(&self) -> io::Result<bool> {
1279+
let result = cvt(unsafe { libc::flock(self.as_raw_fd(), libc::LOCK_SH | libc::LOCK_NB) });
1280+
if let Err(ref err) = result {
1281+
if err.kind() == io::ErrorKind::WouldBlock {
1282+
return Ok(false);
1283+
}
1284+
}
1285+
result?;
1286+
return Ok(true);
1287+
}
1288+
1289+
pub fn unlock(&self) -> io::Result<()> {
1290+
cvt(unsafe { libc::flock(self.as_raw_fd(), libc::LOCK_UN) })?;
1291+
return Ok(());
1292+
}
1293+
12571294
pub fn truncate(&self, size: u64) -> io::Result<()> {
12581295
let size: off64_t =
12591296
size.try_into().map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;

library/std/src/sys/pal/unsupported/fs.rs

+20
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,26 @@ impl File {
198198
self.0
199199
}
200200

201+
pub fn lock(&self) -> io::Result<()> {
202+
self.0
203+
}
204+
205+
pub fn lock_shared(&self) -> io::Result<()> {
206+
self.0
207+
}
208+
209+
pub fn try_lock(&self) -> io::Result<bool> {
210+
self.0
211+
}
212+
213+
pub fn try_lock_shared(&self) -> io::Result<bool> {
214+
self.0
215+
}
216+
217+
pub fn unlock(&self) -> io::Result<()> {
218+
self.0
219+
}
220+
201221
pub fn truncate(&self, _size: u64) -> io::Result<()> {
202222
self.0
203223
}

0 commit comments

Comments
 (0)