跳到主要内容
自适应运行时物理设计 · MorphoSys → AURA

第7章:W11 / RDMA dispatch 完整 post-mortem —— 负结果方法论

把 v27 两个最大的工程负结果完整摊开:W11 REMOTE_OWNER takeover 的 99.5% abort cascade 根因(cv.wait_for(5s) × worker pool 16 槽死锁)、W11.2/3/4/5 四次修复尝试时间线、RDMA dispatch 6 步实现到 mlx5 first SEND REMOTE_INVAL_REQ_ERR 的完整还原、何时该 punt 的工程判断框架

Negative Results W11 Backlog RDMA Dispatch Wake-Queue Race REMOTE_INVAL_REQ_ERR Post-mortem Engineering Judgment AURA v27

写 paper 最难的是诚实地讲负结果。v27 有两个最大的工程负结果:W11 REMOTE_OWNER takeover 99.5% abort cascade(2026-05-12 团队 punt)和 RDMA dispatch first SEND REMOTE_INVAL_REQ_ERR(2026-05-13 实现完成但 hardware 层失败)。常见的处理方式是”删掉、当没发生”——但这会让 paper §6.4 RQ4 缺失”CN-only locks 在 RPC fan-in 下的成本”这一关键 row。v27 选择把它们做成 §6.5 caveat + §8 limitation,用负结果反证 thesis——既然 partial CN-only 路径产出 99.5% abort cascade,那正说明”为什么需要 adaptive、为什么不能简单 LOTUS 化”。本章就是这两个负结果的完整 post-mortem——把每一次尝试、每一个 root cause、每一次 punt 决定的工程判断都讲清楚,让你能用它做”怎么把负结果写进 paper 而不是删掉”的方法论参考。

📑 目录


1. 为什么把负结果写进 paper,而不是删掉

很多组在面对”做不动的实验”时第一反应是”那就不写”——这是 paper 的最大暗债。被删掉的负结果在 5 个地方反咬:

  1. reviewer 总会在 RQ4 那里问到——“你 §6 没提 CN-only locks 的对照,为什么?”
  2. 同行复现时会撞到——别人按你的代码跑,会看到 99.5% abort cascade,然后写信问你。
  3. 后续工作没了接口——“为什么不直接 finish W11” 这种问题需要负结果记录才能回答。
  4. 未来回看自己时迷茫——3 个月后回到这个工作,自己也忘了为什么这条路走不通。
  5. 学术诚实成本累积——一次删掉是省事,三次删掉就是研究态度问题。

v27 选择把两个负结果写进 paper。具体策略是 §6.5 caveat row + §8 limitation paragraph

写法优点缺点
完整删除paper 看起来”全 full”5 个暗债
写成 §6.5 caveat rowreviewer 可独立验证,给 future work 留接口占 paper 0.5 页
写成 §8 limitation paragraph不占 §6 篇幅,但留下 root cause不在 evaluation table 里

v27 团队选 caveat row + limitation paragraph 双管齐下。理由很简单:partial CN-only locks 的 99.5% abort cascade 本身就是 thesis 的支持证据——“为什么需要 adaptive 而不是静态 LOTUS 化”,没有比这条数据更直接的反证。

🌟 关键洞察:负结果分两类——“我们的 thesis 错了” 和 “thesis 对,但实现遇到工程墙”。前者必须诚实承认 thesis 走偏;后者反而是 thesis 的反证,应该亮出来。v27 这两个负结果都属于后者。

🧠 教学要点:评委对负结果的态度是”有诚实说出来 + 给出根因解释”远比”删掉装没事”更友好——因为前者可以 generalize 成方法论,后者只是缺数据。


2. W11 root cause:cv.wait_for(5s) × worker pool 16 槽的死锁

W11 是 v27 唯一一个 partial 维度(“仲裁位置”)的根源问题。完整 root cause 如下:

2.1 背景:REMOTE_OWNER takeover 的路径

当 cohort owner 在 CN A 但本次 txn 跑在 CN B 时,CN B 需要走 OwnerRpc 让 CN A 帮它 acquire lock。CN A 上的 OwnerRpcServer 处理路径是:

