بنقرة واحدة
deepen-wiki
编写或审计核心层文档时确保知识深度。
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
编写或审计核心层文档时确保知识深度。
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
写入新模块文档、重构模块、或用户要求分析模块时触发。
Bug 修复或对接问题排查确认有效后,将经验记录到知识库。
修改 PMR 分配器、内存池配置、热路径容器、对象生命周期管理代码后触发。
编写或修改性能基准测试、分析 benchmark 结果、优化热路径性能时触发。
新增或修改 enable_shared_from_this 类、co_spawn 调用、shared_ptr 捕获的 lambda、co_await 后的成员访问时触发。
新增或修改原子操作、异步定时器、无锁通道、跨线程状态同步、CAS 竞争相关代码时触发。
| name | deepen-wiki |
| description | 编写或审计核心层文档时确保知识深度。 |
本 skill 负责文档的深度内容(设计意图、约束、故障模式、跨模块协议)。骨架部分(frontmatter、段落结构、函数签名表)按项目 wiki 的通用格式规范执行。
适用范围:仅 core/ 层 API 文档(type: api)和 dev/ 层编码指南。ref/、docs/、client/ 不适用。
前置条件:必须先按通用 wiki 格式规范完成骨架(frontmatter、段落结构、函数签名表)。本 skill 不负责骨架格式,而是在已有骨架上注入深度。
代码文档的深度不是行数,而是回答了几个读者无法从源码直接得到答案的问题。
好的深度文档回答五类问题:
| # | 问题 | 源码能回答吗 | 文档必须回答 |
|---|---|---|---|
| 1 | 为什么这么设计? | 不能 | 设计动机 |
| 2 | 有什么隐藏约束? | 部分(散在注释里) | 显式列出 |
| 3 | 出错了会怎样? | 部分(散在 trace 里) | 故障模式 |
| 4 | 和其他模块有什么隐式协议? | 不能 | 跨模块契约 |
| 5 | 改了这个会影响谁? | 不能 | 变更敏感度 |
设计动机解释为什么选择这个实现方式,而不是其他可行方案。它回答"作者面对什么问题时做了这个选择"。
@details 和 @note:这是作者自己写的意图防止、避免、确保、否则、必须、限制、复用、解耦在 API 文档的「概述」段落末尾,加一个 ## 设计决策 子段落(S 档必写,A 档可选,B 档不要写)。
## 设计决策
### 为什么 X 使用 Y 模式?
[一句话描述问题]
[两到三句话解释选择原因和替代方案的取舍]
**后果**: [这个选择带来的限制或好处]
以下是三类常见的设计决策模式,提取时对号入座:
模式 1:性能关键路径上的非显而易见选择
当一个看似多余的类型包装或间接层出现在热路径上时,通常是零分配或避免悬挂指针。
### 为什么会话计数器用 shared_ptr<atomic<T>> 包装?
会话关闭回调在对象析构后才执行。如果回调捕获裸指针,
所属模块部分析构后回调访问的是悬挂指针。
通过 shared_ptr 包装计数器,回调只捕获计数器本身,
独立于所属模块生命周期。
**后果**: 控制块生命周期可能长于所属模块,但开销可忽略
(每个模块仅一个计数器)。
模式 2:看似多余但实际有保护作用的代码
当出现 std::move 后对空容器操作、冗余的状态检查、双重锁等,通常是防范重入或迭代器失效。
### 为什么 close() 用 std::move(map) 而不是直接迭代?
子对象的 close() 会回调本对象的 remove 方法,后者执行
map.erase()。如果迭代的是 map 本身,erase 导致迭代器失效。
先 std::move 把 map 移到局部变量,原 map 变空。
后续 remove 方法对空 map 的 erase 是空操作。
**后果**: close() 只能调用一次。由原子标志的幂等性保证。
模式 3:类型选择中的权衡
当选择了看起来"不对"的类型(如 string_view 而非 string、span 而非 vector),通常是零拷贝或避免分配。
### 为什么路由查找使用透明哈希?
每次请求的路由查找需要用 `string_view`(来自协议解析的零拷贝视图)
查 `unordered_map<string, endpoint>`。普通查找会构造临时 string,
触发堆分配。
使用 `is_transparent` 透明查找,`string_view` 直接查表,热路径零分配。
**后果**: 哈希函数和相等比较必须保证跨类型一致性
(统一用 `hash<string_view>` 计算哈希值)。
在写「设计决策」时,逐条检查:
@note、@warning、@details 标注了设计理由?shared_ptr<atomic> 而非 atomic 成员)?std::move 后对空容器操作)?约束是使用此代码时必须遵守的规则。违反约束不会产生编译错误,但会在运行时导致未定义行为、数据竞争、资源泄漏或安全漏洞。
| 类型 | 说明 | 严重度 |
|---|---|---|
| 生命周期约束 | A 必须比 B 活得长 | 违反→悬挂指针/崩溃 |
| 调用顺序约束 | 必须先调 X 再调 Y | 违反→未初始化/数据丢失 |
| 线程安全约束 | 必须在特定上下文调用 | 违反→数据竞争 |
| 状态前置条件 | 调用时对象必须处于某状态 | 违反→逻辑错误 |
| 资源上限约束 | 数据大小/数量有硬上限 | 违反→溢出/OOM |
在 API 文档中加 ## 约束 段落(S 档必写,A 档必写,B 档写重要的)。
放在「依赖关系」之后、「函数签名表」之前。
## 约束
### [约束名称]
**类型**: 生命周期 / 调用顺序 / 线程安全 / 状态前置 / 资源上限
**规则**: [一句话描述约束]
**违反后果**: [具体会出什么问题]
**源码依据**: [文件:行号]
模式 1:状态前置 — 某方法仅在特定状态下安全
### rewind 只能在纯读取阶段使用
**类型**: 状态前置
**规则**: rewind() 仅在未发生写入时有效。
认证阶段是纯读取,安全 rewind。一旦开始写入,状态不可恢复。
**违反后果**: rewind 后读取到不一致的状态,
可能导致协议解析错误或安全漏洞。
**源码依据**: snapshot.hpp 布尔标志字段
模式 2:调用顺序 — 初始化依赖
### start() 必须在事件循环运行前调用
**类型**: 调用顺序
**规则**: start() 必须在事件循环 run() 之前调用,
否则内部清理协程不会启动。
**违反后果**: 过期资源永远不会被清理,容器无限增长。
**源码依据**: pool.hpp start() 方法注释
模式 3:线程安全 — 非线程安全但有例外
### 工作线程不可跨线程共享
**类型**: 线程安全
**规则**: 所有成员访问必须在所属线程内进行。
只有 dispatch() 和 snapshot() 是线程安全的
(内部使用 post 跨线程分发)。
**违反后果**: 数据竞争,未定义行为。
模式 4:资源管理 — RAII 依赖
### 连接包装器必须显式析构或 reset
**类型**: 资源管理
**规则**: 包装器必须在底层资源不再需要前析构或
显式 reset()/release()。否则资源不会被归还池中。
**违反后果**: 资源泄漏。池中可用资源逐渐耗尽。
@warning 标注?(最常见的约束信号)noexcept 方法但内部依赖有效状态?(状态前置)atomic 成员但文档说"不是线程安全的"?(局部线程安全)故障模式描述代码在异常条件下怎么失败、失败时什么表现、如何恢复。它不是"函数返回错误码"的罗列,而是端到端的故障场景。
错误码(bad):函数返回 timeout
故障场景(good):上游服务器无响应时,连接池等待超时后返回 timeout。
连接未归还池中。调用方会尝试下一个端点或返回错误。
最终会话关闭,客户端收到连接重置。
S 档文档加 ## 故障场景 段落,放在「调用链」之后。
A 档文档只在关键路径(网络 I/O、加密、状态机)写故障场景。
B 档不写。
## 故障场景
### [场景名称]
**触发条件**: [什么情况会导致这个故障]
**传播路径**: [错误从哪里产生,经过哪些层,最终到哪]
**外部表现**: [客户端/运维看到什么现象]
**恢复机制**: [系统如何自动恢复,或需要人工干预]
**日志关键字**: [grep 什么字符串可以在日志中定位此故障]
模式 1:级联失败 — 从底层到顶层的完整传播
### 所有候选地址连接失败
**触发条件**: 目标域名的所有解析结果都无法建立连接。
常见原因:目标宕机、防火墙阻断、本地网络中断。
**传播路径**: racer 每个候选连接超时或拒绝 →
所有 pending 递减到 0 且无 winner → 返回错误码 →
上层重试(默认 N 次)→ 全部失败 → 转发层返回错误 →
会话关闭 → 客户端收到 RST。
**外部表现**: 客户端连接超时。
**恢复机制**: 自动。下次请求重新触发解析和竞速。
如果是临时网络故障,新请求可能成功。
**日志关键字**: 模块名 + 错误码
模式 2:资源耗尽 — 自限流与自动恢复
### accept() 因文件描述符耗尽而失败
**触发条件**: 系统文件描述符达到上限。
**传播路径**: accept() 返回错误 →
指数退避(10ms → 20ms → 40ms → ... → 上限 1s) →
持续重试直到有 fd 释放 → 恢复 accept。
**外部表现**: 新连接间歇性无法建立。已建立的连接不受影响。
**恢复机制**: 自动。指数退避避免 CPU 空转。
当旧连接关闭释放 fd 后,accept 自动恢复。
**日志关键字**: accept + error
模式 3:安全检测 — 主动防御
### 密钥交换输出全零(低阶点攻击)
**触发条件**: 攻击者构造特殊的公钥,使得密钥交换输出全零。
**传播路径**: 密钥交换成功返回 → 全零检查捕获 →
日志警告 → 返回错误码 → 握手失败 →
回退到伪装目标或关闭连接。
**外部表现**: 连接被拒绝,日志出现警告信息。
**恢复机制**: 自动。该连接被拒绝,不影响其他连接。
攻击者无法获得任何有用的认证信息。
两个模块之间的隐式协议 — 不在类型系统或接口签名中体现, 但违反了就会出问题的约定。
| 契约类型 | 说明 |
|---|---|
| 初始化顺序 | A 必须在 B 之前初始化 |
| 生命周期绑定 | A 持有 B 的引用,B 必须比 A 活得长 |
| 回调重入 | A 调用 B,B 的回调又会调用 A(需要防范重入) |
| 数据格式约定 | A 产生的数据 B 消费,格式必须匹配 |
| 线程归属 | A 的实例只被特定线程使用,B 必须遵守 |
在「依赖关系」表格中增加一行契约说明。S 档必写,A 档重要契约必写。
在依赖关系表格后加:
### 跨模块契约
| 模块 A | 模块 B | 契约内容 |
|--------|--------|---------|
| 上层 | 下层 | 上层负责 X;下层假设 Y;违反后 Z |
模式 1:回调重入契约
### 跨模块契约
| 模块 A | 模块 B | 契约内容 |
|--------|--------|---------|
| 子对象 | 容器 | 子对象::close() 内部调用容器::remove()。这是重入调用:容器::close() → 子对象::close() → 容器::remove()。容器用 std::move(map) 在迭代前清空,确保 remove() 的 erase 操作安全(对空 map 的 erase 是空操作) |
模式 2:初始化顺序契约
### 跨模块契约
| 模块 A | 模块 B | 契约内容 |
|--------|--------|---------|
| 注册模块 | 使用模块 | register() 必须在使用模块首次使用前调用(启动流程保证)。注册顺序决定默认优先级 |
模式 3:副作用契约
### 跨模块契约
| 模块 A | 模块 B | 契约内容 |
|--------|--------|---------|
| 健康检查 | 连接池 | check() 临时设置 socket 为非阻塞模式,检查后恢复原始状态。如果恢复失败,后续 async 操作可能阻塞在非预期模式 |
friend 声明?(特权访问契约)描述修改此模块时,哪些改动会影响其他模块,以及其他模块的哪些改动会破坏此模块。
S 档文档末尾加 ## 变更敏感度 段落。A 档不写。
## 变更敏感度
### 对外影响(改这里会影响谁)
| 变更 | 影响范围 | 影响 |
|------|---------|------|
| 修改 close() 的幂等性假设 | 所有子对象 | 它们的 close() 依赖 remove() 对空容器安全 |
| 修改内部数据结构 | 所有子类 | 子类直接操作该 buffer |
### 对内影响(改别人会影响这里)
| 上游变更 | 本模块受影响 | 需要检查 |
|---------|------------|---------|
| 子对象增加新的关闭路径 | remove() 被新路径调用 | 确认容器状态一致 |
| 依赖层新增装饰器 | cancel()/close() 穿透 | 确认新装饰器正确转发 |
friend 和继承关系:基类改了,所有子类都受影响wiki 格式规范的 Pitfall #15 说"不要贴源码片段"。 这个规则过于绝对。以下定义什么时候该贴、什么时候不该贴。
展示调用模式:读者无法仅从函数签名理解的组合使用方式
// 好:展示了 shared_ptr<atomic> 的使用模式
auto counter = metrics.session_counter(); // shared_ptr 拷贝
auto on_closed = [counter]() noexcept
{
counter->fetch_sub(1U, std::memory_order_relaxed); // 安全
};
展示关键算法:不可从函数名直接推断的逻辑
// 好:EMA 平滑算法的具体参数
smoothed = (smoothed * 7 + effective) / 8;
展示错误处理路径:多步恢复逻辑
// 好:fd 转移后的错误恢复
auto native_handle = sock.release();
migrated.assign(protocol, native_handle, ec);
if (ec || !migrated.is_open())
{
closesocket(native_handle);
}
问自己:如果删掉这段代码,只保留文字描述,读者能否理解?
完成提取后,运行 audit-and-templates.md 中的审计脚本验证质量。新建 S 档文档时参照其中的完整模板结构。源码无 WHY 注释时的处理策略和伪深度防御也在该文件中。