Bug
Three WS broadcast loops use raw ws.send() without per-socket error handling. If one socket throws during the loop, the entire iteration aborts and all remaining subscribers miss that tick.
Source
src/routes/ws.ts — three loops: flushPriceUpdate (:436-449), trade.executed listener (:539-549), funding.updated listener (:572-582).
436 for (const client of slabClients) {
437 if (client.ws.readyState === WebSocket.OPEN && ...) {
441 if (client.ws.bufferedAmount > MAX_BUFFER_BYTES) continue;
442 client.ws.send(msg); // ← raw send; can throw
443 }
444 }
Impact
readyState and bufferedAmount are checked, but the socket can transition to closing in the TOCTOU window before send(), which the ws library throws synchronously. The single outer try/catch then aborts the entire loop — every subsequent subscriber in the iteration misses that price/trade/funding tick.
safeSend() was introduced at ws.ts:223-236 precisely to neutralise this for message-handler sends; the fan-out loops were not converted.
Fix
Replace all three raw client.ws.send(msg) calls with safeSend(client.ws, payloadObject). Since safeSend serializes internally, build the payload object once per channel before the loop rather than pre-serializing.
Verification
Subscribe 3 clients to price:<slab>; force one socket into a half-closed state mid-loop; emit a price.updated event → after fix, the other two still receive the tick.
Bug
Three WS broadcast loops use raw
ws.send()without per-socket error handling. If one socket throws during the loop, the entire iteration aborts and all remaining subscribers miss that tick.Source
src/routes/ws.ts— three loops:flushPriceUpdate(:436-449),trade.executedlistener (:539-549),funding.updatedlistener (:572-582).Impact
readyStateandbufferedAmountare checked, but the socket can transition to closing in the TOCTOU window beforesend(), which thewslibrary throws synchronously. The single outertry/catchthen aborts the entire loop — every subsequent subscriber in the iteration misses that price/trade/funding tick.safeSend()was introduced atws.ts:223-236precisely to neutralise this for message-handler sends; the fan-out loops were not converted.Fix
Replace all three raw
client.ws.send(msg)calls withsafeSend(client.ws, payloadObject). SincesafeSendserializes internally, build the payload object once per channel before the loop rather than pre-serializing.Verification
Subscribe 3 clients to
price:<slab>; force one socket into a half-closed state mid-loop; emit aprice.updatedevent → after fix, the other two still receive the tick.