CN B (caller) ──RPC──▶ CN A OwnerRpcServer

                              ├─ ProxyTxnQueue.push(req)

                              └─ worker.dequeue()  ◀────────┐
                                    │                       │
                                    ├─ execute txn          │
                                    │                       │
                                    └─ ProxyTxnTask.cv      │
                                          .notify_one() ───▶│ reply 路径

                                       worker park if idle: │
                                       cv.wait_for(5s) ─────┘

每个 ProxyTxnTask 上挂一个 condition_variable,worker 干完一单后通过 cv.notify_one() 唤醒 reply path。worker 没活时 park 在 cv.wait_for(5s)

2.2 死锁路径

CN A 的 worker pool 是 8 OS thread × 2 coroutine = 16 个槽。死锁触发链:

  1. 大量 cross-CN txn 同时 fire,CN B/C/D 都向 CN A 发 proxy req → ProxyTxnQueue 堆积
  2. CN A 16 槽 worker 全部”开工”——但每个 worker 在 ProxyTxnTask.cv.wait_for(5s) 等 client side reply 路径完成
  3. 16 槽全部 parked,新 proxy req 进不来
  4. CN B 端因为没收到 reply,OCC validation 超时 → abort
  5. CN B 端继续 fire 新 txn(client driving)→ 加剧 CN A 堆积
  6. 正反馈循环 → 99.5% abort cascade

🍎 直觉比喻:银行 16 个柜员每个都在”等领导签字”,签字台又在领导出差的电话另一头——柜员既不能挪窗口也不能放人走,新客户全堵门外。

2.3 关键证据

证明这条路径,不是”猜”,是实测

指标测量值解读
aura.lock.granted88%Lock 实际被 grant 了——说明 CN A 干活了
txn commit rate0.5%但 99.5% abort——说明 CN B 等不到 reply
ProxyTxnQueue.size持续 ≥ 16队列堆积证据
worker pool busy ratio100%16 槽全部”假忙”(实际 parked)

📊 commit anchorec830c6 —— v27 的 W11 deep-debug post-mortem,含完整 perf 数据 + 死锁 trace。

🧠 关键洞察:这不是”实现 bug”——是 OwnerRpc protocol 设计与 worker pool 大小的不匹配。每个 worker 不应该 park 在 cv.wait_for——但要解开这个 park 又要重构 reply 路径。这就是为什么后续 4 次尝试都修不彻底。


3. W11.2 / W11.3 / W11.4 / W11.5 四次修复尝试时间线

修一次失败一次的过程本身就是 paper 的 “methodology insight”。完整时间线:

尝试commit修复策略实际效果为什么不够
W11.21913c29slot reaper:定期清死 slotabort 略降到 95%处理的是”死 slot”,但活 slot 的 cv.wait_for也卡住
W11.3cce71d5fire-and-forget Release:Release 不等 replyabort 大幅降到 60%引入 SI 安全性疑虑:Release 可能在 commit 完成前就发出
W11.4(未单独 commit,rolled back)busy-yield 替代 cv.wait_forCPU 飙到 100%,吞吐降 30%“用 CPU 换 latency” 治标不治本,根因(reply 路径阻塞 worker)没变
W11.5(未单独 commit,rolled back)bounded timeout:cv.wait_for(50ms)abort 降但出现大量 false abort50 ms 太短,正常 txn 在 reply 到达前就被超时误判 abort

3.1 每次尝试的”诚实记录”

  • W11.2 把”死 slot”清掉,但活 slot仍在等 reply。死锁本质没变,只是被掩盖一部分。
  • W11.3 让 Release 不等 reply。abort 大幅下降,但引入 I2 invariant 风险——OCC validation 完成前的 commit_ts 还没确定,Release 可能让其他 txn 看到 partial 状态。SI 安全性不能保证。
  • W11.4 把 cv.wait_for 换成 busy-yield。CPU 占用爆表(100% × 16 槽 × 4 CN = 64 核全开)。reply 路径仍然是 worker 自己等——根因没动。
  • W11.5 把 timeout 从 5s 砍到 50ms。正常情况下 reply 平均 100-200 ms 才能到,50ms 触发大量误判。问题转化成”如何选 timeout”——但这不是 W11 应该解决的问题。

3.2 punt 决定的触发点

