Skip to content

Fix IllegalReferenceCountException in AdaptiveByteBuf.deallocate()#16654

Merged
normanmaurer merged 2 commits into
netty:4.2from
gzsombor:adaptiv-allocator-bug
Apr 20, 2026
Merged

Fix IllegalReferenceCountException in AdaptiveByteBuf.deallocate()#16654
normanmaurer merged 2 commits into
netty:4.2from
gzsombor:adaptiv-allocator-bug

Conversation

@gzsombor

Copy link
Copy Markdown
Contributor

Motivation:

Fix IllegalReferenceCountException in AdaptiveByteBuf.deallocate() when JFR enabled when lot's of buffer is used, we noticed this error:

io.netty.util.IllegalReferenceCountException
    at AdaptiveByteBuf.rootParent(AdaptivePoolingAllocator.java:1653)
    at AdaptiveByteBuf.isDirect(AdaptivePoolingAllocator.java:1725)
    at AbstractBufferEvent.fill(AbstractBufferEvent.java:44)
    at AdaptiveByteBuf.deallocate(AdaptivePoolingAllocator.java:2110)
    at AbstractReferenceCountedByteBuf.handleRelease(AbstractReferenceCountedByteBuf.java:93)
    at AbstractReferenceCountedByteBuf.release(AbstractReferenceCountedByteBuf.java:83)
    at AdaptivePoolingAllocator$MagazineGroup.allocate(AdaptivePoolingAllocator.java:431)
    at AdaptivePoolingAllocator.allocate(AdaptivePoolingAllocator.java:271)
    at AdaptiveByteBufAllocator.newDirectBuffer(AdaptiveByteBufAllocator.java:67)

Modification:

When MagazineGroup.allocate() exhausts all magazine stripes under high
contention, the bail-out path calls buf.release() on an AdaptiveByteBuf
that was obtained from newBuffer() but never initialised via init().
The buffer's rootParent field is therefore null.

With JFR recording active, deallocate() fires a FreeBufferEvent before
clearing rootParent. AbstractBufferEvent.fill() calls buf.isDirect()
which delegates to rootParent().isDirect(), and rootParent() throws
IllegalReferenceCountException when the field is null.

Result:

JFR events are emitted even under pressure

…th JFR enabled

When MagazineGroup.allocate() exhausts all magazine stripes under high
contention, the bail-out path calls buf.release() on an AdaptiveByteBuf
that was obtained from newBuffer() but never initialised via init().
The buffer's rootParent field is therefore null.

With JFR recording active, deallocate() fires a FreeBufferEvent before
clearing rootParent. AbstractBufferEvent.fill() calls buf.isDirect()
which delegates to rootParent().isDirect(), and rootParent() throws
IllegalReferenceCountException when the field is null.

Guard the fill() call with a rootParent != null check so that the JFR
event is silently skipped for never-initialised recycled buffers.
No allocation ever occurred for such a buffer lifecycle, so there is
nothing meaningful to record. Normally allocated buffers are unaffected.
Comment thread buffer/src/main/java/io/netty/buffer/AdaptivePoolingAllocator.java Outdated
a safe default when the buffer is not in an accessible state
@chrisvest

Copy link
Copy Markdown
Member

@gzsombor Did you sign the ICLA already?

@gzsombor

Copy link
Copy Markdown
Contributor Author

Yes, yesterday I've filled that form and submitted it!

@normanmaurer normanmaurer requested a review from franz1981 April 20, 2026 08:28
@normanmaurer normanmaurer added this to the 4.2.13.Final milestone Apr 20, 2026
@normanmaurer normanmaurer added the needs-cherry-pick-5.0 This PR should be cherry-picked to 5.0 once merged. label Apr 20, 2026
@normanmaurer normanmaurer merged commit 66568d1 into netty:4.2 Apr 20, 2026
21 checks passed
netty-project-bot pushed a commit that referenced this pull request Apr 20, 2026
…16654)

Motivation:

