@@ -154,21 +154,75 @@ async def delete_everything_stream(path: Path) -> AsyncGenerator[dict[str, Any],
154154 # fmt: on
155155
156156
157+ def _file_is_open_command (path : Path ) -> list [str ]:
158+ # fmt: off
159+ return [
160+ "lsof" ,
161+ "-t" , # output only PIDs (terse)
162+ "-n" , # do NOT resolve hostnames (faster, avoids DNS)
163+ "-P" , # do NOT resolve ports to service names
164+ "-S" , "2" , # kernel function timeout = 2 seconds
165+ "--" , # stop option parsing, treat next as path
166+ str (path .resolve ()),
167+ ]
168+ # fmt: on
169+
170+
171+ def _file_is_open_logic_lsof (returncode : int | None , stdout : str , stderr : str ) -> bool :
172+ if returncode == 0 :
173+ # Check if we have any PIDs in the output
174+ return bool (stdout .strip ())
175+
176+ if returncode == 1 and not stderr .strip ():
177+ return False
178+
179+ logger .error (f"lsof error checking: returncode={ returncode } , stderr={ stderr .strip ()} " )
180+ return True
181+
182+
157183def file_is_open (path : Path ) -> bool :
184+ cmd = _file_is_open_command (path )
185+
158186 try :
159- kernel_functions_timeout = str (2 )
160187 result = subprocess .run (
161- [ "lsof" , "-t" , "-n" , "-P" , "-S" , kernel_functions_timeout , path . resolve ()] ,
188+ cmd ,
162189 stdout = subprocess .PIPE ,
163190 stderr = subprocess .PIPE ,
164- check = False ,
191+ text = True ,
165192 timeout = 5 ,
193+ check = False ,
166194 )
167- return result .returncode == 0
168195 except Exception as error :
169- logger .error (f"Failed to check if file { path } is open, { error } " )
196+ logger .error (f"Failed to run lsof for { path } : { error } " )
170197 return True
171198
199+ return _file_is_open_logic_lsof (result .returncode , result .stdout .strip (), result .stderr .strip ())
200+
201+
202+ async def file_is_open_async (path : Path ) -> bool :
203+ cmd = _file_is_open_command (path )
204+
205+ try :
206+ process = await asyncio .create_subprocess_exec (
207+ * cmd ,
208+ stdout = asyncio .subprocess .PIPE ,
209+ stderr = asyncio .subprocess .PIPE ,
210+ )
211+
212+ try :
213+ stdout , stderr = await asyncio .wait_for (process .communicate (), timeout = 5 )
214+ except asyncio .TimeoutError :
215+ process .kill ()
216+ await process .wait ()
217+ logger .error (f"Timeout running lsof for { path } " )
218+ return True
219+
220+ except Exception as error :
221+ logger .error (f"Failed to run lsof for { path } : { error } " )
222+ return True
223+
224+ return _file_is_open_logic_lsof (process .returncode , stdout .decode ().strip (), stderr .decode ().strip ())
225+
172226
173227@cache
174228def local_unique_identifier () -> str :
0 commit comments