Skip to content

fix #152 rnf count#154

Merged
NeatGuyCoding merged 3 commits intomainfrom
fix-ssl-buffer-cnt
Mar 23, 2026
Merged

fix #152 rnf count#154
NeatGuyCoding merged 3 commits intomainfrom
fix-ssl-buffer-cnt

Conversation

@NeatGuyCoding
Copy link
Collaborator

@NeatGuyCoding NeatGuyCoding commented Mar 18, 2026

Description

Brief description of the changes in this PR.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Code refactoring
  • Test improvements
  • Build/tooling changes

Related Issue

Closes #152

Changes Made

Testing

  • All existing tests pass
  • New tests added for new functionality
  • Tests pass locally with mvn test
  • Integration tests pass (if applicable)

Checklist

  • Code follows project coding standards
  • Self-review completed
  • Code is commented where necessary
  • Documentation updated (if needed)
  • Commit messages follow conventional format
  • No merge conflicts
  • All CI checks pass

Additional Notes

Any additional information, screenshots, or context that reviewers should know.

Summary by CodeRabbit

  • Bug Fixes
    • Improved stability and memory safety in message processing by guarding access to buffer content.
    • Ensured downstream handlers receive valid message content by fixing buffer retention and release handling.
    • Simplified and hardened JSON preprocessing to avoid premature releases and reduce decoding errors.
    • Improved error logging to avoid referencing released buffer content.

Signed-off-by: NeatGuyCoding <15627489+NeatGuyCoding@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 18, 2026 12:01
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 18, 2026

📝 Walkthrough

Walkthrough

Changes add explicit reference-count handling and safer buffer usage: PacketsMessage becomes ReferenceCounted; transports (WebSocketTransport, PollingTransport) retain frame/content before forwarding; PacketDecoder.preprocessJson mutates the incoming ByteBuf in-place and expects caller retention; InPacketHandler guards logging on buffer refCnt().

Changes

Cohort / File(s) Summary
Reference-counted message
netty-socketio-core/src/main/java/com/socketio4j/socketio/messages/PacketsMessage.java
Now implements ReferenceCounted; delegates refCnt(), retain(), retain(int), touch(), touch(Object), release(), release(int) to the underlying ByteBuf.
Transport retention
netty-socketio-core/src/main/java/com/socketio4j/socketio/transport/WebSocketTransport.java, netty-socketio-core/src/main/java/com/socketio4j/socketio/transport/PollingTransport.java
Transports call content.retain() (or frame.content().retain()) before constructing/forwarding PacketsMessage; WebSocketTransport wraps forwarding in try/finally to ensure frame.release() runs after fireChannelRead.
Decoder in-place preprocessing
netty-socketio-core/src/main/java/com/socketio4j/socketio/protocol/PacketDecoder.java
preprocessJson now mutates the provided ByteBuf in-place (no intermediate copy/release); URL-decoding and newline replacements are applied directly; buffer retention is expected from the caller.
Defensive logging
netty-socketio-core/src/main/java/com/socketio4j/socketio/handler/InPacketHandler.java
Exception logging now checks content.refCnt() and logs content.toString(CharsetUtil.UTF_8) only when refCnt() > 0, otherwise logs "<released>" to avoid accessing released buffers.

Sequence Diagram(s)

sequenceDiagram
    rect rgba(200,230,255,0.5)
    participant Client
    end
    rect rgba(200,255,200,0.5)
    participant Transport
    end
    rect rgba(255,230,200,0.5)
    participant PacketsMessage
    end
    rect rgba(255,200,200,0.5)
    participant PacketDecoder
    end
    rect rgba(230,200,255,0.5)
    participant InPacketHandler
    end

    Client->>Transport: WebSocket/Binary or Polling POST (ByteBuf)
    Transport->>Transport: content.retain()
    Transport->>PacketsMessage: new PacketsMessage(content)
    Transport->>Pipeline: fireChannelRead(PacketsMessage)
    PacketsMessage->>PacketDecoder: preprocessJson(content) (in-place)
    PacketDecoder->>PacketsMessage: returns same ByteBuf
    PacketsMessage->>InPacketHandler: delivered for handling
    InPacketHandler->>InPacketHandler: checks content.refCnt() before logging
    InPacketHandler->>Pipeline: continue processing / release when appropriate
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested labels

