Skip to content

[grid] Fix latent bugs in WebSocket proxy#17429

Open
shs96c wants to merge 5 commits intoSeleniumHQ:trunkfrom
shs96c:grid-websocket-proxy-bugs
Open

[grid] Fix latent bugs in WebSocket proxy#17429
shs96c wants to merge 5 commits intoSeleniumHQ:trunkfrom
shs96c:grid-websocket-proxy-bugs

Conversation

@shs96c
Copy link
Copy Markdown
Member

@shs96c shs96c commented May 8, 2026

Summary

Five small, independent bug fixes in the Grid WebSocket proxy code, each on its own commit with a focused unit test. No behaviour change for callers; each commit explains the failure mode it closes.

  • Forward orphan WebSocket frames inbound, not outbound (MessageInboundConverter). Orphan continuation frames and unknown-type frames went out via ctx.write instead of forward through the pipeline via ctx.fireChannelRead — a malformed peer would be echoed instead of dropped.
  • Propagate ChannelPromise through MessageOutboundConverter. The converter called ctx.writeAndFlush(frame) and dropped the supplied promise, so any caller awaiting the original future would never see it complete.
  • Size BinaryMessage(ByteBuffer) by readable bytes, not buffer capacity. The constructor sized its array by capacity(); works only because Netty's nioBuffer() happens to be slice()-backed. A flipped buffer over a larger backing array would either pad zeros onto the message or hit BufferUnderflowException.
  • Latch upstreamClosing on WebSocketFrameProxy send failure. A failing forward fired the exception through the pipeline but left the flag unset, so any frames already queued behind it re-attempted the same failing send before the close handshake ran.
  • Release WebSocket consumer when WS handshake fails (WebSocketUpgradeHandler). The factory passed in may have already opened an upstream WebSocket and acquired a connection slot before the WS handshake itself fails (unsupported Sec-WebSocket-Version, or handshake-future failure). The previous code dropped the produced consumer without invoking it, leaking both the upstream and the slot. Both failure paths now drive the consumer through its CloseMessage cleanup so the existing consumer logic frees the resources.

Test plan

  • `bazel test --config=rbe //java/test/org/openqa/selenium/netty/server/...`
  • `bazel test --config=rbe //java/test/org/openqa/selenium/remote/http:BinaryMessageTest`
  • `bazel test --config=rbe //java/test/org/openqa/selenium/grid/router:ProxyWebsocketTest` (existing end-to-end coverage)

🤖 Generated with Claude Code

shs96c added 5 commits May 7, 2026 16:48
MessageInboundConverter handled orphan continuation frames and unknown
frame types by calling ctx.write(frame), which sends the frame back out
to the peer. They should travel forward through the inbound pipeline so
the upgrade/keepalive handlers can deal with them.
The converter was calling ctx.writeAndFlush(frame) and dropping the
incoming ChannelPromise, so any caller awaiting the original future
would never see it complete. Pipe the promise through ctx.write(...,
promise) and rely on the upstream writeAndFlush() to trigger the flush.
BinaryMessage(ByteBuffer) sized its array by capacity(), which works
only because Netty's ByteBuf.nioBuffer() happens to return a slice
where capacity == remaining. A caller passing a flipped ByteBuffer
backed by a larger array would either pad zero bytes onto the message
or hit a BufferUnderflowException. Use remaining() so the contract
matches the comment "data to use".
A failing forward currently fires the exception through the pipeline
but leaves upstreamClosing unset, so any frames already queued behind
this one re-attempt the same failing send before the close handshake
runs. Set the flag on first failure so subsequent frames short-circuit
to the drop path immediately.
The factory passed to WebSocketUpgradeHandler may have already opened
an upstream WebSocket and acquired a connection slot by the time the
WS handshake itself fails (unsupported Sec-WebSocket-Version, or the
handshake future completing exceptionally). The previous code dropped
the produced consumer without invoking it, so the upstream and the
slot leaked. Drive each failure path through the consumer's CloseMessage
cleanup so existing consumer logic frees the resources.
@selenium-ci selenium-ci added C-java Java Bindings B-build Includes scripting, bazel and CI integrations labels May 8, 2026
@qodo-code-review
Copy link
Copy Markdown
Contributor

