baa-conductor

git clone 

commit
b2f6ea7
parent
c87af13
author
jiaozhiwang
date
2026-03-26 20:25:59 +0800 CST
docs: add FIX-BUG-011/012/013 fix cards for codex
4 files changed,  +185, -5
A bugs/FIX-BUG-011.md
+56, -0
 1@@ -0,0 +1,56 @@
 2+# FIX-BUG-011: writeHttpResponse drain handler 永久挂起
 3+
 4+## 关联 Bug
 5+
 6+BUG-011-writeHttpResponse-drain-handler-hangs.md
 7+
 8+## 目标
 9+
10+修复 `writeHttpResponse()` 中 drain 等待无 close 监听导致协程挂死的问题。
11+
12+## 修改文件
13+
14+`apps/conductor-daemon/src/index.ts` — `writeHttpResponse()` 函数
15+
16+## 修改方案
17+
18+在每个 `await new Promise(resolve => writableResponse.on("drain", resolve))` 处,同时监听 `close` 事件,任一触发即 resolve 并清理另一个 listener。
19+
20+有两处需要修改:
21+
22+### 第一处:初始 body 写入的 drain 等待
23+
24+将:
25+```typescript
26+if (!writableResponse.write(payload.body)) {
27+  await new Promise<void>((resolve) => {
28+    writableResponse.on?.("drain", resolve);
29+  });
30+}
31+```
32+
33+改为:
34+```typescript
35+if (!writableResponse.write(payload.body)) {
36+  await new Promise<void>((resolve) => {
37+    const onDrain = () => { cleanup(); resolve(); };
38+    const onClose = () => { cleanup(); resolve(); };
39+    const cleanup = () => {
40+      writableResponse.off?.("drain", onDrain);
41+      writableResponse.off?.("close", onClose);
42+    };
43+    writableResponse.on?.("drain", onDrain);
44+    writableResponse.on?.("close", onClose);
45+  });
46+}
47+```
48+
49+### 第二处:streamBody 循环内的 drain 等待
50+
51+同样模式,替换 streamBody `for await` 循环中的 drain 等待。
52+
53+## 验收标准
54+
55+1. `pnpm typecheck` 通过
56+2. `pnpm test` 通过
57+3. 不引入新的 lint 或类型警告
A bugs/FIX-BUG-012.md
+75, -0
 1@@ -0,0 +1,75 @@
 2+# FIX-BUG-012: browser-request-policy waiter 死锁
 3+
 4+## 关联 Bug
 5+
 6+BUG-012-browser-request-policy-waiter-deadlock.md
 7+
 8+## 目标
 9+
10+防止 `acquireTargetSlot` 和 `acquirePlatformAdmission` 中的 waiter Promise 永久挂起。
11+
12+## 修改文件
13+
14+`apps/conductor-daemon/src/browser-request-policy.ts`
15+
16+## 修改方案
17+
18+### 方案 A(推荐):给 waiter 加超时
19+
20+在 `acquireTargetSlot` 中:
21+
22+```typescript
23+private async acquireTargetSlot(state: BrowserRequestTargetState): Promise<void> {
24+  if (
25+    state.inFlight < this.config.concurrency.maxInFlightPerClientPlatform
26+    && state.waiters.length === 0
27+  ) {
28+    state.inFlight += 1;
29+    return;
30+  }
31+
32+  await new Promise<void>((resolve, reject) => {
33+    const timer = this.setTimeoutImpl(() => {
34+      const index = state.waiters.indexOf(onReady);
35+      if (index !== -1) state.waiters.splice(index, 1);
36+      reject(new BrowserRequestPolicyError(
37+        "waiter_timeout",
38+        "Timed out waiting for a browser request slot.",
39+        { timeoutMs: WAITER_TIMEOUT_MS }
40+      ));
41+    }, WAITER_TIMEOUT_MS);
42+    const onReady = () => {
43+      clearTimeout(timer);
44+      resolve();
45+    };
46+    state.waiters.push(onReady);
47+  });
48+}
49+```
50+
51+超时常量建议 `WAITER_TIMEOUT_MS = 120_000`(2 分钟)。
52+
53+同样模式应用到 `acquirePlatformAdmission`。
54+
55+### 方案 B(防御性补充):releaseTargetSlot 防御检查
56+
57+在 `releaseTargetSlot` 中增加防御:
58+
59+```typescript
60+private releaseTargetSlot(state: BrowserRequestTargetState): void {
61+  const waiter = state.waiters.shift();
62+  if (waiter != null) {
63+    waiter();
64+    return;
65+  }
66+  state.inFlight = Math.max(0, state.inFlight - 1);
67+}
68+```
69+
70+当前已有此逻辑,确认 `Math.max(0, ...)` 防御到位。
71+
72+## 验收标准
73+
74+1. `pnpm typecheck` 通过
75+2. `pnpm test` 通过
76+3. 新增单元测试:模拟 lease 未 complete 的场景,验证后续请求在超时后返回错误而非永久挂起
A bugs/FIX-BUG-013.md
+41, -0
 1@@ -0,0 +1,41 @@
 2+# FIX-BUG-013: stream session timer 未清除
 3+
 4+## 关联 Bug
 5+
 6+BUG-013-stream-session-timer-not-cleared.md
 7+
 8+## 目标
 9+
