Skip to content

fix: let WebSocketClient borrow a client created without a context - #1607

Open
jlouvel wants to merge 2 commits into
fix/websocket-client-shared-event-loopfrom
fix/websocket-borrow-client-without-context
Open

jlouvel wants to merge 2 commits into
fix/websocket-client-shared-event-loopfrom
fix/websocket-borrow-client-without-context

Conversation

@jlouvel

@jlouvel jlouvel commented Oct 30, 2026

Copy link
Copy Markdown
Collaborator

Fixes #1599.

Stacked on #1568 (itself on #1566). Merge those first; this PR's base then moves to 3.0. It could also be folded into #1568.

Summary

WebSocketClient.setClient found the borrowed client's NettyClientHelper through the org.restlet.engine.helper context attribute. A client created with new Client(Protocol.HTTP) has no context, so it couldn't be borrowed, and the error didn't say what to change.

  • Client.getHelper() is now public, documented as an engine extension point for extensions that share the connector's resources. It was private, and it doesn't depend on a context.
  • WebSocketClient uses client.getHelper(). The context requirement is removed from the setClient Javadoc and from the error message, which now reads "The borrowed client must use the Netty HTTP connector".

Server keeps its helper private. It could get the same accessor later if an extension needs it.

Testing

  • New WebSocketSharedEventLoopTestCase.acceptsAClientWithoutContext: a started new Client(Protocol.HTTP) is borrowed and serves a session. The existing tests still reject unstarted clients and clients without a Netty HTTP connector.
  • org.restlet.ext.websocket tests pass locally: 33 tests. SpotBugs and the full core suite couldn't run locally because the machine was short of memory; CI runs them.

🤖 Generated with Claude Code

jlouvel and others added 2 commits October 29, 2026 21:10
WebSocketClient created its own Netty event loop group in every
instance, next to the one of the core HTTP client connector, with a
thread count that could not be configured.

- Add NettyClientHelper.getEventLoopGroup(), an engine extension point
  exposing the client connector's event loop group.
- Add WebSocketClient.setClient(Client): a borrowed, started Netty HTTP
  client whose event loop is shared. Without it, the client still
  creates its own threads.
- Track the WebSocket connections in a channel group, so stopping the
  client closes its sessions without shutting down a shared event loop.
- Store the helper in the "org.restlet.engine.helper" context attribute
  in Client.setContext(), as the constructors do. Clients added with
  Component.getClients().add(Protocol) get their context this way, and
  did not expose their helper.

Fixes #1567

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Jerome Louvel <374450+jlouvel@users.noreply.github.com>
WebSocketClient.setClient found the borrowed client's NettyClientHelper
through the "org.restlet.engine.helper" context attribute, so a client
created with new Client(Protocol.HTTP), which has no context, couldn't
be borrowed.

Client.getHelper() is now public, as an engine extension point, and the
WebSocket client uses it. The context requirement is removed from the
setClient Javadoc and the error message.

Fixes #1599.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Jerome Louvel <374450+jlouvel@users.noreply.github.com>
@sonarqubecloud

Copy link
Copy Markdown

@jlouvel
jlouvel force-pushed the fix/websocket-client-shared-event-loop branch from c24fe96 to eec9acc Compare October 31, 2026 07:17
@jlouvel jlouvel self-assigned this Oct 31, 2026
@jlouvel
jlouvel requested a balanced review from Copilot October 31, 2026 07:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

A critical connect/stop race allows a late-registered channel to remain open on a borrowed event loop, leaving a WebSocket session active after the client stops.

1 open issue
What changed in this PR

This PR lets WebSocketClient borrow a started Netty HTTP client without requiring a context, addressing #1599. It builds on the shared-event-loop support from #1568.

Changes:

  • Exposes Client.getHelper() and uses it to access the borrowed client's connector directly.
  • Adds shared event-loop ownership and WebSocket session cleanup.
  • Adds lifecycle and context-free borrowing tests, plus changelog entries.
File Description
org.restlet/​src/​main/​java/​org/​restlet/​engine/​netty/​NettyClientHelper.java Exposes the connector's event loop for extensions.
org.restlet/​src/​main/​java/​org/​restlet/​Client.java Makes helper access public and updates context registration.
org.restlet.ext.websocket/​src/​test/​java/​org/​restlet/​ext/​websocket/​WebSocketSharedEventLoopTestCase.java Tests borrowing, lifecycle behavior, and unsuitable clients.
org.restlet.ext.websocket/​src/​main/​java/​org/​restlet/​ext/​websocket/​WebSocketClient.java Supports borrowing through direct helper access.
org.restlet.ext.websocket/​src/​main/​java/​org/​restlet/​ext/​websocket/​internal/​NettyWebSocketConnector.java Tracks connections and distinguishes owned from borrowed event loops.
changes.md Documents borrowing support and core extension points.

Review effort: Balanced


/** The open connections, closed with the connector. */
private final ChannelGroup channels =
new DefaultChannelGroup("restlet-websocket-client", GlobalEventExecutor.INSTANCE);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants