v0.4+v0.5: TUI dashboard, tab completion, systemd, Docker, process isolation
- 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
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user