mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-20 23:54:26 +08:00
feat.文档更新
This commit is contained in:
263
docs/archive/PLUGIN_PROCESS_ISOLATION_ARCHITECTURE.md
Normal file
263
docs/archive/PLUGIN_PROCESS_ISOLATION_ARCHITECTURE.md
Normal file
@@ -0,0 +1,263 @@
|
||||
# 插件进程隔离架构
|
||||
|
||||
## 1. 背景与问题
|
||||
|
||||
当前插件系统只做了程序集隔离,没有做进程隔离。
|
||||
|
||||
- 宿主通过 `LanMountainDesktop/plugins/PluginRuntimeService.cs` 和 `LanMountainDesktop/plugins/PluginLoader.cs` 在 Host 进程内发现、加载并初始化插件。
|
||||
- 插件依赖 `PluginLoadContext` 获得 `AssemblyLoadContext` 级别的依赖隔离,但代码、线程、托管堆和原生句柄仍与 Host 共处同一进程。
|
||||
- 插件 `IHostedService` 也由 Host 直接构造并启动,所以插件后台逻辑和 Host 生命周期强耦合。
|
||||
- 桌面组件、组件编辑器、设置页当前都直接返回 `Avalonia Control` 或 `SettingsPageBase`,并由 Host 直接插入视觉树。
|
||||
|
||||
这带来三个核心风险:
|
||||
|
||||
1. 插件崩溃会拖垮 Host,典型场景包括 `StackOverflowException`、`AccessViolationException`、原生依赖崩溃。
|
||||
2. 插件可直接访问 Host 进程中的服务实例与内存对象,缺少安全边界与权限审计点。
|
||||
3. 现有“对象实例共享”模型难以迁移到跨进程,因为它默认调用成本近似于内存内方法调用。
|
||||
|
||||
## 2. 目标与非目标
|
||||
|
||||
### 2.1 一期目标
|
||||
|
||||
- 保持增量兼容,未声明新运行模式的插件继续走 `in-proc`。
|
||||
- 新增 `isolated-background` 运行模式,为每个隔离插件启动独立 Worker 进程。
|
||||
- 把后台逻辑、定时任务、网络调用、原生高风险代码迁移到 Worker。
|
||||
- UI 仍保留 Host 侧薄壳,通过 IPC 获取状态并发送命令。
|
||||
- 新建独立 IPC 契约与封装层,为后续实际接线和插件升级提供稳定边界。
|
||||
|
||||
### 2.2 二期预留
|
||||
|
||||
- 预留 `isolated-window` 模式。
|
||||
- 插件 UI 在进程外窗口中渲染,Host 通过平台能力嵌入窗口句柄。
|
||||
- Windows 侧可评估 `SetParent`,Linux 侧可评估 `XEmbed` 或等价方案。
|
||||
|
||||
### 2.3 非目标
|
||||
|
||||
- 一期不强制所有插件升级。
|
||||
- 一期不把现有 `IPluginExportRegistry`、`IPluginMessageBus` 直接升级成跨进程远程对象模型。
|
||||
- 一期不实现完整的窗口嵌入渲染。
|
||||
|
||||
## 3. 运行模式设计
|
||||
|
||||
### 3.1 `in-proc`
|
||||
|
||||
- 默认模式。
|
||||
- 继续使用当前 `PluginRuntimeService` + `PluginLoader` + `PluginLoadContext` 路径。
|
||||
- 适合存量插件和仍依赖直接控件构造的插件。
|
||||
|
||||
### 3.2 `isolated-background`
|
||||
|
||||
- 一期目标模式。
|
||||
- Host 为每个插件启动独立 Worker 进程。
|
||||
- 启动时通过环境变量或命令行参数下发:
|
||||
- `pluginId`
|
||||
- `sessionId`
|
||||
- `hostPipeName`
|
||||
- `protocolVersion`
|
||||
- `runtimeMode`
|
||||
- Worker 内承载后台逻辑和 IPC 端点。
|
||||
- Host 只保留 UI 壳层与状态同步逻辑。
|
||||
|
||||
### 3.3 `isolated-window`
|
||||
|
||||
- 二期预留模式。
|
||||
- Worker 自己创建窗口并负责 UI 渲染。
|
||||
- Host 负责窗口嵌入、生命周期协调、焦点与尺寸同步。
|
||||
- 这是彻底切断插件 UI 崩溃影响 Host 的最终方案。
|
||||
|
||||
## 4. UI 方案取舍
|
||||
|
||||
### 4.1 方案一:进程外窗口
|
||||
|
||||
优点:
|
||||
|
||||
- 最强崩溃隔离。
|
||||
- 插件 UI 不再进入 Host 视觉树。
|
||||
- 安全边界更清晰。
|
||||
|
||||
缺点:
|
||||
|
||||
- 跨平台复杂度高。
|
||||
- 窗口句柄嵌入、焦点管理、输入法、缩放、多屏和无障碍都需要额外设计。
|
||||
- Avalonia 与平台窗口宿主的交互验证成本高。
|
||||
|
||||
### 4.2 方案二:Host 薄 UI 壳层
|
||||
|
||||
优点:
|
||||
|
||||
- 与现有组件系统、编辑器系统、设置页系统的迁移成本最低。
|
||||
- 可以先隔离最危险的后台与原生逻辑。
|
||||
- 适合做增量兼容与插件生态迁移。
|
||||
|
||||
缺点:
|
||||
|
||||
- 如果 Host 仍执行插件提供的 UI 代码,仍有残余稳定性风险。
|
||||
- 无法从根本上解决所有 UI 级崩溃。
|
||||
|
||||
### 4.3 一期结论
|
||||
|
||||
一期采用方案二,也就是 `isolated-background`。
|
||||
|
||||
这意味着:
|
||||
|
||||
- 后台逻辑先隔离。
|
||||
- UI 交互先代理。
|
||||
- 文档必须明确残余风险。
|
||||
- `isolated-window` 的架构接口要预留,但不进入一期实现。
|
||||
|
||||
## 5. IPC 协议设计
|
||||
|
||||
底层 IPC 继续基于 [dotnetCampus.Ipc](https://github.com/dotnet-campus/dotnetCampus.Ipc),但插件协议采用“显式路由 + DTO + 会话/心跳/故障管理”的方式,而不是把 Host 服务对象直接远程化。
|
||||
|
||||
### 5.1 路由分组
|
||||
|
||||
- `session/*`
|
||||
- `session/handshake`
|
||||
- `session/capabilities`
|
||||
- `session/ready`
|
||||
- `lifecycle/*`
|
||||
- `lifecycle/initialize`
|
||||
- `lifecycle/stop`
|
||||
- `lifecycle/restart-request`
|
||||
- `lifecycle/state-changed`
|
||||
- `settings/*`
|
||||
- `settings/get-snapshot`
|
||||
- `settings/write`
|
||||
- `settings/changed`
|
||||
- `appearance/*`
|
||||
- `appearance/get-snapshot`
|
||||
- `appearance/changed`
|
||||
- `ui/*`
|
||||
- `ui/attach`
|
||||
- `ui/detach`
|
||||
- `ui/command`
|
||||
- `ui/state-changed`
|
||||
- `heartbeat/*`
|
||||
- `heartbeat/ping`
|
||||
- `heartbeat/pong`
|
||||
- `log/*`
|
||||
- `log/write`
|
||||
- `fault/*`
|
||||
- `fault/report`
|
||||
|
||||
### 5.2 契约原则
|
||||
|
||||
- 只传 DTO,不传 Host 内存对象。
|
||||
- 所有 handler 必须在 `StartServer()` 前注册完成。
|
||||
- 使用 source-generated `System.Text.Json` 上下文统一序列化。
|
||||
- 协议版本通过 `session/handshake` 协商。
|
||||
- 能力通过显式 capability 列表声明和授予,不做隐式远程对象暴露。
|
||||
|
||||
### 5.3 明确不兼容的旧能力
|
||||
|
||||
- `IPluginExportRegistry` 的对象实例共享不延续到隔离模式。
|
||||
- 现有 `IPluginMessageBus` 不作为隔离插件主通信通道。
|
||||
- Worker 不直接创建 `Avalonia Control` 并返回给 Host。
|
||||
|
||||
## 6. 工程拆分
|
||||
|
||||
### 6.1 `LanMountainDesktop.PluginIsolation.Contracts`
|
||||
|
||||
职责:
|
||||
|
||||
- 纯 DTO
|
||||
- 协议版本
|
||||
- 路由常量
|
||||
- 错误码
|
||||
- capability 声明
|
||||
- source-generated JSON context
|
||||
|
||||
约束:
|
||||
|
||||
- 不引用 Avalonia
|
||||
- 不依赖 Host 服务实现
|
||||
- 作为 Host、Worker、SDK 共享的传输边界
|
||||
|
||||
### 6.2 `LanMountainDesktop.PluginIsolation.Ipc`
|
||||
|
||||
职责:
|
||||
|
||||
- 对标 ClassIsland 的轻量 IPC 封装外壳
|
||||
- 统一 `PluginIpcClient`
|
||||
- 统一 `PluginIpcServer`
|
||||
- 统一启动参数、环境变量、通知路由常量
|
||||
|
||||
约束:
|
||||
|
||||
- 借鉴 ClassIsland 的“包装层 + 常量集中 + 客户端低接入成本”
|
||||
- 但不把插件系统主协议设计成大面积远程属性模型
|
||||
|
||||
### 6.3 `LanMountainDesktop.PluginSdk`
|
||||
|
||||
新增内容:
|
||||
|
||||
- `runtime.mode` Manifest 支持
|
||||
- `PluginRuntimeMode`
|
||||
- `IPluginWorker`
|
||||
- `IPluginWorkerContext`
|
||||
- `PluginWorkerBase`
|
||||
- `[PluginWorkerEntrance]`
|
||||
|
||||
## 7. ClassIsland IPC 借鉴与取舍
|
||||
|
||||
参考资料:
|
||||
|
||||
- [ClassIsland 仓库](https://github.com/ClassIsland/ClassIsland)
|
||||
- [ClassIsland.Shared.IPC/IpcClient.cs](https://github.com/ClassIsland/ClassIsland/blob/master/ClassIsland.Shared.IPC/IpcClient.cs)
|
||||
- [ClassIsland.Shared.IPC/IpcRoutedNotifyIds.cs](https://github.com/ClassIsland/ClassIsland/blob/master/ClassIsland.Shared.IPC/IpcRoutedNotifyIds.cs)
|
||||
- [ClassIsland.Shared.IPC Abstractions](https://github.com/ClassIsland/ClassIsland/tree/master/ClassIsland.Shared.IPC/Abstractions/Services)
|
||||
|
||||
借鉴点:
|
||||
|
||||
- IPC 能力独立成包,边界清晰。
|
||||
- `IpcClient` 对底层库做轻量封装,接入成本低。
|
||||
- 通知路由有集中定义,事件名稳定。
|
||||
- 通过公共接口暴露很小的可用面,减少耦合。
|
||||
|
||||
不照搬的部分:
|
||||
|
||||
- 不把插件隔离主协议做成“远程对象/远程属性”模型。
|
||||
- 不隐藏跨进程调用成本。
|
||||
- 不让 UI 状态同步变成一串隐式属性访问。
|
||||
|
||||
最终结论:
|
||||
|
||||
- 采用“ClassIsland 风格的封装外壳”。
|
||||
- 协议主线仍是显式路由和明确 DTO。
|
||||
|
||||
## 8. 迁移策略
|
||||
|
||||
### 8.1 Manifest
|
||||
|
||||
`plugin.json` 新增:
|
||||
|
||||
```json
|
||||
{
|
||||
"runtime": {
|
||||
"mode": "in-proc"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
默认值为 `in-proc`。
|
||||
|
||||
### 8.2 插件迁移顺序
|
||||
|
||||
1. 保持现有 UI 注册方式不变。
|
||||
2. 把后台任务和风险代码收敛到 Worker。
|
||||
3. 让 Host UI 通过 `ui/*` 与 `settings/*` 路由访问 Worker 状态。
|
||||
4. 在二期再评估 `isolated-window` 迁移。
|
||||
|
||||
## 9. 故障模型与残余风险
|
||||
|
||||
一期必须满足以下行为:
|
||||
|
||||
- Worker 启动失败时,Host 仅禁用该插件并记录诊断。
|
||||
- Worker 心跳超时或被强杀时,Host 不崩溃。
|
||||
- Worker 上报 `fault/report` 时,Host 将插件标记为 degraded 或 faulted。
|
||||
|
||||
一期残余风险也必须明确写出:
|
||||
|
||||
- 如果 Host 仍执行插件提供的 UI 代码,UI 级崩溃仍可能影响 Host。
|
||||
- 因此 `isolated-background` 不是最终隔离形态,只是第一阶段收益最高的落点。
|
||||
- 完整 UI 崩溃隔离依赖二期 `isolated-window`。
|
||||
Reference in New Issue
Block a user