4b26b6f086
- New: services/sysmon_widget.py (CPU/MEM/DISK/NET real-time) - Enhanced: tui_service.py (4-section layout, plugin status panel) - Enhanced: command_service.py + tui (Tab completion) - New: deploy/sensu.service (systemd) - New: deploy/Dockerfile (Alpine, 4200+4240) - New: services/process_isolated.py (multiprocess plugin isolation) - Tests: 23/23 passing
69 lines
2.5 KiB
Python
69 lines
2.5 KiB
Python
#!/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
|