bug

Poem

🐇 I counted refs beneath the moonlit patch,
Held bytes with care, avoided a scratch,
Retain and pass, then gently release,
No more ref errors to break our peace.
Hopping onward — pipeline neat and spry! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is largely a template with empty 'Changes Made' section and all checklists unchecked; only 'Closes #152' is filled in, lacking substantive implementation details. Complete the description with actual changes made, specific files modified, testing confirmation, and mark appropriate checkboxes (bug fix type) to document the reference counting fix.
Docstring Coverage ⚠️ Warning Docstring coverage is 8.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'fix #152 rnf count' is vague and uses cryptic abbreviation 'rnf' that doesn't clearly convey the actual change (reference count handling in ByteBuf management). Replace 'rnf count' with a more descriptive term like 'reference count' or 'buffer reference counting' to clarify the specific issue being fixed.
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The code changes address the IllegalReferenceCountException by implementing proper ByteBuf reference counting across WebSocket and polling transports, fixing the core issue of buffers accessed after release.
Out of Scope Changes check ✅ Passed All changes are scoped to fixing reference counting issues: PacketsMessage implements ReferenceCounted, PacketDecoder.preprocessJson operates in-place, WebSocketTransport and PollingTransport retain buffers before downstream usage.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-ssl-buffer-cnt
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes Netty IllegalReferenceCountException (issue #152) by correcting ByteBuf ownership / reference-count handling as websocket (and related) payloads are forwarded through the Netty pipeline.

Changes:

  • Retain WebSocketFrame content before forwarding it downstream, then release the frame.
  • Make PacketsMessage implement ReferenceCounted so Netty handlers can manage buffer lifetimes via auto-release.
  • Change PacketDecoder.preprocessJson to mutate the provided ByteBuf in-place (avoiding derived-buffer refCnt pitfalls).

Reviewed changes

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

File Description
netty-socketio-core/src/main/java/com/socketio4j/socketio/transport/WebSocketTransport.java Retains frame content before firing PacketsMessage downstream.
netty-socketio-core/src/main/java/com/socketio4j/socketio/protocol/PacketDecoder.java Switches JSON preprocessing to in-place mutation to avoid slice/refCnt issues.
netty-socketio-core/src/main/java/com/socketio4j/socketio/messages/PacketsMessage.java Implements ReferenceCounted to participate in Netty’s ref-count lifecycle management.
netty-socketio-core/src/main/java/com/socketio4j/socketio/handler/InPacketHandler.java Avoids toString() on already-released buffers in error logging.
Comments suppressed due to low confidence (1)

netty-socketio-core/src/main/java/com/socketio4j/socketio/messages/PacketsMessage.java:35

  • PacketsMessage now implements ReferenceCounted, which means SimpleChannelInboundHandler (e.g., InPacketHandler) will auto-release it, cascading to content.release(). Call sites that also release the original owner (e.g., FullHttpRequest.release() in PollingTransport) will end up double-releasing the same ByteBuf unless they retain() before wrapping it. Please update the producers (notably the polling path) to retain() the ByteBuf when creating PacketsMessage, or disable autoRelease / change ownership rules so each buffer is released exactly once.
public class PacketsMessage implements ReferenceCounted {

    private final ClientHead client;
    private final ByteBuf content;
    private final Transport transport;

    public PacketsMessage(ClientHead client, ByteBuf content, Transport transport) {
        this.client = client;
        this.content = content;
        this.transport = transport;
    }

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
netty-socketio-core/src/main/java/com/socketio4j/socketio/messages/PacketsMessage.java (1)

25-86: ⚠️ Potential issue | 🟠 Major

Polling transport double-releases reference-counted content.

PollingTransport.onPost() wraps req.content() directly in PacketsMessage and fires it downstream (line 172), then the finally block calls req.release() (line 129). Since preprocessJson() mutates in-place without creating a new buffer, the same buffer is passed to InPacketHandler, which extends SimpleChannelInboundHandler<PacketsMessage>. Netty's SimpleChannelInboundHandler automatically calls release() after processing, causing a double-release:

  1. req.release() decrements refCnt (finally block, line 129)
  2. SimpleChannelInboundHandler.release() calls PacketsMessage.release()content.release() again

The WebSocketTransport path correctly calls .retain() before wrapping (line 98), leaving one reference for the downstream auto-release. Both paths must follow the same pattern: either all producers retain before wrapping, or move the retain into the PacketsMessage constructor and remove upstream retains.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@netty-socketio-core/src/main/java/com/socketio4j/socketio/messages/PacketsMessage.java`
around lines 25 - 86, PacketsMessage currently wraps a ByteBuf without taking
ownership, causing double-release when upstream also releases; change ownership
consistently by having the PacketsMessage constructor call content.retain() (and
keep the existing release/refCnt/retain/touch methods unchanged) so any producer
(e.g., PollingTransport.onPost) can safely release its original req and
downstream InPacketHandler / SimpleChannelInboundHandler will release the
PacketsMessage-held buffer exactly once; update any code paths that previously
called retain (WebSocketTransport) to remove redundant retains if present so the
retain semantics are centralized in the PacketsMessage constructor and not
duplicated upstream.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@netty-socketio-core/src/main/java/com/socketio4j/socketio/protocol/PacketDecoder.java`:
- Around line 71-82: In PacketDecoder (inside the block where jsonIndex != null
and after calling replaceEscapedNewlinesInPlace), explicitly validate that the
JSONP payload is at least 2 bytes long before attempting to check the 'd='
prefix; if content.readableBytes() < 2, throw an IllegalArgumentException with a
clear message (e.g., "Invalid JSONP format: payload too short for 'd=' prefix")
so short/malformed payloads fail fast instead of bypassing the prefix check;
preserve the existing logic that checks content.getByte(ri) and
content.getByte(ri + 1) and advances content.readerIndex(ri + 2) when valid.

---

Outside diff comments:
In
`@netty-socketio-core/src/main/java/com/socketio4j/socketio/messages/PacketsMessage.java`:
- Around line 25-86: PacketsMessage currently wraps a ByteBuf without taking
ownership, causing double-release when upstream also releases; change ownership
consistently by having the PacketsMessage constructor call content.retain() (and
keep the existing release/refCnt/retain/touch methods unchanged) so any producer
(e.g., PollingTransport.onPost) can safely release its original req and
downstream InPacketHandler / SimpleChannelInboundHandler will release the
PacketsMessage-held buffer exactly once; update any code paths that previously
called retain (WebSocketTransport) to remove redundant retains if present so the
retain semantics are centralized in the PacketsMessage constructor and not
duplicated upstream.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 71f0cac8-3b06-423d-a320-c3ecde509d09

📥 Commits

Reviewing files that changed from the base of the PR and between 92b3982 and 58a7753.

📒 Files selected for processing (4)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/handler/InPacketHandler.java
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/messages/PacketsMessage.java
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/protocol/PacketDecoder.java
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/transport/WebSocketTransport.java

@sanjomo
Copy link
Member

sanjomo commented Mar 18, 2026

@NeatGuyCoding we need this in PollingTransport also in SSL right??

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 19, 2026 01:11
@NeatGuyCoding
Copy link
Collaborator Author

@sanjomo Seems not, it does not same to Websocket

And there are some tests which you previously added always failed, can you check them please?

Copy link
Contributor

Copilot AI left a comment

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 targets issue #152 by adjusting Netty ByteBuf reference-counting/ownership in the inbound message path to prevent IllegalReferenceCountException when using WSS/TLS and newer Netty versions.

Changes:

  • Retain WebSocketFrame content before forwarding it as PacketsMessage, then release the frame safely.
  • Make PacketsMessage implement ReferenceCounted so it can participate in Netty auto-release semantics.
  • Simplify PacketDecoder.preprocessJson to mutate the provided ByteBuf in-place (avoiding derived buffers) and harden error logging against released buffers.

Reviewed changes

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

File Description
netty-socketio-core/src/main/java/com/socketio4j/socketio/transport/WebSocketTransport.java Retains WebSocket frame content before re-firing to the pipeline, releases frame in finally.
netty-socketio-core/src/main/java/com/socketio4j/socketio/protocol/PacketDecoder.java preprocessJson now mutates the input buffer in-place and returns it (no slice/release management).
netty-socketio-core/src/main/java/com/socketio4j/socketio/messages/PacketsMessage.java Implements ReferenceCounted and delegates ref-count ops to underlying ByteBuf.
netty-socketio-core/src/main/java/com/socketio4j/socketio/handler/InPacketHandler.java Avoids calling toString() on a released buffer in the exception path.
Comments suppressed due to low confidence (1)

netty-socketio-core/src/main/java/com/socketio4j/socketio/messages/PacketsMessage.java:33

  • PacketsMessage now implements ReferenceCounted and delegates release() to content.release(). Since InPacketHandler is a SimpleChannelInboundHandler with auto-release enabled, every inbound PacketsMessage will now release its ByteBuf automatically. However, PollingTransport currently creates new PacketsMessage(client, req.content(), ...) and then unconditionally req.release() in its channelRead finally block (see PollingTransport.java:103-116 + onPost(...)). That results in the same ByteBuf being released twice (once via PacketsMessage auto-release, once via FullHttpRequest.release()), which can trigger IllegalReferenceCountException under load.

To fix, ensure every PacketsMessage creator transfers ownership consistently (e.g., call retain() on the ByteBuf when constructing PacketsMessage if the original parent message will be released later), and/or adjust the request/frame release strategy so the content is only released once.

public class PacketsMessage implements ReferenceCounted {

    private final ClientHead client;
    private final ByteBuf content;
    private final Transport transport;

    public PacketsMessage(ClientHead client, ByteBuf content, Transport transport) {
        this.client = client;
        this.content = content;

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

Comment on lines +96 to +103
// WebSocketFrame is reference-counted and will be released below.
// Retain its content since we pass it further down the pipeline.
PacketsMessage packetsMessage = new PacketsMessage(client, frame.content().retain(), Transport.WEBSOCKET);
try {
ctx.pipeline().fireChannelRead(packetsMessage);
} finally {
frame.release();
}
@sanjomo
Copy link
Member

sanjomo commented Mar 19, 2026

@sanjomo Seems not, it does not same to Websocket

And there are some tests which you previously added always failed, can you check them please?

@NeatGuyCoding
I see build passed in all three java versions, please tell me which tests you are referring to, so that I can have a look..

and in Polling also same problem (I replicated in my local)

13:37:19.239 [multiThreadIoEventLoopGroup-3-16] ERROR com.socketio4j.socketio.listener.DefaultExceptionListener -- refCnt: 0
io.netty.util.IllegalReferenceCountException: refCnt: 0
        at io.netty.buffer.AbstractByteBuf.ensureAccessible(AbstractByteBuf.java:1480)
        at io.netty.buffer.AbstractByteBuf.checkIndex(AbstractByteBuf.java:1390)
        at io.netty.buffer.AbstractByteBuf.checkDstIndex(AbstractByteBuf.java:1435)
        at io.netty.buffer.CompositeByteBuf.getBytes(CompositeByteBuf.java:1062)
        at io.netty.buffer.CompositeByteBuf.getBytes(CompositeByteBuf.java:49)
        at io.netty.buffer.ByteBufUtil.decodeString(ByteBufUtil.java:1364)
        at io.netty.buffer.AbstractByteBuf.toString(AbstractByteBuf.java:1253)
        at io.netty.buffer.AbstractByteBuf.toString(AbstractByteBuf.java:1248)
        at com.socketio4j.socketio.handler.InPacketHandler.channelRead0(InPacketHandler.java:131)
        at com.socketio4j.socketio.handler.InPacketHandler.channelRead0(InPacketHandler.java:40)

@sanjomo
Copy link
Member

sanjomo commented Mar 19, 2026

@NeatGuyCoding full logs here

13:52:45.981 [multiThreadIoEventLoopGroup-3-3] DEBUG com.socketio4j.socketio.handler.AuthorizeHandler -- Processing HTTP request: /socket.io/?EIO=4&transport=polling&t=Pq5C9-w&sid=a9aaa8c6-24e1-42e7-8746-9e666eae0b66 from client: /127.0.0.1:54614 13:52:45.981 [multiThreadIoEventLoopGroup-3-3] DEBUG com.socketio4j.socketio.handler.AuthorizeHandler -- Processing existing session request: [a9aaa8c6-24e1-42e7-8746-9e666eae0b66] from client: /127.0.0.1:54614 13:52:45.981 [multiThreadIoEventLoopGroup-3-3] DEBUG com.socketio4j.socketio.handler.EncoderHandler -- Processing message type: XHRPostMessage, sessionId: a9aaa8c6-24e1-42e7-8746-9e666eae0b66 13:52:45.981 [multiThreadIoEventLoopGroup-3-3] DEBUG com.socketio4j.socketio.handler.EncoderHandler -- Processing XHR POST message, sessionId: a9aaa8c6-24e1-42e7-8746-9e666eae0b66 13:52:45.983 [multiThreadIoEventLoopGroup-3-3] ERROR com.socketio4j.socketio.handler.InPacketHandler -- Error during data processing. Client sessionId: a9aaa8c6-24e1-42e7-8746-9e666eae0b66, data: <released> io.netty.util.IllegalReferenceCountException: refCnt: 0 at io.netty.buffer.AbstractByteBuf.ensureAccessible(AbstractByteBuf.java:1480) at io.netty.buffer.AbstractByteBuf.checkIndex(AbstractByteBuf.java:1390) at io.netty.buffer.AbstractByteBuf.checkIndex(AbstractByteBuf.java:1386) at io.netty.buffer.CompositeByteBuf.findComponent(CompositeByteBuf.java:1623) at io.netty.buffer.CompositeByteBuf.getByte(CompositeByteBuf.java:954) at com.socketio4j.socketio.protocol.PacketDecoder.isStringPacket(PacketDecoder.java:54) at com.socketio4j.socketio.protocol.PacketDecoder.decodePackets(PacketDecoder.java:232) at com.socketio4j.socketio.handler.InPacketHandler.channelRead0(InPacketHandler.java:71) at com.socketio4j.socketio.handler.InPacketHandler.channelRead0(InPacketHandler.java:40) at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:99) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:356) at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:107) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:356) at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:331) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:356) at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:331) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:356) at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1429) at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:918) at io.netty.channel.kqueue.AbstractKQueueStreamChannel$KQueueStreamUnsafe.readReady(AbstractKQueueStreamChannel.java:544) at io.netty.channel.kqueue.AbstractKQueueChannel$AbstractKQueueUnsafe.handle(AbstractKQueueChannel.java:406) at io.netty.channel.kqueue.KQueueIoHandler$DefaultKqueueIoRegistration.handle(KQueueIoHandler.java:472) at io.netty.channel.kqueue.KQueueIoHandler.processReady(KQueueIoHandler.java:214) at io.netty.channel.kqueue.KQueueIoHandler.run(KQueueIoHandler.java:276) at io.netty.channel.SingleThreadIoEventLoop.runIo(SingleThreadIoEventLoop.java:225) at io.netty.channel.SingleThreadIoEventLoop.run(SingleThreadIoEventLoop.java:196) at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:1195) at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) at java.base/java.lang.Thread.run(Thread.java:1474) 13:52:45.984 [multiThreadIoEventLoopGroup-3-3] DEBUG com.socketio4j.socketio.handler.InPacketHandler -- Exception caught in InPacketHandler for channel: 21f09bb4, exception type: IllegalReferenceCountException, message: refCnt: 0, decrement: 1 13:52:45.984 [multiThreadIoEventLoopGroup-3-3] ERROR com.socketio4j.socketio.listener.DefaultExceptionListener -- refCnt: 0, decrement: 1 io.netty.util.IllegalReferenceCountException: refCnt: 0, decrement: 1 at io.netty.util.internal.RefCnt.throwIllegalRefCountOnRelease(RefCnt.java:239) at io.netty.util.internal.RefCnt$VarHandleRefCnt.release0(RefCnt.java:367) at io.netty.util.internal.RefCnt$VarHandleRefCnt.release(RefCnt.java:352) at io.netty.util.internal.RefCnt.release(RefCnt.java:146) at io.netty.buffer.AbstractReferenceCountedByteBuf.release(AbstractReferenceCountedByteBuf.java:83) at com.socketio4j.socketio.messages.PacketsMessage.release(PacketsMessage.java:80) at io.netty.util.ReferenceCountUtil.release(ReferenceCountUtil.java:90) at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:106) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:356) at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:107) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:356) at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:331) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:356) at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:331) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:356) at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1429) at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:918) at io.netty.channel.kqueue.AbstractKQueueStreamChannel$KQueueStreamUnsafe.readReady(AbstractKQueueStreamChannel.java:544) at io.netty.channel.kqueue.AbstractKQueueChannel$AbstractKQueueUnsafe.handle(AbstractKQueueChannel.java:406) at io.netty.channel.kqueue.KQueueIoHandler$DefaultKqueueIoRegistration.handle(KQueueIoHandler.java:472) at io.netty.channel.kqueue.KQueueIoHandler.processReady(KQueueIoHandler.java:214) at io.netty.channel.kqueue.KQueueIoHandler.run(KQueueIoHandler.java:276) at io.netty.channel.SingleThreadIoEventLoop.runIo(SingleThreadIoEventLoop.java:225) at io.netty.channel.SingleThreadIoEventLoop.run(SingleThreadIoEventLoop.java:196) at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:1195) at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) at java.base/java.lang.Thread.run(Thread.java:1474)