10+在 stream session 关闭时清除 openTimer 和 idleTimer,避免 GC 延迟和多余回调。
11+
12+## 修改文件
13+
14+`apps/conductor-daemon/src/firefox-bridge.ts` — `FirefoxBridgeApiStreamSession` 类
15+
16+## 修改方案
17+
18+在 `finishWithEvent` 方法中,在设置 `this.closed = true` 之后,立即清除两个 timer:
19+
20+```typescript
21+private finishWithEvent(event: FirefoxBridgeStreamEvent): boolean {
22+  this.closed = true;
23+
24+  // 清除未触发的 timer
25+  if (this.openTimer != null) {
26+    this.clearTimeoutImpl(this.openTimer);
27+    this.openTimer = null;
28+  }
29+  if (this.idleTimer != null) {
30+    this.clearTimeoutImpl(this.idleTimer);
31+    this.idleTimer = null;
32+  }
33+
34+  // ... 现有的 enqueue 和 onClose 逻辑
35+}
36+```
37+
38+## 验收标准
39+
40+1. `pnpm typecheck` 通过
41+2. `pnpm test` 通过
42+3. 不引入新的 lint 或类型警告
M bugs/README.md
+13, -5
 1@@ -6,15 +6,23 @@
 2 - 一个通用 `BUG-TEMPLATE.md`
 3 - 对应 bug 的修复任务卡
 4 
 5+## 已关闭
 6+
 7 按当前工作区状态,这 3 个 bug 都已完成修复,但仍保留文档作为根因与回归记录:
 8 
 9-1. `BUG-008` — 已随 `BUG-010` 一并修复;如果后续 recent events 出现 `app-server.turn.completed.missing` / `failureClass=transport_closed_before_turn_completed`,应作为新的 child / transport bug 记录,而不是 reopen 旧尾包问题
10-2. `BUG-009` — 已修复;如果后续测试绕开 `withRuntimeFixture(...)` 或关闭路径本身阻塞,仍需新开专项问题
11-3. `BUG-010` — 已修复;如果后续还有 `app-server.transport.closed` 且同一 turn 没有合法 `app-server.turn.completed`,应按新 bug 跟踪
12+1. `BUG-008` — 已随 `BUG-010` 一并修复
13+2. `BUG-009` — 已修复
14+3. `BUG-010` — 已修复
15+
16+## 待修复
17 
18-修复任务:
19+| # | 文件 | 问题 | 严重度 | 修复卡 |
20+|---|---|---|---|---|
21+| BUG-011 | `BUG-011-*.md` | writeHttpResponse drain handler 永久挂起 | Medium-High | FIX-BUG-011.md |
22+| BUG-012 | `BUG-012-*.md` | browser-request-policy waiter 死锁 | Medium | FIX-BUG-012.md |
23+| BUG-013 | `BUG-013-*.md` | stream session timer 未清除 | Low | FIX-BUG-013.md |
24 
25-- FIX-BUG-008:`/Users/george/code/baa-conductor/bugs/FIX-BUG-008.md`
26+修复优先级:BUG-011 > BUG-012 > BUG-013
27 
28 ## 编号规则
29