-
Notifications
You must be signed in to change notification settings - Fork 913
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix a possible IllegalReferenceCountException
in WebSocket handshake
#5495
Merged
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Motivation: The following `IllegalReferenceCountException` could be raised during HTTP/1 Websocket handshake when `HttpClientCodec` is removed. ``` 01:54:00.031 [armeria-common-worker-kqueue-2-2] WARN c.l.a.i.client.PendingExceptionUtil - [id: 0xd2fa853c, L:/10.31.29.254:65140 - R:vks-api-prod.linecorp-dev.com/10.120.97.166:443] Unexpected suppressed exception: com.linecorp.armeria.common.ClosedSessionException: null Caused by: io.netty.util.IllegalReferenceCountException: refCnt: 0, decrement: 1 at io.netty.util.internal.ReferenceCountUpdater.toLiveRealRefCnt(ReferenceCountUpdater.java:83) at io.netty.util.internal.ReferenceCountUpdater.release(ReferenceCountUpdater.java:148) at io.netty.buffer.AbstractReferenceCountedByteBuf.release(AbstractReferenceCountedByteBuf.java:101) at io.netty.util.ReferenceCountUtil.release(ReferenceCountUtil.java:90) at com.linecorp.armeria.client.WebSocketHttp1ClientChannelHandler.channelRead(WebSocketHttp1ClientChannelHandler.java:246) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436) at io.netty.handler.codec.ByteToMessageDecoder.handlerRemoved(ByteToMessageDecoder.java:266) at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.remove0(CombinedChannelDuplexHandler.java:608) at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.remove(CombinedChannelDuplexHandler.java:593) at io.netty.channel.CombinedChannelDuplexHandler.handlerRemoved(CombinedChannelDuplexHandler.java:181) at io.netty.channel.AbstractChannelHandlerContext.callHandlerRemoved(AbstractChannelHandlerContext.java:1138) at io.netty.channel.DefaultChannelPipeline.callHandlerRemoved0(DefaultChannelPipeline.java:637) at io.netty.channel.DefaultChannelPipeline.remove(DefaultChannelPipeline.java:477) at io.netty.channel.DefaultChannelPipeline.remove(DefaultChannelPipeline.java:429) at com.linecorp.armeria.client.WebSocketHttp1ClientChannelHandler.channelRead(WebSocketHttp1ClientChannelHandler.java:209) ``` - If 101 Switching Protocols response is received, `WebSocketHttp1ClientChannelHandler` removes `HttpClientCodec` from the channel pipeline. https://github.com/line/armeria/blob/75d5af1be6770e5e42690e1b6181c40fed1bf568/core/src/main/java/com/linecorp/armeria/client/WebSocketHttp1ClientChannelHandler.java#L195 - The removal may produce remaing buffers via `channelRead()`. https://github.com/netty/netty/blob/06a7e12df9118e3ffbeae3cdc50e638a999d93ec/codec/src/main/java/io/netty/handler/codec/ByteToMessageDecoder.java#L266-L267 - The packet after the WebSocket handshake comes in, but the state is still `NEEDS_HANDSHAKE_RESPONSE`, so the state machine is broken and the `ByteBuf` could be double released. https://github.com/line/armeria/blob/75d5af1be6770e5e42690e1b6181c40fed1bf568/core/src/main/java/com/linecorp/armeria/client/WebSocketHttp1ClientChannelHandler.java#L165-L167 Modifications: - Retain a message before passing the next `ChannelHandler` because it is released always. - Remove `HttpClientCodec` after `state` is transitted to `UPGRADE_COMPLTE` to correctly handle pending messages in the buffer. Result: `IllegalReferenceCountException` no longer occurs during WebSocket handshake.
ikhoon
force-pushed
the
fix-websocket-illegal-release
branch
from
March 12, 2024 03:27
ee642af
to
0d28953
Compare
minwoox
approved these changes
Mar 12, 2024
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice catch! Thanks!
jrhee17
approved these changes
Mar 14, 2024
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice! Thanks for the analysis + fix! 🙇 👍 🙇
Thanks for all the reviews. 🙇♂️🙇♂️ |
ikhoon
added a commit
that referenced
this pull request
Mar 26, 2024
#5495) Motivation: The following `IllegalReferenceCountException` could be raised during a HTTP/1 Websocket handshake. ```java 01:54:00.031 [armeria-common-worker-kqueue-2-2] WARN c.l.a.i.client.PendingExceptionUtil - [id: 0xd2fa853c, L:/10.31.29.254:65140 - R:vks-api-prod.linecorp-dev.com/10.120.97.166:443] Unexpected suppressed exception: com.linecorp.armeria.common.ClosedSessionException: null Caused by: io.netty.util.IllegalReferenceCountException: refCnt: 0, decrement: 1 at io.netty.util.internal.ReferenceCountUpdater.toLiveRealRefCnt(ReferenceCountUpdater.java:83) at io.netty.util.internal.ReferenceCountUpdater.release(ReferenceCountUpdater.java:148) at io.netty.buffer.AbstractReferenceCountedByteBuf.release(AbstractReferenceCountedByteBuf.java:101) at io.netty.util.ReferenceCountUtil.release(ReferenceCountUtil.java:90) at com.linecorp.armeria.client.WebSocketHttp1ClientChannelHandler.channelRead(WebSocketHttp1ClientChannelHandler.java:246) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436) at io.netty.handler.codec.ByteToMessageDecoder.handlerRemoved(ByteToMessageDecoder.java:266) at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.remove0(CombinedChannelDuplexHandler.java:608) at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.remove(CombinedChannelDuplexHandler.java:593) at io.netty.channel.CombinedChannelDuplexHandler.handlerRemoved(CombinedChannelDuplexHandler.java:181) at io.netty.channel.AbstractChannelHandlerContext.callHandlerRemoved(AbstractChannelHandlerContext.java:1138) at io.netty.channel.DefaultChannelPipeline.callHandlerRemoved0(DefaultChannelPipeline.java:637) at io.netty.channel.DefaultChannelPipeline.remove(DefaultChannelPipeline.java:477) at io.netty.channel.DefaultChannelPipeline.remove(DefaultChannelPipeline.java:429) at com.linecorp.armeria.client.WebSocketHttp1ClientChannelHandler.channelRead(WebSocketHttp1ClientChannelHandler.java:209) ``` - When 101 Switching Protocols response is received, `WebSocketHttp1ClientChannelHandler` immediately removes `HttpClientCodec` from the channel pipeline. https://github.com/line/armeria/blob/75d5af1be6770e5e42690e1b6181c40fed1bf568/core/src/main/java/com/linecorp/armeria/client/WebSocketHttp1ClientChannelHandler.java#L195 - The removal may emit remaining buffers via `channelRead()`. https://github.com/netty/netty/blob/06a7e12df9118e3ffbeae3cdc50e638a999d93ec/codec/src/main/java/io/netty/handler/codec/ByteToMessageDecoder.java#L266-L267 - WebSocket packets after the HTTP/1 WebSocket handshake are passed to `WebSocketHttp1ClientChannelHandler`, but the state is still `NEEDS_HANDSHAKE_RESPONSE`. As a result, the state machine becomes broken and the `ByteBuf` could be double released. https://github.com/line/armeria/blob/75d5af1be6770e5e42690e1b6181c40fed1bf568/core/src/main/java/com/linecorp/armeria/client/WebSocketHttp1ClientChannelHandler.java#L165-L167 Modifications: - Retain a message before passing the next `ChannelHandler` because it is always released. - Remove `HttpClientCodec` after `EMPTY_LAST_CONTENT` is received and `state` becomes `UPGRADE_COMPLTE` to handle pending messages in the buffer correctly. Result: `IllegalReferenceCountException` no longer occurs during WebSocket handshake.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Motivation:
The following
IllegalReferenceCountException
could be raised during a HTTP/1 Websocket handshake.WebSocketHttp1ClientChannelHandler
immediately removesHttpClientCodec
from the channel pipeline.armeria/core/src/main/java/com/linecorp/armeria/client/WebSocketHttp1ClientChannelHandler.java
Line 195 in 75d5af1
channelRead()
. https://github.com/netty/netty/blob/06a7e12df9118e3ffbeae3cdc50e638a999d93ec/codec/src/main/java/io/netty/handler/codec/ByteToMessageDecoder.java#L266-L267WebSocketHttp1ClientChannelHandler
, but the state is stillNEEDS_HANDSHAKE_RESPONSE
. As a result, the state machine becomes broken and theByteBuf
could be double released.armeria/core/src/main/java/com/linecorp/armeria/client/WebSocketHttp1ClientChannelHandler.java
Lines 165 to 167 in 75d5af1
Modifications:
ChannelHandler
because it is always released.HttpClientCodec
afterEMPTY_LAST_CONTENT
is received andstate
becomesUPGRADE_COMPLTE
to handle pending messages in the buffer correctly.Result:
IllegalReferenceCountException
no longer occurs during WebSocket handshake.