mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-05 15:38:19 +08:00
fix(plugin): close cancellable install lifecycle gaps
This commit is contained in:
@@ -188,9 +188,11 @@ class SystemUtils:
|
||||
grace_seconds: float = 5,
|
||||
) -> None:
|
||||
"""终止安装子进程及其同组子进程,并确保句柄已回收。"""
|
||||
process_tree = SystemUtils._process_tree(process.pid)
|
||||
if process.returncode is None:
|
||||
try:
|
||||
if os.name == "nt":
|
||||
SystemUtils._signal_process_tree(process_tree)
|
||||
process.terminate()
|
||||
else:
|
||||
os.killpg(process.pid, signal.SIGTERM)
|
||||
@@ -205,9 +207,11 @@ class SystemUtils:
|
||||
except (asyncio.TimeoutError, asyncio.CancelledError):
|
||||
try:
|
||||
if os.name == "nt":
|
||||
SystemUtils._signal_process_tree(process_tree, force=True)
|
||||
process.kill()
|
||||
else:
|
||||
os.killpg(process.pid, signal.SIGKILL)
|
||||
SystemUtils._signal_process_tree(process_tree, force=True)
|
||||
except (ProcessLookupError, OSError):
|
||||
pass
|
||||
try:
|
||||
@@ -224,6 +228,27 @@ class SystemUtils:
|
||||
except (ProcessLookupError, OSError):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def _process_tree(pid: int) -> list[psutil.Process]:
|
||||
"""收集安装进程及其后代,避免构建进程继续写运行环境。"""
|
||||
try:
|
||||
parent = psutil.Process(pid)
|
||||
return [parent, *parent.children(recursive=True)]
|
||||
except (psutil.Error, OSError):
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def _signal_process_tree(
|
||||
processes: list[psutil.Process],
|
||||
force: bool = False,
|
||||
) -> None:
|
||||
"""向进程树发送终止或强制结束信号。"""
|
||||
for child in reversed(processes):
|
||||
try:
|
||||
(child.kill if force else child.terminate)()
|
||||
except (psutil.Error, OSError):
|
||||
continue
|
||||
|
||||
@staticmethod
|
||||
async def execute_with_subprocess_async(
|
||||
command: list,
|
||||
|
||||
@@ -101,16 +101,18 @@ class PluginPackageManager:
|
||||
@staticmethod
|
||||
def rollback(checkpoint: PluginPackageCheckpoint) -> None:
|
||||
"""删除当前包并把变更前文件快照恢复到运行目录。"""
|
||||
if checkpoint.plugin_dir.exists():
|
||||
shutil.rmtree(checkpoint.plugin_dir)
|
||||
snapshot_dir = checkpoint.transaction_dir / "package"
|
||||
if checkpoint.existed:
|
||||
if not snapshot_dir.is_dir():
|
||||
raise FileNotFoundError(
|
||||
f"插件 {checkpoint.plugin_id} 的补偿快照不存在:{snapshot_dir}"
|
||||
)
|
||||
if checkpoint.plugin_dir.exists():
|
||||
shutil.rmtree(checkpoint.plugin_dir)
|
||||
if checkpoint.existed:
|
||||
shutil.copytree(snapshot_dir, checkpoint.plugin_dir)
|
||||
shutil.rmtree(checkpoint.transaction_dir, ignore_errors=False)
|
||||
if checkpoint.transaction_dir.exists():
|
||||
shutil.rmtree(checkpoint.transaction_dir, ignore_errors=False)
|
||||
|
||||
async def async_rollback(self, checkpoint: PluginPackageCheckpoint) -> None:
|
||||
"""在线程池中恢复插件包文件快照。"""
|
||||
|
||||
Reference in New Issue
Block a user