Review Summary by Qodo

Fix five latent bugs in Grid WebSocket proxy implementation

🐞 Bug fix 🧪 Tests

Grey Divider

Walkthroughs

Description
• Forward orphan WebSocket frames inbound through pipeline, not outbound to peer
• Propagate ChannelPromise through MessageOutboundConverter to complete futures
• Size BinaryMessage by readable bytes instead of buffer capacity
• Latch upstreamClosing flag on WebSocketFrameProxy send failure
• Release WebSocket consumer on handshake failure to prevent resource leaks
Diagram
flowchart LR
  A["Orphan WebSocket Frames"] -->|fireChannelRead| B["Forward Inbound"]
  C["ChannelPromise"] -->|ctx.write| D["MessageOutboundConverter"]
  E["ByteBuffer"] -->|remaining| F["BinaryMessage Sizing"]
  G["Send Failure"] -->|set flag| H["upstreamClosing Latch"]
  I["Handshake Failure"] -->|CloseMessage| J["Consumer Cleanup"]
Loading

Grey Divider

File Changes

1. java/src/org/openqa/selenium/netty/server/MessageInboundConverter.java 🐞 Bug fix +2/-2

Forward orphan frames inbound, not outbound

java/src/org/openqa/selenium/netty/server/MessageInboundConverter.java


2. java/src/org/openqa/selenium/netty/server/MessageOutboundConverter.java 🐞 Bug fix +6/-5

Propagate ChannelPromise through converter

java/src/org/openqa/selenium/netty/server/MessageOutboundConverter.java


3. java/src/org/openqa/selenium/netty/server/WebSocketFrameProxy.java 🐞 Bug fix +3/-0

Latch upstreamClosing on send failure

java/src/org/openqa/selenium/netty/server/WebSocketFrameProxy.java


View more (8)
4. java/src/org/openqa/selenium/netty/server/WebSocketUpgradeHandler.java 🐞 Bug fix +18/-0

Release consumer on handshake failure

java/src/org/openqa/selenium/netty/server/WebSocketUpgradeHandler.java


5. java/src/org/openqa/selenium/remote/http/BinaryMessage.java 🐞 Bug fix +1/-1

Size message by readable bytes not capacity

java/src/org/openqa/selenium/remote/http/BinaryMessage.java


6. java/test/org/openqa/selenium/netty/server/MessageInboundConverterTest.java 🧪 Tests +64/-0

Test orphan frame forwarding behavior

java/test/org/openqa/selenium/netty/server/MessageInboundConverterTest.java


7. java/test/org/openqa/selenium/netty/server/MessageOutboundConverterTest.java 🧪 Tests +47/-0

Test promise completion on message write

java/test/org/openqa/selenium/netty/server/MessageOutboundConverterTest.java


8. java/test/org/openqa/selenium/netty/server/WebSocketFrameProxyTest.java 🧪 Tests +57/-0

Test upstreamClosing flag on failure

java/test/org/openqa/selenium/netty/server/WebSocketFrameProxyTest.java


9. java/test/org/openqa/selenium/netty/server/WebSocketUpgradeHandlerTest.java 🧪 Tests +63/-0

Test consumer cleanup on unsupported version

java/test/org/openqa/selenium/netty/server/WebSocketUpgradeHandlerTest.java


10. java/test/org/openqa/selenium/remote/http/BinaryMessageTest.java 🧪 Tests +38/-0

Test readable region buffer sizing

java/test/org/openqa/selenium/remote/http/BinaryMessageTest.java


11. java/test/org/openqa/selenium/netty/server/BUILD.bazel ⚙️ Configuration changes +7/-0

Add new test targets and dependencies

java/test/org/openqa/selenium/netty/server/BUILD.bazel


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown
Contributor

qodo-code-review Bot commented May 8, 2026

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider

Great, no issues found!

Qodo reviewed your code and found no material issues that require review

Grey Divider

Qodo Logo

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

B-build Includes scripting, bazel and CI integrations C-java Java Bindings

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants