Shared Worker 不会死锁,但因 port 管理缺失、异常未兜底、多端口并发重连及心跳机制不完善,易导致资源滞留、雪崩重连、连接中断或实例崩溃。

Shared Worker 本身不会死锁,但多个页面端口(port)同时异常、未正确关闭或错误处理时,可能引发资源滞留、重连雪崩、心跳失效甚至 Worker 实例意外退出——这种“类死锁”现象常被误认为死锁,实则是状态管理缺失与异常未兜底所致。
确保每个 port 生命周期受控
页面刷新、崩溃或关闭会自动 close 其持有的 port,但若页面 JS 异常卡死(如无限循环),port 可能长期挂起不释放。Worker 端不能被动等待 port 关闭,需主动维护 port 映射并设置超时清理:
- 为每个 port 分配唯一 clientId,并用 Map 存储 port → { clientId, lastActive, heartbeatCount }
- 在 onmessage 中更新 lastActive 时间戳;配合 setInterval 每 30 秒扫描一次,对超过 90 秒无响应的 port 主动调用 port.close() 并清理映射
- 页面应在 unload 或 visibilitychange 为 hidden 时主动发送 { type: "disconnect", clientId },Worker 收到后立即移除对应条目
所有 WebSocket 回调必须加 try-catch
Shared Worker 内任何未捕获的异常(尤其是 onopen/onmessage/onerror 中抛出的错误)会导致整个 Worker 实例终止——此时所有 port 失效,连接中断,且无法自动恢复。这是最隐蔽的“崩溃即中断”根源:
- 每个事件回调开头包裹 try { ... } catch (e) { console.error("WS handler error:", e); self.postMessage({ type: "error", from: "ws", error: e.message }); }
- 禁止在回调中执行可能 throw 的同步操作(如 JSON.parse 无校验、访问 undefined 属性);解析消息前先检查 data?.type 和必要字段
- onerror 不代表连接失败,它可能由非法消息触发;真正判断连接状态应以 readyState 和自定义心跳响应为准
避免多 port 触发重复重连
当连接断开时,若每个 port 都各自请求重连(例如页面监听 visibilitychange 后调用 postMessage({ type: "reconnect" })),Worker 可能并发 new 多个 WebSocket 实例,造成服务端压力与自身状态混乱:
- Worker 内只维护一个全局 ws 实例,所有重连逻辑集中控制;收到任意 port 的 reconnect 请求,仅作为“唤醒信号”,不立即执行 new WebSocket()
- 使用标志位 isReconnecting 防止并发;重连前检查 ws?.readyState === WebSocket.CLOSED || ws?.readyState === WebSocket.CONNECTING
- 首次连接由首个 port 发送 connect 指令触发;后续断线统一由心跳失败或 onclose(配合超时判定)驱动重连,而非依赖页面指令
用心跳 + 超时双机制识别真实失联
仅靠 onclose 或 onerror 无法区分网络抖动、服务端重启还是页面假死。必须结合客户端心跳与服务端 pong 响应做闭环判断:
- Worker 内 setInterval 每 25 秒发一次 ping 消息,附带时间戳;服务端必须原样返回 pong + 同一时间戳
- 维护 per-port 的 pendingPingMap,记录每次 ping 的发送时间;若 2 次连续 pong 未在 5 秒内返回,标记该 port 异常,并触发 ws.close()
- ws.close() 后不立即重连,而是广播 { type: "port-offline", clientId } 给其余存活 port,由它们协同决策是否启动重连(例如:仅当剩余 port ≥ 1 时延迟 1 秒重连)

















