Skip to content

JAVA-6250: Do not retain the connection-open handler from the channel close listener.#2018

Merged
rozza merged 4 commits into
mongodb:mainfrom
rozza:JAVA-6250
Jul 21, 2026
Merged

JAVA-6250: Do not retain the connection-open handler from the channel close listener.#2018
rozza merged 4 commits into
mongodb:mainfrom
rozza:JAVA-6250

Conversation

@rozza

@rozza rozza commented Jul 20, 2026

Copy link
Copy Markdown
Member

The listener registered on the channel closeFuture when a pooled
connection opens captured the enclosing OpenChannelFutureListener
instance. This kept the open handler, and the whole operation graph
reachable from it (callbacks and their buffers), strongly referenced
for as long as the pooled channel stayed alive. Capture the enclosing
NettyStream in a local variable instead, so the operation graph
becomes collectable as soon as the connection opening completes.

JAVA-6250

m-k8s and others added 3 commits July 11, 2026 19:57
…close listener.

The listener registered on the channel closeFuture when a pooled
connection opens captured the enclosing OpenChannelFutureListener
instance. This kept the open handler, and the whole operation graph
reachable from it (callbacks and their buffers), strongly referenced
for as long as the pooled channel stayed alive. Capture the enclosing
NettyStream in a local variable instead, so the operation graph
becomes collectable as soon as the connection opening completes.

JAVA-6250
The close listener now captures only the NettyStream, so the one-shot
connection-open handler can no longer be pinned for the channel lifetime.
Replaces the local-variable capture idiom with a structural guarantee.

JAVA-6250
Explicit NettyStream dependency instead of the implicit enclosing instance,
removing the this$0 capture footgun that caused JAVA-6250. No behavioral change.

JAVA-6250
@rozza
rozza requested a review from a team as a code owner July 20, 2026 10:13
@rozza
rozza requested a review from vbabanin July 20, 2026 10:13
@rozza

rozza commented Jul 20, 2026

Copy link
Copy Markdown
Member Author

Includes the excellent #2013 by @m-k8s and adds some suggested improvements; using statics to help ensure no regressions in the future and slight clarifications to the tests for future readability.

@rozza
rozza requested review from Copilot and nhachicha and removed request for vbabanin July 20, 2026 10:16

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.

Pull request overview

This PR addresses a memory-retention issue in driver-core’s Netty-based stream implementation where a channel.closeFuture() listener could keep the connection-open handler (and its transitive callback graph) strongly reachable for the lifetime of a pooled channel. The fix ensures the close listener only retains the NettyStream, allowing the open handler graph to be garbage-collected once open completes.

Changes:

  • Refactors NettyStream channel-open completion handling to avoid retaining the open handler via a close-future listener (introduces a dedicated CloseChannelFutureListener).
  • Makes OpenChannelFutureListener a static nested class and explicitly passes NettyStream to prevent implicit outer captures.
  • Adds a unit test covering both the GC/reachability aspect and the “pending read fails on channel close” behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
driver-core/src/main/com/mongodb/internal/connection/netty/NettyStream.java Avoids retaining the open handler by ensuring the channel close-future listener captures only NettyStream.
driver-core/src/test/unit/com/mongodb/internal/connection/netty/NettyStreamCloseFutureListenerTest.java Adds regression tests for handler reachability after open and for failing pending reads on channel close.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Extract the GC reachability probe into a named assertRefUnreachable helper
that documents the WeakReference-as-reachability-probe idiom, extract the
accept plumbing into openAndAcceptConnection, and add explanatory comments
tying the footprint assertion back to JAVA-6250. Test-only, no behaviour change.

JAVA-6250

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.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Comment on lines +155 to +162
@SuppressWarnings("BusyWait")
private static void assertRefUnreachable(final WeakReference<?> ref, final String message) throws InterruptedException {
long deadlineNanos = System.nanoTime() + TimeUnit.SECONDS.toNanos(10);
while (ref.get() != null && System.nanoTime() < deadlineNanos) {
System.gc();
Thread.sleep(100);
}
assertNull(ref.get(), message);

@nhachicha nhachicha left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM 👍

@BeforeEach
public void setUp() throws Exception {
serverSocket = new ServerSocket(0, 1, InetAddress.getLoopbackAddress());
eventLoopGroup = new NioEventLoopGroup();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

NioEventLoopGroup is deprecated, you can use new MultiThreadIoEventLoopGroup(NioIoHandler.newFactory())

private static void assertRefUnreachable(final WeakReference<?> ref, final String message) throws InterruptedException {
long deadlineNanos = System.nanoTime() + TimeUnit.SECONDS.toNanos(10);
while (ref.get() != null && System.nanoTime() < deadlineNanos) {
System.gc();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think this is only honoured in HotSpsot, it's not guarantee to trigger the GC...

@rozza
rozza merged commit cab37d0 into mongodb:main Jul 21, 2026
53 checks passed
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.

4 participants