2026-05-12 团队评估:

  • “继续修需要至少 2-3 天,且无法保证 SI” —— 工程估时 + 风险评估
  • 真正的 fix 需要重构 reply 路径(不让 worker 自己等 reply),而不是修 cv.wait_for 的细节
  • 这种重构跟 RDMA dispatch 工作高度重叠——与其修 W11 再做 RDMA dispatch,不如直接做 RDMA dispatch(让 worker 通过 SEND/RECV polling 而不是 cv.wait_for)

决定 punt W11,转向 RDMA dispatch

🍎 教学要点:连续修不动的迹象——“每次修都引入新问题、根因没变、修复成本接近 redesign 成本”——是 punt 的红线。修复成本超过重写成本时,必须停修

MEMOIRABLE QUOTE:v27 团队的内部判断是 “W11.2/3/4/5 这条路上每次都治标不治本——治本要换 transport,那就直接换 transport”


4. RDMA dispatch 6 步实现:从 ProxyTxnQueue 到 ExecuteTxn

W11 punt 之后,v27 立刻转向 RDMA dispatch——目标是让 CN 之间的 ExecuteTxn 走 OwnerRpcRdma (RDMA SEND/RECV),而不是 OwnerRpc (TCP)。期望:吞吐从 13 KOPS → 30+ KOPS(去掉 cv.wait_for 瓶颈)。

4.1 6 步实现的 commit map

Stepcommit改动范围一句话
1-243efe4dProxyTxnQueue.h + TpccBenchmarkExecutor.ccProxyTxnTask 增加 reply_fn / reply_ctx hook,解耦 worker 与 reply transport
3-4740db0bOwnerRpcRdma.h/cc + AuraTypes.h增加 server-side HandleInboundExecuteTxn + client-side ExecuteTxn();引入 kClientRdmaCnId = 16 sentinel
56b1f109BenchRunner.cc + BenchClient.h + TpccClient.ccClient 侧 bootstrap OwnerRpcRdma 实例,TpccClient 路由通过 RDMA
682cf079bench/aura/run_v27_rdma_dispatch_smoke.sh + post-mortemsmoke 脚本 + first SEND failure 完整文档

4.2 关键改动点 walkthrough

Step 1-2 (43efe4d):ProxyTxnTask 增加 reply hook。worker 完成后调用 task.reply_fn(task.reply_ctx, task) 取代 cv.notify_one()

// ProxyTxnQueue.h
using ReplyFn = void(*)(void* ctx, ProxyTxnTask* task);
ReplyFn  reply_fn  = nullptr;
void*    reply_ctx = nullptr;

// TpccBenchmarkExecutor.cc (worker drain)
if (task->reply_fn != nullptr) {
    task->reply_fn(task->reply_ctx, task);   // 新路径
} else {
    task->cv.notify_one();                    // 旧路径,保留向后兼容
}

这是关键一招——让 reply transport 跟 worker 完全解耦,worker 不再需要 park 等 reply。

Step 3-4 (740db0b):OwnerRpcRdma 加 ExecuteTxn 全套。Server side 收到 EXECUTE_TXN_REQ 后 heap-alloc 一个 ProxyTxnTask(带 reply_fn 指向 RDMA SEND trampoline),交给 worker。worker 干完后 reply_fn 走 RDMA 发回 EXECUTE_TXN_REPLY

// OwnerRpcRdma.cc - server-side
void HandleInboundExecuteTxn(MsgHeader& h, ...) {
    auto* task = new ProxyTxnTask{...};
    task->reply_fn = ExecuteTxnReplyTrampoline;
    task->reply_ctx = new ExecuteReplyCtx{this, h.request_id};
    proxy_queue_.push(task);
}

kSlotBytes 从 256 → 1024 容纳 NewOrder ~376 B payload + headers。

Step 5 (6b1f109):Client 侧(在 MN 节点跑 BenchClient)现在也需要 bootstrap 一个 OwnerRpcRdma 实例——self_cn = kClientRdmaCnId = 16,nullptr lock_table/owner_map(client 不当 owner)。

// BenchRunner.cc - client branch
auto rpc_rdma = std::make_unique<OwnerRpcRdma>(self_cn=16, /*lock_table=*/nullptr, /*owner_map=*/nullptr);
rpc_rdma->Init(...);
rpc_rdma->StartServer();
bench->SetRpcRdma(rpc_rdma.get());

TpccClient 路由:如果 --aura_rdma_dispatch=truerpc_rdma_ 非空,走 RDMA;否则回退 TCP。

