Skip to content

Commit 633928f

Browse files
core: libs: commonwealth: general: Improve file_is_open logic and add async version
Signed-off-by: Patrick José Pereira <patrickelectric@gmail.com>
1 parent e1c3a13 commit 633928f

File tree

1 file changed

+59
-5
lines changed
  • core/libs/commonwealth/src/commonwealth/utils

1 file changed

+59
-5
lines changed

core/libs/commonwealth/src/commonwealth/utils/general.py

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
157183
def 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
174228
def local_unique_identifier() -> str:

0 commit comments

Comments
 (0)