Files
SimpleRemoter/docs/CrashProtection.md
2026-04-19 22:55:21 +02:00

273 lines
7.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 崩溃保护与 MTBF 统计设计文档
## 概述
本文档描述服务端代理进程的崩溃保护机制和可靠性统计功能。
## 功能目标
1. **崩溃保护**:防止代理进程连续崩溃导致的无限重启循环
2. **崩溃统计**:记录崩溃次数、时间、退出代码等信息
3. **MTBF 计算**收集数据用于计算平均故障间隔时间Mean Time Between Failures
---
## 一、崩溃保护机制
### 1.1 触发条件
| 参数 | 值 | 说明 |
|------|-----|------|
| 窗口期 | 5 分钟 | `CRASH_WINDOW_MS = 300000` |
| 阈值 | 3 次 | `CRASH_THRESHOLD = 3` |
| 快速崩溃定义 | 30 秒内退出 | `FAST_CRASH_TIME_MS = 30000` |
**触发逻辑**:在 5 分钟窗口期内,代理进程发生 3 次快速崩溃(启动后 30 秒内异常退出),则触发崩溃保护。
### 1.2 判定标准
只有同时满足以下条件才计入崩溃:
- 退出代码 ≠ 0正常退出不计入
- 运行时间 < 30 秒(长时间运行后退出不计入)
**设计原则**:程序正常退出必须返回 0任何非 0 退出代码都视为异常退出。包括:
- Windows 异常(如 `0xC0000005` ACCESS_VIOLATION
- 自定义错误码(如 `1001` CONFIG_ERROR
- 其他非零退出码
### 1.3 保护行为
触发崩溃保护后:
1. 服务停止自动重启代理进程
2. 写入 `AgentCrashProtected = 1` 到配置
3. 下次程序启动时检测到此标志,切换到普通运行模式
4. 弹出提示框告知用户
### 1.4 状态持久化
崩溃窗口状态持久化到注册表,确保服务重启后状态不丢失:
配置 section: `[crash]`
| 配置键 | 类型 | 说明 |
|--------|------|------|
| `winCount` | int | 窗口期内崩溃次数 |
| `winStart` | string (uint64) | 窗口起始时间 (GetTickCount64) |
**服务重启时的恢复逻辑**
```
加载保存的状态
检查 savedFirstCrashTime <= 当前时间?
├─ 否 → 系统重启过,清除状态
└─ 是 → 检查是否仍在 5 分钟窗口内?
├─ 否 → 窗口已过期,清除状态
└─ 是 → 恢复状态,继续计数
```
**注意**`GetTickCount64()` 在系统重启后会重置为 0因此如果保存的时间戳大于当前时间说明系统重启过需要清除状态重新开始。
---
## 二、崩溃统计
### 2.1 记录内容
每次崩溃时记录以下信息section: `[crash]`
| 配置键 | 类型 | 说明 | 示例 |
|--------|------|------|------|
| `count` | int | 总崩溃次数 | `15` |
| `lastTime` | string | 最后崩溃时间 | `"2024-01-15 10:30:45"` |
| `lastCode` | string | 最后退出代码 | `"0xC0000005 (ACCESS_VIOLATION)"` |
| `lastRunMs` | string (uint64) | 最后运行时间(ms) | `"25000"` |
### 2.2 退出代码描述
常见的 Windows 异常代码:
| 代码 | 描述 |
|------|------|
| `0xC0000005` | ACCESS_VIOLATION访问违规 |
| `0xC0000094` | INTEGER_DIVIDE_BY_ZERO整数除零 |
| `0xC00000FD` | STACK_OVERFLOW栈溢出 |
| `0xC0000409` | STACK_BUFFER_OVERRUN栈缓冲区溢出 |
| `0xC000001D` | ILLEGAL_INSTRUCTION非法指令 |
| `0x80000003` | BREAKPOINT断点 |
正常退出:
| 代码 | 描述 |
|------|------|
| `0` | NORMAL正常退出 |
自定义错误代码1000-1999
| 代码 | 描述 |
|------|------|
| `1001` | CONFIG_ERROR配置错误 |
| `1002` | NETWORK_ERROR网络错误 |
| `1003` | AUTH_FAILED认证失败 |
| `1004` | RESTART_REQUEST请求重启 |
| `1005` | MANUAL_STOP手动停止 |
---
## 三、MTBF 统计
### 3.1 数据收集
| 配置键 | 类型 | 说明 |
|--------|------|------|
| `starts` | int | 启动次数 |
| `totalRunMs` | string (uint64) | 累计运行时间(ms) |
| `count` | int | 崩溃次数 |
**注意**`totalRunMs``lastRunMs` 使用字符串存储 64 位整数,避免 32 位整数的限制(最大约 49 天)。
### 3.2 计算公式
**MTBF平均故障间隔时间**
```
MTBF = 累计运行时间 / 崩溃次数
```
**失败率Failure Rate**
```
失败率 = 崩溃次数 / 启动次数
```
### 3.3 示例
假设:
- 启动次数100 次
- 崩溃次数5 次
- 累计运行时间30 天
计算结果:
- MTBF = 30 天 / 5 = 6 天(平均每 6 天发生一次崩溃)
- 失败率 = 5 / 100 = 5%(每次启动有 5% 概率崩溃)
---
## 四、回调函数设计
### 4.1 回调类型
| 回调 | 触发时机 | 参数 |
|------|---------|------|
| `onAgentStart` | 代理进程启动成功 | processId, sessionId |
| `onAgentExit` | 代理进程退出(任何原因) | exitCode, runtimeMs |
| `onCrash` | 检测到快速崩溃 | exitCode, runtimeMs |
| `onCrashWindowChange` | 崩溃窗口状态变化 | crashCount, firstCrashTime |
| `onCrashProtection` | 触发崩溃保护 | 无 |
### 4.2 调用顺序
正常退出:
```
onAgentExit(exitCode=0, runtime)
```
快速崩溃:
```
onAgentExit(exitCode, runtime)
onCrash(exitCode, runtime)
[如果 count >= 3]
onCrashProtection()
onCrashWindowChange(count, startTime)
```
---
## 五、配置键汇总
Section: `[crash]`
| 配置键 | 类型 | 用途 | 持久性 |
|--------|------|------|--------|
| `count` | int | 总崩溃次数 | 永久 |
| `lastTime` | string | 最后崩溃时间 | 永久 |
| `lastCode` | string | 最后退出代码 | 永久 |
| `lastRunMs` | string | 最后运行时间 | 永久 |
| `totalRunMs` | string | 累计运行时间 | 永久 |
| `starts` | int | 启动次数 | 永久 |
| `winCount` | int | 窗口期崩溃次数 | 临时* |
| `winStart` | string | 窗口起始时间 | 临时* |
| `protected` | int | 崩溃保护标志 | 一次性** |
\* 临时:窗口期过期或系统重启后自动清除
\** 一次性:程序启动时读取并清除
---
## 六、文件改动
| 文件 | 改动内容 |
|------|---------|
| `CrashReport.h` | 配置键定义、退出代码常量、MTBF 计算函数 |
| `ServerSessionMonitor.h` | 回调类型定义、监控器结构体 |
| `ServerSessionMonitor.cpp` | 崩溃检测逻辑、回调调用 |
| `ServerServiceWrapper.cpp` | 回调实现、状态持久化/恢复 |
| `2015Remote.cpp` | 启动时检查崩溃保护标志 |
---
## 七、使用场景示例
### 场景 1连续崩溃
```
T=00:00 代理启动 (startCount=1)
T=00:05 代理崩溃 (crashCount=1, 窗口开始)
T=00:10 代理重启 (startCount=2)
T=00:15 代理崩溃 (crashCount=2)
T=00:20 代理重启 (startCount=3)
T=00:25 代理崩溃 (crashCount=3, 触发保护!)
→ 服务停止重启代理
→ 写入 AgentCrashProtected=1
T=00:30 用户手动启动程序
→ 检测到保护标志,切换到普通模式
→ 弹出提示框
```
### 场景 2服务重启后恢复
```
T=00:00 代理崩溃 (crashCount=1)
T=00:30 代理崩溃 (crashCount=2, 保存到注册表)
T=01:00 服务重启(非系统重启)
→ 加载状态: count=2, elapsed=1分钟
→ 仍在5分钟窗口内恢复状态
T=01:30 代理崩溃 (crashCount=3, 触发保护!)
```
### 场景 3窗口过期
```
T=00:00 代理崩溃 (crashCount=1)
T=02:00 代理崩溃 (crashCount=2)
T=08:00 代理崩溃
→ 距离第一次崩溃已超过5分钟
→ 重置窗口: crashCount=1, 新窗口开始
```
---
## 八、设计决策记录
| 问题 | 决策 | 理由 |
|------|------|------|
| 窗口期时长 | 5 分钟,不可配置 | 足够检测连续崩溃,避免误判 |
| 崩溃阈值 | 3 次,不可配置 | 平衡敏感度和容错 |
| 快速崩溃定义 | 30 秒 | 覆盖大多数初始化场景 |
| 系统重启处理 | 清除窗口状态 | 系统重启后问题可能已修复 |
| 统计数据重置 | 暂不实现 | 需求不明确,后续按需添加 |
| 日志记录 | 使用 Mprintf | 与现有日志系统一致 |
| 非零退出码 | 都算崩溃 | 正常退出必须返回 0 |
| lastRunMs 类型 | string (uint64) | 支持超过 24 天的运行时间 |