Step 6 (82cf079):写 smoke harness + 跑实验 + post-mortem。

4.3 设计选择:为什么是 SEND/RECV 而不是 RDMA WRITE

可选方案对比:

方案latencyreply 路径复杂度
TCP (现状)高(cv.wait_for)client 等 reply简单
RDMA WRITE双向 ring buffer + polling高(需 buffer 管理)
RDMA SEND/RECVserver polling CQ中(需 RECV 预 post)

v27 选 SEND/RECV——折中。理由:

  • SEND/RECV 有显式 message boundary,wire format 简单
  • 不需要双向 ring buffer 协调
  • CQ polling 路径已经在 mempool 用了,复用度高

🧠 关键洞察:选 SEND/RECV 而不是 WRITE 是对的——不幸的是 hardware 层在 first SEND 就翻车,这跟选 SEND 还是 WRITE 没关系。


5. mlx5 first SEND REMOTE_INVAL_REQ_ERR 的完整还原

实现完成后,跑 smoke 的结果是:

bootstrap 阶段全成功

  • 4 CN × 4 CN 全连接 QP 都进入 RTS state
  • Client (cn_id=16) 也成功跟 4 CN 建立 QP
  • 所有 RECV 都预 post 好了

第一次实际 dispatch

  • TpccClient 发起 ExecuteTxn → ibv_post_send(EXECUTE_TXN_REQ)
  • polling CQ
  • 收到 IBV_WC_REM_INVAL_REQ_ERR (vendor syndrome 0xd2)

5.1 已排查项(确认不是问题的)

检查项状态
QP state✅ RTS 正常
MR registration✅ lkey/rkey 匹配
Slot size✅ 1024 ≥ 实际 payload 376 + headers
Application wire format✅ MsgHeader 字节布局对齐
peer_addr✅ 相互 reserve、QP info TCP 交换正确
RECV pre-post✅ 远端有 RECV slot
GID 表✅ gid index 3 = RoCEv2 IPv4 (与其他 RDMA 路径一致)

5.2 剩余假设(无法在 CloudLab 验证的)

REMOTE_INVAL_REQ_ERR 的 mlx5 文档解读:“远端报本端发的 request 是 invalid”——但 invalid 在哪一层、由什么字段触发,文档没说。剩余可能性:

  1. mlx5 firmware 对某种 verb 序列敏感——例如 “first SEND 必须先 RECV posted 并经过一次 CQ tick”
  2. GID 表项错配——experiment 网卡 gid index 3 配置可能与 PD/CQ 共享对象冲突
  3. 协议头某字段(如 IMM_DATA)触发硬件检查失败——但我们没用 IMM_DATA
  4. QPN 复用 —— 跟 mempool 那条 RDMA 路径的 QP 共享 PD 时 firmware 内部状态机冲突

5.3 为什么不能继续 debug

需要 mlx5 firmware-level 调试工具

  • mlxlink —— firmware-level diagnostics(CloudLab 不开放 root-level firmware 调试)
  • mlx5dv direct verb —— bypass standard verbs 看底层(需要 patched OFED)
  • ConnectX-6 Dx hardware debugger(厂内才有)

CloudLab 给的是普通 user 权限 + 标准 MLNX_OFED 4.9。剩下假设要验证就需要换环境——这是 punt 触发条件 2(工具/环境超出可调范围)。

📊 commit anchor82cf079 —— 含完整 smoke 输出 + 4 个剩余假设的 post-mortem 文档。

🧠 关键洞察:这个失败不是软件 bug——是硬件抽象层(IB verbs)的”undocumented behavior”。诚实写法:在 paper §6.5 列为”软件实现完整 + 硬件层不可调试 → 留为 caveat”。这种 caveat reviewer 不会拒,因为问题不在作者能控制的范围内。

MEMOIRABLE QUOTE“软件层做完了,硬件层在第一个 SEND 就拒了,syndrome 0xd2 没有可读文档——这是 mlx5 的 undocumented behavior,不是我们的 bug”


6. 何时该 punt:工程判断的 4 条标准

整个 W11 + RDMA dispatch 的过程沉淀出 4 条 punt 标准。每条都在 v27 上有触发证据:

标准 1:连续修不动 + 根因没动

触发:W11.2/3/4/5 四次每次引入新问题但根因(cv.wait_for × 16 槽)没变。

