Commit 83b0c35
authored
[grid] Apply the WebSocket frame fast path on the Node (#17545)
The Router has had a direct frame-forwarding path between the Netty
pipeline and the upstream JDK WebSocket since db9b07a (2026-03-11,
"[grid] Router WebSocket handle dropped close frames, idle disconnects,
high-latency proxying", #17197). Once the client-side handshake
completes, an inbound WebSocketFrameProxy forwards each Netty
WebSocketFrame straight to the upstream WebSocket, and the outbound
DirectForwardingListener writes upstream replies directly to the
client channel. Together those removed the per-frame Message
allocation and the executor hop in WebSocketMessageHandler on the
Router side.
The Node still did the full round-trip through MessageInboundConverter,
WebSocketMessageHandler, the registered Consumer<Message>, and
MessageOutboundConverter in both directions for every frame. Each
frame allocated a TextMessage or BinaryMessage and hopped onto the
channel executor on delivery. For a busy CDP or VNC session that is
measurable allocation and executor-queue pressure on the Node.
Apply the same PostUpgradeHook pattern on the Node side: the consumer
returned from ProxyNodeWebsockets installs a WebSocketFrameProxy after
the handshake so inbound frames forward straight to the browser-side
WebSocket, and a DirectForwardingListener writes outbound frames
directly to the client channel. Frames received before the handshake
are buffered in arrival order and drained on handover, so a frame
cannot land in a pipeline that has already had its Message-layer
handlers removed.
The hardening that the Router-side listener picked up in 8d8cf64
(2026-05-14, "[grid] Close pre-handshake race in WebSocket proxy",
#17435) is mirrored on the Node listener: the pre-handshake buffer is
capped at 128 frames with a 1009 close recorded on overflow; the
close code and reason are recorded on pre-handshake close or error so
a late onUpgrade can write a clean close frame to the client and tear
the channel down rather than leaving it open; and the buffer is
released on close so ref-counted frames cannot leak if the handshake
never completes.
Close-frame reasons coming from the upstream are now truncated to the
123-byte UTF-8 cap that RFC 6455 §5.5.1 imposes. The truncation uses
a CharsetEncoder writing into a 120-byte buffer so it stops at a
clean character boundary on overflow — a naive byte-truncate-then-
decode could split a multi-byte sequence, produce a U+FFFD replacement
on decode, and re-encode back over 123 bytes, breaking the close
frame. The helper lives as a public static on WebSocketFrameProxy
because both DirectForwardingListener classes already depend on that
class. The Router-side listener that landed in #17435 had the same
unchecked path; apply the helper there too so both proxies share the
same safe behaviour.
The Node-specific behaviour is preserved:
- Session-activity heartbeats (sessionConsumer.accept(sessionId)) fire
per frame, both pre- and post-handshake.
- The connectionReleased CAS still guards a single
node.releaseConnection call across the close and error paths,
including the overflow path introduced here.
- VNC sessions still install a no-op heartbeat consumer so VNC
traffic does not mark the session as recently active.
The existing ProxyNodeWebsocketsTest continues to exercise the slot
accounting, including the regression from #17197 where onError without
a follow-on onClose used to leak the slot. New unit tests in
NodeDirectForwardingListenerTest pin the per-frame heartbeat, the
buffer-then-drain ordering, the surface-and-teardown behaviour on a
pre-handshake close, the overflow path's clean release of the session
slot, and the safe truncation of an overlong upstream close reason
that contains multi-byte UTF-8 characters. The shared helper has a
focused unit test alongside it in WebSocketFrameProxyTest.1 parent 73019cd commit 83b0c35
7 files changed
Lines changed: 647 additions & 95 deletions
File tree
- java
- src/org/openqa/selenium
- grid
- node
- router
- netty/server
- test/org/openqa/selenium
- grid/node
- netty/server
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
| 24 | + | |
24 | 25 | | |
25 | 26 | | |
26 | 27 | | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
27 | 31 | | |
28 | 32 | | |
29 | 33 | | |
0 commit comments