Skip to content

Commit 649b445

Browse files
authored
fix: Leaking file descriptors
1 parent d68529f commit 649b445

File tree

1 file changed

+43
-31
lines changed

1 file changed

+43
-31
lines changed

homcc/common/arguments.py

Lines changed: 43 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -564,14 +564,52 @@ def _execute_args_sync(
564564

565565
return ArgumentsExecutionResult.from_process_result(result)
566566

567+
@staticmethod
568+
def _wait_for_async_termination(
569+
poller: select.poll,
570+
process: subprocess.Popen[bytes],
571+
process_fd: int,
572+
event_socket_fd: int,
573+
timeout: Optional[float],
574+
):
575+
start_time = time.time()
576+
577+
while True:
578+
now_time = time.time()
579+
580+
if timeout is not None and now_time - start_time >= timeout:
581+
raise TimeoutError(f"Compiler timed out. (Timeout: {timeout}s).")
582+
583+
events = poller.poll(1)
584+
for fd, event in events:
585+
if fd == event_socket_fd:
586+
logger.info("Terminating compilation process as socket got closed by remote. (event: %i)", event)
587+
588+
process.terminate()
589+
# we need to wait for the process to terminate, so that the handle is correctly closed
590+
process.wait()
591+
592+
raise ClientDisconnectedError
593+
elif fd == process_fd:
594+
logger.debug("Process has finished (process_fd has event): %i", event)
595+
596+
stdout_bytes, stderr_bytes = process.communicate()
597+
stdout = stdout_bytes.decode(ENCODING)
598+
stderr = stderr_bytes.decode(ENCODING)
599+
600+
return ArgumentsExecutionResult(process.returncode, stdout, stderr)
601+
else:
602+
logger.warning(
603+
"Got poll() event for fd '%i', which does neither match the socket nor the process.", fd
604+
)
605+
567606
@staticmethod
568607
def _execute_async(
569608
args: List[str],
570609
event_socket_fd: int,
571610
cwd: Path,
572611
timeout: Optional[float],
573612
) -> ArgumentsExecutionResult:
574-
start_time = time.time()
575613
with subprocess.Popen(args, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as process:
576614
poller = select.poll()
577615
# socket is readable when TCP FIN is sended, so also check if we can read (POLLIN).
@@ -582,36 +620,10 @@ def _execute_async(
582620
process_fd = os.pidfd_open(process.pid)
583621
poller.register(process_fd, select.POLLRDHUP | select.POLLIN)
584622

585-
while True:
586-
now_time = time.time()
587-
588-
if timeout is not None and now_time - start_time >= timeout:
589-
raise TimeoutError(f"Compiler timed out. (Timeout: {timeout}s).")
590-
591-
events = poller.poll(1)
592-
for fd, event in events:
593-
if fd == event_socket_fd:
594-
logger.info(
595-
"Terminating compilation process as socket got closed by remote. (event: %i)", event
596-
)
597-
598-
process.terminate()
599-
# we need to wait for the process to terminate, so that the handle is correctly closed
600-
process.wait()
601-
602-
raise ClientDisconnectedError
603-
elif fd == process_fd:
604-
logger.debug("Process has finished (process_fd has event): %i", event)
605-
606-
stdout_bytes, stderr_bytes = process.communicate()
607-
stdout = stdout_bytes.decode(ENCODING)
608-
stderr = stderr_bytes.decode(ENCODING)
609-
610-
return ArgumentsExecutionResult(process.returncode, stdout, stderr)
611-
else:
612-
logger.warning(
613-
"Got poll() event for fd '%i', which does neither match the socket nor the process.", fd
614-
)
623+
try:
624+
return Arguments._wait_for_async_termination(poller, process, process_fd, event_socket_fd, timeout)
625+
finally:
626+
os.close(process_fd)
615627

616628
def execute(self, **kwargs) -> ArgumentsExecutionResult:
617629
"""

0 commit comments

Comments
 (0)