Safer ffmpeg kill, POST process/service actions, ARM encoder from hardware.env, filter_threads scaling
Made-with: Cursor
This commit is contained in:
@@ -12,7 +12,7 @@ import subprocess
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
from typing import Any, Iterable, Optional
|
||||
|
||||
BACKEND_STATE_VERSION = 1
|
||||
|
||||
@@ -49,10 +49,58 @@ async def run_pm2(cmd: str, timeout: float = 15.0) -> str:
|
||||
return text
|
||||
|
||||
|
||||
async def force_kill_ffmpeg(process_name: str) -> None:
|
||||
"""尽量结束与本业务相关的 ffmpeg(Linux 精准 + 兜底;Windows 与 Linux 兜底策略一致:结束 ffmpeg 进程)。"""
|
||||
def _linux_ffmpeg_pids_for_stack(process_name: str, project_root: Path | None) -> list[int]:
|
||||
"""仅匹配与本项目直播栈相关的 ffmpeg(命令行含 YouTube 推流、抖音拉流或项目路径),避免 killall 误伤整机其它 ffmpeg。"""
|
||||
markers: list[str] = [
|
||||
process_name,
|
||||
"a.rtmp.youtube.com",
|
||||
"rtmps://a.rtmp.youtube.com",
|
||||
"live.douyin.com",
|
||||
"pull-flv",
|
||||
"douyincdn.com",
|
||||
]
|
||||
if project_root is not None:
|
||||
try:
|
||||
markers.append(str(project_root.resolve()))
|
||||
except OSError:
|
||||
pass
|
||||
markers = [m for m in markers if m]
|
||||
seen: set[int] = set()
|
||||
proc_root = Path("/proc")
|
||||
if not proc_root.is_dir():
|
||||
return []
|
||||
for entry in proc_root.iterdir():
|
||||
if not entry.name.isdigit():
|
||||
continue
|
||||
try:
|
||||
pid = int(entry.name)
|
||||
except ValueError:
|
||||
continue
|
||||
cmdline = entry / "cmdline"
|
||||
try:
|
||||
raw = cmdline.read_bytes()
|
||||
except OSError:
|
||||
continue
|
||||
if not raw or b"ffmpeg" not in raw.lower():
|
||||
continue
|
||||
cmd = raw.replace(b"\0", b" ").decode("utf-8", "replace")
|
||||
if any(m in cmd for m in markers):
|
||||
seen.add(pid)
|
||||
return sorted(seen)
|
||||
|
||||
|
||||
def _signal_pids(pids: Iterable[int], sig: int) -> None:
|
||||
for pid in pids:
|
||||
try:
|
||||
os.kill(pid, sig)
|
||||
except (ProcessLookupError, PermissionError):
|
||||
pass
|
||||
|
||||
|
||||
async def force_kill_ffmpeg(process_name: str, project_root: Path | None = None) -> None:
|
||||
"""结束与本业务相关的 ffmpeg:Linux 按 /proc 命令行匹配 + 可选 pkill;不再使用 killall -9 ffmpeg。"""
|
||||
if sys.platform == "win32":
|
||||
_ = process_name # 保留参数供将来按命令行筛选;当前与 Linux killall 行为对齐
|
||||
_ = process_name
|
||||
proc2 = await asyncio.create_subprocess_shell(
|
||||
"taskkill /IM ffmpeg.exe /F /T 2>nul || exit /b 0",
|
||||
stdout=asyncio.subprocess.DEVNULL,
|
||||
@@ -61,17 +109,25 @@ async def force_kill_ffmpeg(process_name: str) -> None:
|
||||
await proc2.wait()
|
||||
return
|
||||
|
||||
for cmd in (
|
||||
f"pkill -f 'ffmpeg.*{process_name}' || true",
|
||||
"killall -9 ffmpeg 2>/dev/null || true",
|
||||
):
|
||||
proc = await asyncio.create_subprocess_shell(
|
||||
cmd,
|
||||
stdout=asyncio.subprocess.DEVNULL,
|
||||
stderr=asyncio.subprocess.DEVNULL,
|
||||
)
|
||||
await proc.wait()
|
||||
await asyncio.sleep(0.4)
|
||||
def collect() -> list[int]:
|
||||
return _linux_ffmpeg_pids_for_stack(process_name, project_root)
|
||||
|
||||
pids = await asyncio.to_thread(collect)
|
||||
if pids:
|
||||
await asyncio.to_thread(_signal_pids, pids, signal.SIGTERM)
|
||||
await asyncio.sleep(0.6)
|
||||
remaining = await asyncio.to_thread(collect)
|
||||
if remaining:
|
||||
await asyncio.to_thread(_signal_pids, remaining, signal.SIGKILL)
|
||||
await asyncio.sleep(0.2)
|
||||
|
||||
safe = re.sub(r"[^a-zA-Z0-9_.-]", "", process_name) or "x"
|
||||
proc = await asyncio.create_subprocess_shell(
|
||||
f"pkill -f 'ffmpeg.*{safe}' || true",
|
||||
stdout=asyncio.subprocess.DEVNULL,
|
||||
stderr=asyncio.subprocess.DEVNULL,
|
||||
)
|
||||
await proc.wait()
|
||||
|
||||
|
||||
class LocalProcessRegistry:
|
||||
|
||||
Reference in New Issue
Block a user