@NeatGuyCoding
Copy link
Collaborator Author

@sanjomo Sorry, I made a mistake, thanks for clarifying

Signed-off-by: NeatGuyCoding <15627489+NeatGuyCoding@users.noreply.github.com>
@NeatGuyCoding
Copy link
Collaborator Author

@sanjomo Fixed, Kindly check

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
netty-socketio-core/src/main/java/com/socketio4j/socketio/transport/PollingTransport.java (1)

162-175: ⚠️ Potential issue | 🟡 Minor

Remove the concern about preprocessJson returning a different buffer; it mutates in-place. However, add exception-handling protection for the retained buffer.

The retain() call correctly addresses the IllegalReferenceCountException by keeping the buffer alive after req.release() in the finally block. However, the original review's concern about preprocessJson returning a different ByteBuf is incorrect—the implementation mutates the buffer in-place (as documented on lines 67–68 of PacketDecoder.java) and returns the same buffer. No leak occurs from preprocessJson.

The valid remaining concern is the exception path leak: if an exception occurs between retain() (line 162) and fireChannelRead() (line 169)—such as from preprocessJson throwing UnsupportedEncodingException—the retained buffer will not be explicitly released, creating a reference-count imbalance.

Wrap the risky section in a try-catch to ensure cleanup on exception:

♻️ Suggested: Add exception handling
         content = content.retain();
-        // release POST response before message processing
-        ctx.channel().writeAndFlush(new XHRPostMessage(origin, sessionId));
-
-        Boolean b64 = ctx.channel().attr(EncoderHandler.B64).get();
-        if (b64 != null && b64) {
-            Integer jsonIndex = ctx.channel().attr(EncoderHandler.JSONP_INDEX).get();
-            content = decoder.preprocessJson(jsonIndex, content);
-        }
-
-        ctx.pipeline().fireChannelRead(new PacketsMessage(client, content, Transport.POLLING));
+        try {
+            // release POST response before message processing
+            ctx.channel().writeAndFlush(new XHRPostMessage(origin, sessionId));
+
+            Boolean b64 = ctx.channel().attr(EncoderHandler.B64).get();
+            if (b64 != null && b64) {
+                Integer jsonIndex = ctx.channel().attr(EncoderHandler.JSONP_INDEX).get();
+                content = decoder.preprocessJson(jsonIndex, content);
+            }
+
+            ctx.pipeline().fireChannelRead(new PacketsMessage(client, content, Transport.POLLING));
+        } catch (Exception e) {
+            content.release();
+            throw e;
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@netty-socketio-core/src/main/java/com/socketio4j/socketio/transport/PollingTransport.java`
around lines 162 - 175, PollingTransport calls content.retain() then does
preprocessing and fires a PacketsMessage, but if an exception (e.g. from
decoder.preprocessJson) is thrown between retain() and
ctx.pipeline().fireChannelRead(...) the retained ByteBuf will leak; wrap the
block from after content.retain() through ctx.pipeline().fireChannelRead(...) in
a try-catch (catch Throwable), and in the catch ensure any retained ByteBuf is
released (release the current content; if preprocessJson may return a different
ByteBuf handle both the original and returned buffers appropriately), then
rethrow or propagate the exception; reference PollingTransport,
decoder.preprocessJson, EncoderHandler.B64/JSONP_INDEX and PacketsMessage to
locate where to add the try-catch and release calls.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In
`@netty-socketio-core/src/main/java/com/socketio4j/socketio/transport/PollingTransport.java`:
- Around line 162-175: PollingTransport calls content.retain() then does
preprocessing and fires a PacketsMessage, but if an exception (e.g. from
decoder.preprocessJson) is thrown between retain() and
ctx.pipeline().fireChannelRead(...) the retained ByteBuf will leak; wrap the
block from after content.retain() through ctx.pipeline().fireChannelRead(...) in
a try-catch (catch Throwable), and in the catch ensure any retained ByteBuf is
released (release the current content; if preprocessJson may return a different
ByteBuf handle both the original and returned buffers appropriately), then
rethrow or propagate the exception; reference PollingTransport,
decoder.preprocessJson, EncoderHandler.B64/JSONP_INDEX and PacketsMessage to
locate where to add the try-catch and release calls.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: adfd12b9-fc30-47d9-bf03-726cd29060fe

📥 Commits

Reviewing files that changed from the base of the PR and between 0396a39 and 8ce1503.

📒 Files selected for processing (1)
  • netty-socketio-core/src/main/java/com/socketio4j/socketio/transport/PollingTransport.java

@NeatGuyCoding NeatGuyCoding merged commit fddd5d1 into main Mar 23, 2026
11 of 21 checks passed
@NeatGuyCoding NeatGuyCoding deleted the fix-ssl-buffer-cnt branch March 23, 2026 07:06
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.

[BUG] IllegalReferenceCountException with 4.0.0-alpha Netty 4.2.9.Final when using WSS/TLS

3 participants