Replies: 1 comment
|
诊断和修法都对——服务端 ping 是这类"中间盒静默收割空闲连接"的标准解法,25 秒也选得合适(nginx 的 一、你的第 3 步其实是两种故障,而你的修法只治得了其中一种这两个"或"是不同的东西:
你的心跳完美解决了左边那一列:让通道上一直有流量,中间盒就不会认为它空闲。但右边那一列它治不了——手机被系统挂起时,服务端 ping 得再勤,那头也可能不会应。更麻烦的是,如果按你说的再加一个"漏 N 个 pong 就 terminate"的收割器,它会主动杀掉那些本来切回前台就能继续用的手机连接,把一个"回来时可能已经断了"变成"回来时保证已经断了"。 所以这两个目标(保活 vs 探活)在移动场景下是互相打架的,而这正是心跳实现最容易做错的地方。 建议把右边那一列交给客户端的 这样两边各管各的:服务端 ping 防中间盒回收, 二、这条 bug 的伤害等级值得在原帖里说得更重你写的是"It reads as 'the UI is frozen'"。我觉得可以更进一步:丢的是审批提示,而审批是一个安全控制。 用户看到的是"UI 卡住了",实际发生的是一个需要人拍板的动作正在等待,而拍板的人根本不知道有这回事。两种后果都不好:
而且这跟你写的部署形态叠加起来更糟:恰恰是"通过隧道从外面访问"的人最可能在手机上、最可能切后台,也就是最容易丢审批提示的那批人。 建议把"approval prompts 属于安全控制面,静默丢帧意味着一个安全提示没有送达"这句话直接写进 Impact 段。同样一份技术报告,定性成"UI 体验问题"还是"安全提示投递不可靠",得到的优先级会完全不同,而按你自己给的证据,后者才是准确的描述。 三、一个小的实现提醒你的片段里清了 边界与利益相关我们不修 DSH 自家组件—— 利益相关:我维护 pi2dsh(Pi 生态兼容层),它在 DSH Web 里也有自己的一半。这条不推销:我们的浏览器半边走的是自己注册的 HTTP 前缀路由、不碰 |
Uh oh!
There was an error while loading. Please reload this page.
Issue 1 draft — WebSocket downlinks have no heartbeat; live events (e.g. approval prompts) silently stop behind a tunnel/proxy
Problem
When the web UI is served behind a WebSocket-terminating proxy or tunnel (Cloudflare Tunnel, nginx, many corporate proxies), an idle
events.mux/events.hostWebSocket is silently reaped by the middlebox after an idle period. No close frame is delivered to either end.The client reconnect logic only fires on a detected close/error. A silently-dead socket is never detected, so the UI keeps believing it is connected and stops receiving live frames — most visibly, approval prompts (
approval/requested) never pop up; the user only sees them after a manual page refresh (which rebuilds state from the baseline replay).Why it happens
packages/client/connection/src/websocket-downlink.tspumps frames server→client but sends no protocol-level ping/keepalive, and the client likewise sends nothing upstream (the downlink is intentionally one-way: any client message is rejected with1008 downlink only). With no traffic in either direction during idle periods, the proxy drops the connection without notice.Reproduction
dsh webbehind Cloudflare Tunnel (cloudflared), open the UI through the public hostname.workspace-write+ask).events.muxlistener the server does emitapproval/requestedthe moment the tool asks — so the server push is fine; the frame is lost in the dead socket.Evidence captured while diagnosing
approval/asked(session event) immediately followed by anapproval/requestedmux frame on a healthy connection.Proposed fix
Send a protocol-level ping on a short interval from the server on each downlink. Browsers answer pongs in the background without page activity, which (a) keeps the tunnel/proxy segment alive and (b) lets genuinely dead peers surface quickly so the existing reconnect logic fires.
Reference implementation that fixed it for us (in
websocket-downlink.ts, insideupgrade):A missed-pong timeout (terminate after N missed pongs) would additionally make the server actively reap dead peers so the client reconnects on resume.
Impact
Anyone deploying the web UI behind a tunnel/reverse proxy — a documented, common deployment (
--trusted-hostexists precisely for it) — hits this on every approval/question/steering prompt. It reads as "the UI is frozen" and only a manual refresh recovers it.All reactions