This is an automated email from the ASF dual-hosted git repository.

ptupitsyn pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new c819020bc2a IGNITE-28413 Java client: fix race condition in message 
decoder (#7926)
c819020bc2a is described below

commit c819020bc2a6a82a3187f3c998714caf4077e6db
Author: Pavel Tupitsyn <[email protected]>
AuthorDate: Tue Apr 7 11:28:20 2026 +0300

    IGNITE-28413 Java client: fix race condition in message decoder (#7926)
    
    Disable automatic `discardReadBytes` in Netty to fix a race condition and 
data corruption caused by concurrent buffer modification:
    * Netty calls `discardSomeReadBytes` on IO thread, which changes 
`readerIndex`
    * `TcpClientChannel.asyncContinuationExecutor` decodes the response on 
another thread, which also uses and changes `readerIndex`
---
 .../ignite/internal/client/proto/ClientMessageDecoder.java  | 13 ++++++++++---
 1 file changed, 10 insertions(+), 3 deletions(-)

diff --git 
a/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientMessageDecoder.java
 
b/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientMessageDecoder.java
index 0c3bda74a33..4d83baf6e42 100644
--- 
a/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientMessageDecoder.java
+++ 
b/modules/client-common/src/main/java/org/apache/ignite/internal/client/proto/ClientMessageDecoder.java
@@ -27,11 +27,12 @@ import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
 import io.netty.util.CharsetUtil;
 import java.util.Arrays;
 import org.apache.ignite.lang.IgniteException;
+import org.jetbrains.annotations.Nullable;
 
 /**
  * Decodes full client messages: 1. MAGIC for first message. 2. Payload length 
(4 bytes). 3. Payload (N bytes).
  */
-public class ClientMessageDecoder extends LengthFieldBasedFrameDecoder {
+public final class ClientMessageDecoder extends LengthFieldBasedFrameDecoder {
     /** Magic decoded flag. */
     private boolean magicDecoded;
 
@@ -43,13 +44,19 @@ public class ClientMessageDecoder extends 
LengthFieldBasedFrameDecoder {
      */
     public ClientMessageDecoder() {
         super(Integer.MAX_VALUE - HEADER_SIZE, 0, HEADER_SIZE, 0, HEADER_SIZE, 
true);
+
+        // Effectively disable automatic calls to discardReadBytes / 
discardSomeReadBytes:
+        // We pass the buffers to other threads, and discardReadBytes modifies 
the buffer concurrently,
+        // leading to race conditions and corrupted offsets.
+        // Moreover, discardReadBytes is not very beneficial with our access 
patterns:
+        // Read small header => pass to another thread => read fully => 
discard.
+        setDiscardAfterReads(Integer.MAX_VALUE);
     }
 
     /** {@inheritDoc} */
     @Override
-    protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws 
Exception {
+    protected @Nullable Object decode(ChannelHandlerContext ctx, ByteBuf in) 
throws Exception {
         if (!readMagic(in)) {
-            //noinspection ReturnOfNull
             return null;
         }
 

Reply via email to