Skip to content
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

[UNDERTOW-2532] Fix NPE when transmitting text or binary message to websocket session at the same time #1708

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

raccoonback
Copy link

Hello.
I modified it in consideration of the case where NPE can occur when sending text message to websocket session.
Please check the UNDERTOW-2532 jira ticket for more information.
Thanks.

@raccoonback raccoonback force-pushed the UNDERTOW-2532 branch 3 times, most recently from c2d0ef2 to 5811376 Compare November 29, 2024 09:04
@baranowb baranowb added bug fix Contains bug fix(es) under verification Currently being verified (running tests, reviewing) before posting a review to contributor waiting CI check Ready to be merged but waiting for CI check waiting peer review PRs that edit core classes might require an extra review labels Nov 29, 2024
if (isLast) {
textFrameSender = null;
try {
Channels.writeBlocking(textFrameSender, WebSocketUtils.fromUtf8String(partialMessage));
Copy link
Contributor

Choose a reason for hiding this comment

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

Whole section starting with Channels.writeBlocking() and ending with Channes.flushBlocking() must be executed outside of intrinsic lock section otherwise it will introduce deadlock possibility.

Copy link
Author

Choose a reason for hiding this comment

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

@ropalka That's a good comment! 👍
I will change the section so that there is no impact on the intrinsic lock.

Copy link
Author

Choose a reason for hiding this comment

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

@ropalka
As you mentioned, the section "Channels.writeBlocking() ~ Channels.flushBlocking()" was excluded from the intrinsic lock range.

Please review again.

if (binaryFrameSender == null) {
binaryFrameSender = undertowSession.getWebSocketChannel().send(WebSocketFrameType.BINARY);
}
try {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here, see comment above.

@raccoonback raccoonback requested a review from ropalka December 5, 2024 00:06
…smitting partial messages through the websocket channel
binaryFrameSender = undertowSession.getWebSocketChannel().send(WebSocketFrameType.BINARY);
}

StreamSinkFrameChannel sender = getBinaryFrameSender();
Copy link
Contributor

Choose a reason for hiding this comment

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

Im going to talk about this with @ropalka . But it seems its technically possible for competing send to undercut one another, though I might be missing how this piece is being used.

Copy link
Author

@raccoonback raccoonback Dec 5, 2024

Choose a reason for hiding this comment

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

@baranowb
I would like to share my understanding and seek your feedback.

For example, in the case of sending a Text message, I assume that a single message is divided into multiple frames for transmission. In this process, the TextFrameSender class variable is used to call sendText(partialMessage, isLast) multiple times, sending partial messages through the same channel. (During this process, other send() methods are not allowed to send messages.)

However, if partial frames are being sent from multiple threads, I believe there is a possibility that one thread might set textFrameSender to null first, and then another thread could call Channels.flushBlocking(StreamSinkFrameChannel), leading to a NullPointerException (NPE).

To address this, I have modified the code to ensure synchronization when accessing the shared resource, the textFrameSender class variable.

Additionally, could you please clarify what specific scenarios you had in mind regarding 'competing send to undercut one another'?
Understanding this will help me consider those cases and make further improvements accordingly.

}
}
partialByte.clear();
}

private synchronized StreamSinkFrameChannel getTextFrameSender() throws IOException {
Copy link
Contributor

Choose a reason for hiding this comment

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

Two different senders are guarded by single instance lock. Is there a reason for such arrangement?

Copy link
Author

@raccoonback raccoonback Dec 5, 2024

Choose a reason for hiding this comment

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

@baranowb
Thank you for your question.
The textFrameSender and binaryFrameSender class variables are initialized and set to null during partial message transmission.
During this process, all other send() methods are blocked from sending messages. Specifically, while sending partial messages using textFrameSender, transmissions using binaryFrameSender are also restricted.

If we were to apply separate locks for textFrameSender and binaryFrameSender, there would be a high risk of deadlocks, especially when different methods interact with these variables concurrently.
To mitigate this, we decided to use a single instance lock to manage both senders.

Additionally, other send() methods call assertNotInFragment() to check if a partial message is being transmitted.
Using separate locks in this scenario could similarly lead to deadlocks.
For these reasons, we opted for a single lock to ensure safe and consistent behavior.

Copy link
Author

Choose a reason for hiding this comment

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

@baranowb
Alternatively, it seems possible to control concurrency by declaring textFrameSender and binaryFrameSender as AtomicReference<StreamSinkFrameChannel> without using synchronized.

@raccoonback raccoonback requested a review from baranowb December 6, 2024 00:13
@raccoonback
Copy link
Author

@ropalka @baranowb
Hello,
Has there been any additional discussion on this?
I’d appreciate it if you could review it further. 😊

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug fix Contains bug fix(es) under verification Currently being verified (running tests, reviewing) before posting a review to contributor waiting CI check Ready to be merged but waiting for CI check waiting peer review PRs that edit core classes might require an extra review
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants