1
1
use crate :: { hash_path, user_forc_directory} ;
2
2
use std:: {
3
- fs:: { create_dir_all, remove_file, File } ,
3
+ fs:: { create_dir_all, read_dir , remove_file, File } ,
4
4
io:: { self , Read , Write } ,
5
5
path:: { Path , PathBuf } ,
6
6
} ;
@@ -19,6 +19,9 @@ impl PidFileLocking {
19
19
dir : Y ,
20
20
extension : & str ,
21
21
) -> PidFileLocking {
22
+ // Try to cleanup stale files, ignore any errors as this is best-effort
23
+ let _ = Self :: cleanup_stale_files ( ) ;
24
+
22
25
let file_name = hash_path ( filename) ;
23
26
Self (
24
27
user_forc_directory ( )
@@ -136,6 +139,38 @@ impl PidFileLocking {
136
139
fs. flush ( ) ?;
137
140
Ok ( ( ) )
138
141
}
142
+
143
+ /// Cleans up all stale lock files in the .lsp-locks directory
144
+ /// Returns a vector of paths that were cleaned up
145
+ pub fn cleanup_stale_files ( ) -> io:: Result < Vec < PathBuf > > {
146
+ let lock_dir = user_forc_directory ( ) . join ( ".lsp-locks" ) ;
147
+ let entries = read_dir ( & lock_dir) ?;
148
+ let mut cleaned_paths = Vec :: new ( ) ;
149
+
150
+ for entry in entries {
151
+ let entry = entry?;
152
+ let path = entry. path ( ) ;
153
+ if let Some ( ext) = path. extension ( ) . and_then ( |ext| ext. to_str ( ) ) {
154
+ if ext == "lock" {
155
+ if let Ok ( mut file) = File :: open ( & path) {
156
+ let mut contents = String :: new ( ) ;
157
+ if file. read_to_string ( & mut contents) . is_ok ( ) {
158
+ if let Ok ( pid) = contents. trim ( ) . parse :: < usize > ( ) {
159
+ if !Self :: is_pid_active ( pid) {
160
+ remove_file ( & path) ?;
161
+ cleaned_paths. push ( path) ;
162
+ }
163
+ } else {
164
+ remove_file ( & path) ?;
165
+ cleaned_paths. push ( path) ;
166
+ }
167
+ }
168
+ }
169
+ }
170
+ }
171
+ }
172
+ Ok ( cleaned_paths)
173
+ }
139
174
}
140
175
141
176
/// Checks if the specified file is marked as "dirty".
@@ -149,7 +184,7 @@ pub fn is_file_dirty<X: AsRef<Path>>(path: X) -> bool {
149
184
150
185
#[ cfg( test) ]
151
186
mod test {
152
- use super :: PidFileLocking ;
187
+ use super :: { user_forc_directory , PidFileLocking } ;
153
188
use std:: {
154
189
fs:: { metadata, File } ,
155
190
io:: { ErrorKind , Write } ,
@@ -210,4 +245,32 @@ mod test {
210
245
let e = metadata ( & x. 0 ) . unwrap_err ( ) . kind ( ) ;
211
246
assert_eq ! ( e, ErrorKind :: NotFound ) ;
212
247
}
248
+
249
+ #[ test]
250
+ fn test_cleanup_stale_files ( ) {
251
+ // First create some test files
252
+ let test_lock = PidFileLocking :: lsp ( "test_cleanup" ) ;
253
+ test_lock. lock ( ) . expect ( "Failed to create test lock file" ) ;
254
+
255
+ // Create a test lock file with invalid PID
256
+ let lock_path = user_forc_directory ( )
257
+ . join ( ".lsp-locks" )
258
+ . join ( "test_cleanup.lock" ) ;
259
+ File :: create ( & lock_path) . expect ( "Failed to create test lock file" ) ;
260
+
261
+ // Run cleanup and check returned paths
262
+ let cleaned_paths =
263
+ PidFileLocking :: cleanup_stale_files ( ) . expect ( "Failed to cleanup stale files" ) ;
264
+
265
+ // Verify that only the invalid lock file was cleaned up
266
+ assert_eq ! ( cleaned_paths. len( ) , 1 ) ;
267
+ assert_eq ! ( cleaned_paths[ 0 ] , lock_path) ;
268
+
269
+ // Verify file system state
270
+ assert ! ( test_lock. 0 . exists( ) , "Active lock file should still exist" ) ;
271
+ assert ! ( !lock_path. exists( ) , "Lock file should be removed" ) ;
272
+
273
+ // Cleanup after test
274
+ test_lock. release ( ) . expect ( "Failed to release test lock" ) ;
275
+ }
213
276
}
0 commit comments