判断:如果 N 次修复都在”治标”,第 N+1 次大概率也是。该停。

例外:如果有人专家入场识别出了根因(如 “重构 reply 路径”),可以继续——但这就不是 N+1 次小修,是 redesign。

标准 2:需要的工具/环境超出可调范围

触发:RDMA dispatch first SEND 失败需要 mlx5 firmware-level debugger,CloudLab 不开放。

判断:debug 工具 / 测试环境是 punt 的 hard limit。可买可借的环境 → 继续;可调的工具 → 继续;都没 → punt 或换方案。

例外:如果机时充裕 + 团队有人有 firmware 经验,可以投入”换环境 debug”。v27 都没有。

标准 3:写成 caveat 的论证更强

触发:partial CN-only locks 的 99.5% abort cascade 本身反证 thesis(adaptive 必须 vs 静态 LOTUS 化会爆)。

判断:如果负结果本身就是证据,写成 caveat 比”修完发”对 thesis 支持力更强。

例外:如果负结果不能反证 thesis,只是”我们没做完”,那不能写成 caveat——必须做完或正面承认 thesis 弱。

标准 4:机会成本超过完成成本

触发:W11 finish 估 2-3 天 vs 用同样时间出 drift workload + tuner ablation 数据。

判断:剩余时间 × 完成概率 × 完成收益 vs 同样时间 × 替代收益。前者小 → punt。

例外:如果替代任务也没明显收益,那继续做正在做的也行。

6.1 4 条标准的复用价值

这 4 条不是 v27 才发明的——是把”工程判断”显式化。任何 system paper 团队都该在 PROGRESS.md / RFC 里把这种 punt 决定留痕:决定时间、4 条标准触发情况、替代方案。3 个月后回看才有据可循。

教学重点punt 不是放弃——是把”完成它的边际收益”换成”另一个更有收益的工作”。判断框架的 4 条标准要在 PROGRESS.md 留痕,未来回看才能解释决定。

🌟 结论:v27 的两次 punt(W11 → RDMA dispatch、RDMA dispatch → paper writing)都是 4 条标准触发的合理决定。


7. 把负结果写成 §6.5 caveat / §8 limitation 的模版

最后给出可复用的写作模版。

7.1 §6.5 caveat row 模版

### §6.5 RQ4: Cost of MN-only vs CN-only lock placement (caveat row)

We attempted to evaluate CN-only lock placement via REMOTE_OWNER
takeover (commits 1913c29 → cce71d5 → ec830c6). The path attains
lock-grant rate of 88% but produces 99.5% abort cascade due to
worker-pool saturation under cross-CN RPC pressure. We document
the root cause in §8 and treat this row as a partial-baseline
caveat rather than a complete number.

| cell | KOPS | atomic/txn | commit_rate | note |
|---|---|---|---|---|
| MN-only (CREST baseline) | 13.64 | 12.16 | 99.92% | §6.2 row |
| CN-only (W11 partial) | — | — | 0.5% | 99.5% abort cascade; §8 |
| AURA full (43% LOCAL) | 13.70 | 12.10 | 99.94% | §6.2 row |

模版的关键三件套

  1. commits 列出来——reviewer 可独立 checkout 验证
  2. root cause 一句话点出来——不要堆细节,让读者知道”我们清楚发生了什么”
  3. caveat 状态明确——不写 ”—” 就行,写明 “99.5% abort cascade; §8”

7.2 §8 limitation paragraph 模版

### §8 Limitations & Discussion

**Partial CN-only lock arbitration**: AURA's REMOTE_OWNER takeover
path is implemented but suffers a worker-pool saturation race when
proxy queue depth exceeds available worker slots (16 in our setup;
see §6.5 caveat row + commit ec830c6 for the deep-debug trace).
Multiple bounded fixes (slot reaper, fire-and-forget Release,
busy-yield, timeout reduction) each introduce a different failure
mode without addressing the root cause: the worker thread itself
blocks on the reply path. A proper fix requires decoupling reply
transport from the worker pool — work that overlaps substantially
with our RDMA dispatch effort.

