원클릭으로
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 注释时的处理策略和伪深度防御也在该文件中。