Fix IllegalReferenceCountException in AdaptiveByteBuf.deallocate() when
JFR enabled when lot's of buffer is used, we noticed this error:

```
io.netty.util.IllegalReferenceCountException
    at AdaptiveByteBuf.rootParent(AdaptivePoolingAllocator.java:1653)
    at AdaptiveByteBuf.isDirect(AdaptivePoolingAllocator.java:1725)
    at AbstractBufferEvent.fill(AbstractBufferEvent.java:44)
    at AdaptiveByteBuf.deallocate(AdaptivePoolingAllocator.java:2110)
    at AbstractReferenceCountedByteBuf.handleRelease(AbstractReferenceCountedByteBuf.java:93)
    at AbstractReferenceCountedByteBuf.release(AbstractReferenceCountedByteBuf.java:83)
    at AdaptivePoolingAllocator$MagazineGroup.allocate(AdaptivePoolingAllocator.java:431)
    at AdaptivePoolingAllocator.allocate(AdaptivePoolingAllocator.java:271)
    at AdaptiveByteBufAllocator.newDirectBuffer(AdaptiveByteBufAllocator.java:67)

```
Modification:

When MagazineGroup.allocate() exhausts all magazine stripes under high
contention, the bail-out path calls buf.release() on an AdaptiveByteBuf
    that was obtained from newBuffer() but never initialised via init().
    The buffer's rootParent field is therefore null.

With JFR recording active, deallocate() fires a FreeBufferEvent before
    clearing rootParent. AbstractBufferEvent.fill() calls buf.isDirect()
    which delegates to rootParent().isDirect(), and rootParent() throws
    IllegalReferenceCountException when the field is null.

Result:

JFR events are emitted even under pressure

(cherry picked from commit 66568d1)
@netty-project-bot

Copy link
Copy Markdown
Contributor

Auto-port PR for 5.0: #16673

@github-actions github-actions Bot removed the needs-cherry-pick-5.0 This PR should be cherry-picked to 5.0 once merged. label Apr 20, 2026
chrisvest pushed a commit that referenced this pull request Apr 21, 2026
…deallocate() (#16673)

Auto-port of #16654 to 5.0
Cherry-picked commit: 66568d1

---


Motivation:

Fix IllegalReferenceCountException in AdaptiveByteBuf.deallocate() when
JFR enabled when lot's of buffer is used, we noticed this error:

```
io.netty.util.IllegalReferenceCountException
    at AdaptiveByteBuf.rootParent(AdaptivePoolingAllocator.java:1653)
    at AdaptiveByteBuf.isDirect(AdaptivePoolingAllocator.java:1725)
    at AbstractBufferEvent.fill(AbstractBufferEvent.java:44)
    at AdaptiveByteBuf.deallocate(AdaptivePoolingAllocator.java:2110)
    at AbstractReferenceCountedByteBuf.handleRelease(AbstractReferenceCountedByteBuf.java:93)
    at AbstractReferenceCountedByteBuf.release(AbstractReferenceCountedByteBuf.java:83)
    at AdaptivePoolingAllocator$MagazineGroup.allocate(AdaptivePoolingAllocator.java:431)
    at AdaptivePoolingAllocator.allocate(AdaptivePoolingAllocator.java:271)
    at AdaptiveByteBufAllocator.newDirectBuffer(AdaptiveByteBufAllocator.java:67)

```
Modification:

When MagazineGroup.allocate() exhausts all magazine stripes under high
contention, the bail-out path calls buf.release() on an AdaptiveByteBuf
    that was obtained from newBuffer() but never initialised via init().
    The buffer's rootParent field is therefore null.

With JFR recording active, deallocate() fires a FreeBufferEvent before
    clearing rootParent. AbstractBufferEvent.fill() calls buf.isDirect()
    which delegates to rootParent().isDirect(), and rootParent() throws
    IllegalReferenceCountException when the field is null.


Result:

JFR events are emitted even under pressure

Co-authored-by: Zsombor <gzsombor@gmail.com>
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.

5 participants