#!/usr/bin/env python3 """插件进程隔离 — 在子进程中运行插件,崩溃不影响框架""" import asyncio, subprocess, json, os, logging, tempfile from multiprocessing import Process, Queue from typing import Dict, Any logger = logging.getLogger(__name__) def _plugin_runner(plugin_path: str, config_json: str, cmd_queue: Queue, result_queue: Queue): """子进程入口 - 加载插件并监听命令队列""" import sys, importlib.util, json as j sys.path.insert(0, os.path.dirname(plugin_path)) spec = importlib.util.spec_from_file_location("plugin", plugin_path) mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(mod) plugin = mod.Plugin(os.path.basename(os.path.dirname(plugin_path)), j.loads(config_json), None) asyncio.run(plugin.initialize()) while True: cmd = cmd_queue.get() if cmd == "__SHUTDOWN__": asyncio.run(plugin.shutdown()) break try: method = getattr(plugin, cmd.get("method", ""), None) if method: result = method(*cmd.get("args", [])) result_queue.put({"ok": True, "result": str(result)}) else: result_queue.put({"ok": False, "error": f"method not found: {cmd.get(method)}"}) except Exception as e: result_queue.put({"ok": False, "error": str(e)}) class IsolatedPlugin: """进程隔离插件包装器""" def __init__(self, plugin_name: str, plugin_path: str, config: dict): self.name = plugin_name self.cmd_queue = Queue() self.result_queue = Queue() self.process = Process( target=_plugin_runner, args=(plugin_path, json.dumps(config), self.cmd_queue, self.result_queue), daemon=True ) self.process.start() logger.info(f"隔离插件已启动: {plugin_name} (PID={self.process.pid})") def call(self, method: str, *args, timeout: float = 30): self.cmd_queue.put({"method": method, "args": args}) try: result = self.result_queue.get(timeout=timeout) return result except: return {"ok": False, "error": "timeout"} def shutdown(self): self.cmd_queue.put("__SHUTDOWN__") self.process.join(timeout=10) if self.process.is_alive(): self.process.terminate() logger.info(f"隔离插件已关闭: {self.name}") @property def pid(self): return self.process.pid if self.process.is_alive() else None