**RDMA dispatch incomplete at hardware layer**: We implemented
RDMA-based ExecuteTxn dispatch end-to-end through software
(commits 43efe4d → 82cf079) including ProxyTxnQueue hooks,
OwnerRpcRdma extensions, client-side bootstrap, and a complete
smoke harness. Bootstrap succeeds cleanly (all 4×4 CN QPs reach
RTS), but the first ibv_post_send fails with REMOTE_INVAL_REQ_ERR
(vendor syndrome 0xd2). The application-layer wire format is
verified intact; the failure is at the mlx5 verb layer and
requires firmware-level debugging tools (mlxlink, mlx5dv) not
available in our CloudLab environment. Fixing this is left to
future work; the current paper's adaptive-vs-static comparison
rests on the LOCAL takeover path (43% of routes) plus the §6.5
caveat row.

模版的关键四件套

  1. 承认 limitation(不是”我们做完了”)
  2. 指明实现到哪一步(让 reviewer 看到诚意)
  3. 解释 root cause(让 reviewer 信服你清楚问题)
  4. 指明 future work 方向(不是”我们放弃了”,是”下一步可以这样做”)

🌟 关键洞察:caveat / limitation 写法本身就是 paper 的卖点之一。一篇有诚实 limitation 章节的 paper 比一篇”全 full 完美数据”的 paper 在 reviewer 眼里更可信——因为它给评委留了 sanity check 的接口。


✅ 自我检验清单

  • 写负结果的理由:能说出删除负结果的 5 个暗债 + 写成 caveat 的 2 个收益
  • W11 root cause:能不查资料默写 cv.wait_for(5s) × 16 槽 死锁的完整路径 + 88% grant vs 0.5% commit 的解释
  • W11.2/3/4/5:能默写 4 次尝试 commits + 每次失败的具体原因(“治标不治本”在哪)
  • RDMA dispatch 6 步:能默写 6 步 + 每步 commit + 关键改动(ProxyTxnTask hook / kClientRdmaCnId / kSlotBytes 1024)
  • REMOTE_INVAL_REQ_ERR:能描述已排查项(7 条 ✅)+ 剩余假设(4 条)+ 为什么不能继续 debug
  • punt 4 条标准:能默写 4 条 + 每条对应的 v27 触发证据
  • caveat 三件套:能写出 §6.5 caveat row 的 commits + root cause + status 模版
  • limitation 四件套:能写出 §8 limitation 的承认 + 实现状态 + root cause + future work 模版

📚 参考资料

概念入门

  • v27 paper outline §6.5 + §8 —— 负结果在 paper 中的位置
  • v27 paper outline “Co-author decision points” —— 团队 punt 决定的 source of truth
  • v27 paper outline “Cumulative commit map” —— 所有 commit 的完整列表

关键论文

  • “How to Get Your CS Paper Accepted at a Top-Tier Conference” 类方法论文章 —— evaluation 章节诚实化范式
  • MorphoSys (VLDB’20) —— evaluation 章节的”诚实负结果”格式启发
  • Bao (SIGMOD’21) —— “我们做不到的事 / 留给 future work” 模版参考

行业讨论

  • 模块二十三《AURA 论文精讲》第8章-Router-Centric实现与Lever-B负结果 —— v25 的 Lever B 负结果章节(小规模负结果模版)
  • CREST-aura-impl/PROGRESS.md —— W11/W12/W13/W14 全部尝试的工程记录
  • docs/aura/v27_paper_outline.md “Co-author decision points” 5 条 —— team punt 决定的 source of truth

框架文档(代码 anchor)

  • src/transaction/aura/OwnerRpc.cc —— TCP dispatch 路径,cv.wait_for(5s) 在 OwnerRpcServer
  • src/transaction/aura/OwnerRpcRdma.cc —— RDMA dispatch 实现
  • src/transaction/aura/ProxyTxnQueue.h —— ProxyTxnTask + reply_fn hook
  • benchmark/BenchRunner.cc —— client bootstrap OwnerRpcRdma 代码
  • bench/aura/run_v27_rdma_dispatch_smoke.sh —— 失败 smoke 的 harness
  • results/v27_rdma_dispatch_step6_smoke/ —— first SEND failure 完整日志

📎 v25 对照视角:模块二十三-AURA 论文精讲 第8章-Router-Centric实现与Lever-B负结果 —— v25 时期负结果章节较短(只讲 Lever B 一处);v27 大幅扩充以承担 §6.5 + §8 的双重职责