Symptom
oma bridge daemon 持续打印 WS attach failed: HTTP 409 后退避重连,永远 attach 不上 RuntimeRoom。Daemon 进程活着但从 console / API 角度 runtime 显示 offline。
复现日志:
```
! WS attach failed: unexpected response: HTTP 409
→ reconnecting in 8000ms
! WS attach failed: HTTP 409
→ reconnecting in 16000ms
... (每次 408/409,无限循环)
```
根因(推测)
`apps/main/src/runtime-room.ts:63-82` `attachDaemon`:
```ts
const existing = this.ctx.getWebSockets("daemon");
if (existing.length > 0) {
try {
existing[0].send(JSON.stringify({ type: "ping" }));
return new Response("daemon already attached", { status: 409 });
} catch {
try { existing[0].close(1011, "stale"); } catch { /* already closing */ }
}
}
```
CF Hibernatable WebSocket 的 `send()` 不会真验证 TCP 活性 —— 只要 socket 没收到 close,DO 视角就认为它活着。客户端那侧 TCP 早断了(network blip / 重启),但 DO 永远收不到 close → `send()` 不抛 → 永远 409 reject 新 attach。
影响
- daemon 重启后无法重新 attach(除非 console 上手动 `oma runtime rm` + 重 setup)
- 静默服务降级:用户端看 daemon 进程在跑、status 探测 OK,但实际 console 显示 offline,触发 turn 没人响应
修复思路(待讨论)
A. `attachDaemon` 收到 409 前先发 `ping` 等一个 `pong` 超时(比如 3s)才判定 stale;超时则 `close()` 旧 WS、accept 新 WS。
B. Daemon 侧定期 ping,DO 在 `webSocketMessage` 收到的最后时间超过阈值(比如 60s)就认为旧连接死了,新 attach 直接踢掉旧的。
C. 新 attach 一律 evict 旧 WS("last writer wins"),不再 409。最简但失去对 split-brain 的保护。
倾向 B + 后备 C。
Out of scope
Symptom
oma bridge daemon持续打印WS attach failed: HTTP 409后退避重连,永远 attach 不上 RuntimeRoom。Daemon 进程活着但从 console / API 角度 runtime 显示 offline。复现日志:
```
! WS attach failed: unexpected response: HTTP 409
→ reconnecting in 8000ms
! WS attach failed: HTTP 409
→ reconnecting in 16000ms
... (每次 408/409,无限循环)
```
根因(推测)
`apps/main/src/runtime-room.ts:63-82` `attachDaemon`:
```ts
const existing = this.ctx.getWebSockets("daemon");
if (existing.length > 0) {
try {
existing[0].send(JSON.stringify({ type: "ping" }));
return new Response("daemon already attached", { status: 409 });
} catch {
try { existing[0].close(1011, "stale"); } catch { /* already closing */ }
}
}
```
CF Hibernatable WebSocket 的 `send()` 不会真验证 TCP 活性 —— 只要 socket 没收到 close,DO 视角就认为它活着。客户端那侧 TCP 早断了(network blip / 重启),但 DO 永远收不到 close → `send()` 不抛 → 永远 409 reject 新 attach。
影响
修复思路(待讨论)
A. `attachDaemon` 收到 409 前先发 `ping` 等一个 `pong` 超时(比如 3s)才判定 stale;超时则 `close()` 旧 WS、accept 新 WS。
B. Daemon 侧定期 ping,DO 在 `webSocketMessage` 收到的最后时间超过阈值(比如 60s)就认为旧连接死了,新 attach 直接踢掉旧的。
C. 新 attach 一律 evict 旧 WS("last writer wins"),不再 409。最简但失去对 split-brain 的保护。
倾向 B + 后备 C。
Out of scope