Fix ByteBuffer concurrency issue in ConcurrentWebSocketSessionDecorator for binary messages #35520
+135
−6
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.
For reference, I'm not good at English, so I wrote this message using a translator. Please understand if my expressions are a bit awkward or if there are any issues.
📝 Summary
This is an alternative approach to PR #35519, addressing the same thread-safety issue with a more fundamental solution.
With Spring's growing adoption of virtual threads (Project Loom), concurrency levels in applications have increased dramatically. This exposed a critical thread-safety issue when sharing
BinaryMessage
instances across multiple WebSocket sessions.The Problem: When multiple threads send the same
BinaryMessage
to different WebSocket sessions, they share a singleByteBuffer
whose position gets corrupted during concurrent reads, resulting in data corruption or empty messages.Why This Is a Trap: Developers using binary messages might not be aware that
ByteBuffer
's position is mutable state. It's natural to assume that sending the same message to multiple sessions is safe - after all, we're just "reading" the data. This hidden implementation detail makes this bug easy to accidentally introduce and particularly challenging to diagnose.I was also unaware of this implementation detail and only discovered it after spending two days debugging in production when concurrency levels increased dramatically. I hope this fix saves others from the same frustrating experience.
🔄 Changes
BinaryMessage.getPayload()
to returnByteBuffer.duplicate()
instead of the direct buffer reference✅ Testing
Added test
concurrentBinaryMessageSharingAcrossSessions()
that reproduces the issue and validates the fix.📊 Impact
🏗️ Why Fix in BinaryMessage Instead of Decorators?
Previous Approach (PR #35519): The original PR fixed this in
ConcurrentWebSocketSessionDecorator
, but this approach had limitations:BinaryMessage
directly would still face the issueCurrent Approach: Fixing at the source (
BinaryMessage.getPayload()
) provides:BinaryMessage
is automatically protectedThis follows the principle of "pit of success" - making the safe behavior the default behavior.
🎯 Why This Matters Now
As Spring applications increasingly adopt virtual threads:
This fix ensures WebSocket handling remains robust in the virtual thread era by addressing the thread-safety issue at its root.
Note: This issue only manifests under high concurrency, particularly with virtual threads, making it challenging to detect in traditional testing scenarios. The fix at the
BinaryMessage
level ensures all usage patterns are protected, not just those using specific decorators.