66use crate :: error:: FileIo ;
77use crate :: file_util:: * ;
88use fd_lock:: { FdLock , FdLockGuard } ;
9+ use std:: os:: windows:: fs:: OpenOptionsExt ;
910use std:: path:: PathBuf ;
1011use std:: { borrow:: Cow , io:: ErrorKind } ;
1112use std:: {
1213 fs:: OpenOptions ,
1314 time:: { Duration , Instant } ,
1415} ;
16+ use winapi:: um:: {
17+ winbase:: FILE_FLAG_DELETE_ON_CLOSE ,
18+ winnt:: { FILE_ATTRIBUTE_HIDDEN , FILE_ATTRIBUTE_TEMPORARY } ,
19+ } ;
1520
1621#[ macro_export]
1722macro_rules! lock {
@@ -56,35 +61,40 @@ impl FileLock {
5661 let lock = self . lock . as_mut ( ) . unwrap ( ) ;
5762 let guard = lock. lock ( ) . unwrap ( ) ;
5863 Ok ( FileLockGuard {
59- _guard : guard,
64+ _guard : LockInner :: FdLockGuard ( guard) ,
6065 path : Cow :: Borrowed ( & self . path ) ,
61- is_lock_file : false ,
6266 } )
6367 } else if self . path . is_dir ( ) {
6468 // for directories, we'll create/open a .tmc.lock file
6569 let lock_path = self . path . join ( ".tmc.lock" ) ;
6670 loop {
6771 // try to create a new lock file
6872 match OpenOptions :: new ( )
73+ // needed for create_new
6974 . write ( true )
75+ // only creates file if it exists, check and creation are atomic
7076 . create_new ( true )
77+ // hidden, so it won't be a problem when going through the directory
78+ . attributes ( FILE_ATTRIBUTE_HIDDEN )
79+ // just tells windows there's probably no point in writing this to disk;
80+ // this might further reduce the risk of leftover lock files
81+ . attributes ( FILE_ATTRIBUTE_TEMPORARY )
82+ // windows deletes the lock file when the handle is closed = when the lock is dropped
83+ . custom_flags ( FILE_FLAG_DELETE_ON_CLOSE )
7184 . open ( & lock_path)
7285 {
7386 Ok ( file) => {
7487 // 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 ( ) ;
7988 return Ok ( FileLockGuard {
80- _guard : guard ,
89+ _guard : LockInner :: LockFile ( file ) ,
8190 path : Cow :: Owned ( lock_path) ,
82- is_lock_file : true ,
8391 } ) ;
8492 }
8593 Err ( err) => {
8694 if err. kind ( ) == ErrorKind :: AlreadyExists {
8795 // lock file already exists, let's wait a little and try again
96+ // after 30 seconds, print a warning in the logs every 10 seconds
97+ // after 120 seconds, print an error in the logs every 10 seconds
8898 if start_time. elapsed ( ) > Duration :: from_secs ( 30 )
8999 && warning_timer. elapsed ( ) > Duration :: from_secs ( 10 )
90100 {
@@ -95,6 +105,16 @@ impl FileLock {
95105 lock_path. display( ) ,
96106 start_time. elapsed( ) . as_secs( )
97107 ) ;
108+ } else if start_time. elapsed ( ) > Duration :: from_secs ( 120 )
109+ && warning_timer. elapsed ( ) > Duration :: from_secs ( 10 )
110+ {
111+ warning_timer = Instant :: now ( ) ;
112+ log:: error!(
113+ "The program has been waiting for lock file {} to be deleted for {} seconds,
114+ the lock file might have been left over from a previous run due to an error." ,
115+ lock_path. display( ) ,
116+ start_time. elapsed( ) . as_secs( )
117+ ) ;
98118 }
99119 std:: thread:: sleep ( Duration :: from_millis ( 500 ) ) ;
100120 } else {
@@ -105,30 +125,24 @@ impl FileLock {
105125 }
106126 }
107127 } else {
108- return Err ( FileIo :: InvalidLockPath ( self . path . to_path_buf ( ) ) ) ;
128+ Err ( FileIo :: InvalidLockPath ( self . path . to_path_buf ( ) ) )
109129 }
110130 }
111131}
112132
113133pub struct FileLockGuard < ' a > {
114- _guard : FdLockGuard < ' a , File > ,
134+ _guard : LockInner < ' a > ,
115135 path : Cow < ' a , PathBuf > ,
116- is_lock_file : bool ,
136+ }
137+
138+ enum LockInner < ' a > {
139+ LockFile ( File ) ,
140+ FdLockGuard ( FdLockGuard < ' a , File > ) ,
117141}
118142
119143impl Drop for FileLockGuard < ' _ > {
120144 fn drop ( & mut self ) {
121145 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- }
132146 }
133147}
134148
@@ -216,4 +230,18 @@ mod test {
216230 // wait for thread, if it panicked, it tried to lock the mutex without the file lock
217231 handle. join ( ) . unwrap ( ) ;
218232 }
233+
234+ #[ test]
235+ fn lock_file_is_created_and_is_deleted ( ) {
236+ init ( ) ;
237+
238+ let temp = tempfile:: tempdir ( ) . unwrap ( ) ;
239+ let mut lock = FileLock :: new ( temp. path ( ) . to_path_buf ( ) ) . unwrap ( ) ;
240+ let lock_path = temp. path ( ) . join ( ".tmc.lock" ) ;
241+ assert ! ( !lock_path. exists( ) ) ;
242+ let guard = lock. lock ( ) . unwrap ( ) ;
243+ assert ! ( lock_path. exists( ) ) ;
244+ drop ( guard) ;
245+ assert ! ( !lock_path. exists( ) ) ;
246+ }
219247}
0 commit comments