67 lines
2.0 KiB
Python
67 lines
2.0 KiB
Python
"""Shared low-level utilities used across services.
|
|
|
|
Currently home to two general-purpose helpers that have no runtime-
|
|
specific concerns:
|
|
|
|
- :func:`get_free_port` asks the kernel for a currently-unused TCP port
|
|
by binding to ``:0`` and reading back the assigned port number.
|
|
- :func:`start_process` launches a subprocess with stdout/stderr merged
|
|
into a per-pid log file under ``log_dir`` and returns the final log
|
|
path so callers can log it themselves with whatever logger they use.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import socket
|
|
import subprocess
|
|
import time
|
|
from pathlib import Path
|
|
|
|
|
|
def get_free_port() -> int:
|
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
s.bind(("", 0))
|
|
s.listen(1)
|
|
port = s.getsockname()[1]
|
|
return port
|
|
|
|
|
|
def start_process(
|
|
cmd: list[str],
|
|
workspace_path: Path,
|
|
log_dir: str | Path = "/tmp/process_logs",
|
|
env: dict[str, str] | None = None,
|
|
) -> tuple[subprocess.Popen, Path]:
|
|
"""Launch ``cmd`` as a subprocess and return ``(process, log_file)``.
|
|
|
|
stderr is merged into a per-pid log file under ``log_dir``; the
|
|
temp ``process_start_*.log`` is renamed to ``process_<pid>_*.log``
|
|
once the real pid is known. The caller logs "I started this" with
|
|
its own context — this function does not log on its own.
|
|
"""
|
|
log_dir_path = Path(log_dir)
|
|
log_dir_path.mkdir(parents=True, exist_ok=True)
|
|
|
|
start_time = time.strftime("%Y%m%d_%H%M%S")
|
|
temp_log = log_dir_path / f"process_start_{start_time}.log"
|
|
|
|
# 构建合并后的环境变量
|
|
full_env = os.environ.copy()
|
|
if env:
|
|
full_env.update(env)
|
|
|
|
with open(temp_log, "a", buffering=1) as log_file:
|
|
process = subprocess.Popen(
|
|
cmd,
|
|
cwd=workspace_path,
|
|
stdout=log_file,
|
|
stderr=subprocess.STDOUT,
|
|
start_new_session=True,
|
|
env=full_env,
|
|
)
|
|
|
|
final_log = log_dir_path / f"process_{process.pid}_{start_time}.log"
|
|
temp_log.replace(final_log)
|
|
|
|
return process, final_log |