diff --git a/services/init_service.py b/services/init_service.py index 6ecd318..e7a29c8 100644 --- a/services/init_service.py +++ b/services/init_service.py @@ -218,7 +218,8 @@ class InitService: 'plugins': { 'auto_load': True, 'hot_reload': True, - 'max_retry_count': 3 + 'max_retry_count': 3, + 'isolation': False, # 默认不隔离,插件可在 settings.isolation 中声明 }, 'auto_start_scripts': { 'enabled': True, diff --git a/services/plugin_service.py b/services/plugin_service.py index 7bf2c37..6163967 100644 --- a/services/plugin_service.py +++ b/services/plugin_service.py @@ -192,36 +192,105 @@ class PluginService: await self.unload_plugin(name) self.is_running = False + def _should_isolate(self, plugin_config: dict) -> bool: + """检查插件是否应使用进程隔离模式""" + global_isolation = self.config.get('plugins', {}).get('isolation', False) + plugin_isolation = plugin_config.get('settings', {}).get('isolation', None) + if plugin_isolation is not None: + return bool(plugin_isolation) + return global_isolation + async def load_all_plugins(self): """加载所有插件""" try: logger.debug("开始加载所有插件") - + if not self.plugins_dir.exists(): logger.warning("插件目录不存在,跳过加载") return - + loaded_count = 0 error_count = 0 - + isolated_count = 0 + # 遍历插件目录 for plugin_dir in self.plugins_dir.iterdir(): if plugin_dir.is_dir(): try: - success = await self.load_plugin(plugin_dir.name) - if success: - loaded_count += 1 + # 先读配置判断是否需要隔离 + config_file = plugin_dir / "config.yaml" + plugin_config = {} + if config_file.exists(): + with open(config_file, 'r') as f: + plugin_config = yaml.safe_load(f) or {} + + if self._should_isolate(plugin_config): + success = await self._load_plugin_isolated(plugin_dir.name) + if success: + isolated_count += 1 + loaded_count += 1 + else: + error_count += 1 else: - error_count += 1 + success = await self.load_plugin(plugin_dir.name) + if success: + loaded_count += 1 + else: + error_count += 1 except Exception as e: logger.error(f"加载插件 {plugin_dir.name} 时出错: {str(e)}", exc_info=True) error_count += 1 - - logger.info(f"插件加载完成: 成功 {loaded_count}, 失败 {error_count}") - + + logger.info( + f"插件加载完成: 成功 {loaded_count} (其中隔离 {isolated_count}), 失败 {error_count}" + ) + except Exception as e: logger.error(f"加载所有插件时出错: {str(e)}", exc_info=True) raise + + async def _load_plugin_isolated(self, plugin_name: str) -> bool: + """在独立子进程中加载插件(进程隔离模式)""" + try: + from services.process_isolated import IsolatedPlugin + + plugin_path = self.plugins_dir / plugin_name + main_module = plugin_path / "__init__.py" + if not main_module.exists(): + logger.error(f"隔离插件主模块不存在: {main_module}") + return False + + config_file = plugin_path / "config.yaml" + plugin_config = {} + if config_file.exists(): + with open(config_file) as f: + plugin_config = yaml.safe_load(f) or {} + + iso = IsolatedPlugin( + plugin_name, + str(main_module), + plugin_config, + ) + + self.plugins[plugin_name] = iso + self.plugin_info[plugin_name] = PluginInfo( + name=plugin_config.get('name', plugin_name), + version=plugin_config.get('version', '0.1.0'), + description=plugin_config.get('description', ''), + author=plugin_config.get('author', ''), + enabled=True, + loaded=True, + error_count=0, + permissions=[], + plugin_path=plugin_path, + commands={}, + ) + logger.info(f"🔒 隔离插件加载成功: {plugin_name} (PID={iso.pid})") + return True + + except Exception as e: + logger.error(f"加载隔离插件 {plugin_name} 失败: {e}") + return False async def load_plugin(self, plugin_name: str) -> bool: """加载单个插件 - 支持异步权限处理""" @@ -454,17 +523,18 @@ class PluginService: """卸载插件""" try: logger.debug(f"开始卸载插件: {plugin_name}") - + if plugin_name not in self.plugins: logger.warning(f"插件未加载: {plugin_name}") return False - + plugin_instance = self.plugins[plugin_name] plugin_info = self.plugin_info[plugin_name] - - # 注销插件命令 - await self._unregister_plugin_commands(plugin_name) - + + # 注销插件命令 (隔离插件可能没有注册命令) + if not self._is_isolated(plugin_instance): + await self._unregister_plugin_commands(plugin_name) + # 调用插件的清理方法 try: if hasattr(plugin_instance, 'shutdown'): @@ -474,23 +544,28 @@ class PluginService: plugin_instance.shutdown() except Exception as e: logger.error(f"插件清理时出错 {plugin_name}: {str(e)}", exc_info=True) - + # 从模块缓存中移除 module_name = f"plugins.{plugin_name}" if module_name in sys.modules: del sys.modules[module_name] - + # 移除插件实例和信息 del self.plugins[plugin_name] plugin_info.loaded = False plugin_info.enabled = False - + logger.info(f"插件卸载成功: {plugin_name}") return True - + except Exception as e: logger.error(f"卸载插件 {plugin_name} 时出错: {str(e)}", exc_info=True) return False + + @staticmethod + def _is_isolated(plugin_instance) -> bool: + from services.process_isolated import IsolatedPlugin + return isinstance(plugin_instance, IsolatedPlugin) async def _unregister_plugin_commands(self, plugin_name: str): """注销插件命令"""