Skip to content

Conversation

@j-tyler
Copy link
Contributor

@j-tyler j-tyler commented Nov 24, 2025

Summary

NettyResponseChannel.write(ByteBuffer) was leaking memory by not releasing
the internal bytebuf wrapper it creates. According to Netty's reference
counting contract, whoever creates or retains a ByteBuf is responsible for
releasing it.

The issue:

  • write(ByteBuffer) wraps the ByteBuffer in a ByteBuf internally
  • This wrapper was never released by NettyResponseChannel
  • Caller has no reference to this wrapper cannot release it themselves

The fix:

  • Release the wrapper bytebuf in the callback internally created
  • This ensures cleanup happens regardless of success or exception

Testing Done

Added a new test class that specifically tests the memory management in NettyResponseChannel
proving both the write(ByteBuffer) and write(ByteBuf) paths manage memory as expected.

NettyResponseChannel.write(ByteBuffer) was leaking memory by not releasing
the internal bytebuf wrapper it creates. According to Netty's reference
counting contract, whoever creates or retains a ByteBuf is responsible for
releasing it.

The issue:
- write(ByteBuffer) wraps the ByteBuffer in a ByteBuf internally
- This wrapper was never released by NettyResponseChannel
- Caller has no reference to this wrapper cannot release it themselves

The fix:
- Release the wrapper bytebuf in the callback internally created
- This ensures cleanup happens regardless of success or exception
@j-tyler j-tyler marked this pull request as ready for review November 24, 2025 17:37
@codecov-commenter
Copy link

codecov-commenter commented Nov 24, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 69.87%. Comparing base (52ba813) to head (0d37b5c).
⚠️ Report is 330 commits behind head on master.

Additional details and impacted files
@@             Coverage Diff              @@
##             master    #3183      +/-   ##
============================================
+ Coverage     64.24%   69.87%   +5.63%     
- Complexity    10398    12802    +2404     
============================================
  Files           840      930      +90     
  Lines         71755    78996    +7241     
  Branches       8611     9431     +820     
============================================
+ Hits          46099    55200    +9101     
+ Misses        23004    20876    -2128     
- Partials       2652     2920     +268     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@Override
public void onCompletion(Long result, Exception exception) {
// This internal wrapped bytebuffer is owned by this class and must be released in the callback.
ByteBuf wrapper = Unpooled.wrappedBuffer(src);
Copy link
Collaborator

Choose a reason for hiding this comment

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

I remember a unpooled wrapped buffered doesn't need to be released. Even if we don't want release it, it's not going to leak any memory.

I suppose we can call release on it, but I don't think it's going to make any difference.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's not the wrapper that is a problem it's the wrapper's reference to the underlying Bytebuffer. If the ByteBuffer is pooled direct memory it will not become available for re-use until the ByteBuf wrapper here is GCed. If we correctly release it, it will become available immediately. If the time between GC cycles is long, the pool can easily exhaust.

@justinlin-linkedin justinlin-linkedin merged commit a5b6a4e into linkedin:master Nov 25, 2025
5 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.

3 participants