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

marcuse pushed a commit to branch cassandra-4.0
in repository https://gitbox.apache.org/repos/asf/cassandra.git


The following commit(s) were added to refs/heads/cassandra-4.0 by this push:
     new 76a7e43613 ArrayIndexOutOfBoundsException with repaired data tracking 
and counters
76a7e43613 is described below

commit 76a7e43613e0810eefb53046254c8f48ad1adf50
Author: Marcus Eriksson <[email protected]>
AuthorDate: Fri Aug 29 09:11:41 2025 +0200

    ArrayIndexOutOfBoundsException with repaired data tracking and counters
    
    Patch by marcuse; reviewed by Sam Tunnicliffe for CASSANDRA-20871
---
 CHANGES.txt                                        |  1 +
 src/java/org/apache/cassandra/db/Digest.java       |  5 +++
 .../cassandra/distributed/test/CountersTest.java   | 49 ++++++++++++++++++++++
 3 files changed, 55 insertions(+)

diff --git a/CHANGES.txt b/CHANGES.txt
index cc845d3588..5b4a438d09 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 4.0.20
+ * ArrayIndexOutOfBoundsException with repaired data tracking and counters 
(CASSANDRA-20871)
  * Fix cleanup of old incremental repair sessions in case of owned token range 
changes or a table deleting (CASSANDRA-20877)
  * Fix memory leak in BufferPoolAllocator when a capacity needs to be extended 
(CASSANDRA-20753)
  * Leveled Compaction doesn't validate maxBytesForLevel when the table is 
altered/created (CASSANDRA-20570)
diff --git a/src/java/org/apache/cassandra/db/Digest.java 
b/src/java/org/apache/cassandra/db/Digest.java
index 6a4ecd8fd1..6123af816f 100644
--- a/src/java/org/apache/cassandra/db/Digest.java
+++ b/src/java/org/apache/cassandra/db/Digest.java
@@ -69,6 +69,11 @@ public class Digest
                 // for the purposes of repaired data tracking on the read 
path, exclude
                 // contexts with legacy shards as these may be irrevocably 
different on
                 // different replicas
+
+                // see super.updateWithCounterContext + 
CountersTest.testEmptyContext - counter context can be empty
+                if (accessor.isEmpty(context))
+                    return this;
+
                 if (CounterContext.instance().hasLegacyShards(context, 
accessor))
                     return this;
 
diff --git 
a/test/distributed/org/apache/cassandra/distributed/test/CountersTest.java 
b/test/distributed/org/apache/cassandra/distributed/test/CountersTest.java
index 7cc632f4aa..493db328ad 100644
--- a/test/distributed/org/apache/cassandra/distributed/test/CountersTest.java
+++ b/test/distributed/org/apache/cassandra/distributed/test/CountersTest.java
@@ -18,11 +18,17 @@
 
 package org.apache.cassandra.distributed.test;
 
+import java.io.IOException;
+import java.util.Iterator;
+
 import org.junit.Test;
 
+import org.apache.cassandra.db.Keyspace;
 import org.apache.cassandra.distributed.Cluster;
 import org.apache.cassandra.distributed.api.ConsistencyLevel;
 import org.apache.cassandra.distributed.api.ICoordinator;
+import org.apache.cassandra.io.sstable.Descriptor;
+import org.apache.cassandra.io.sstable.format.SSTableReader;
 
 import static org.apache.cassandra.distributed.api.Feature.GOSSIP;
 import static org.apache.cassandra.distributed.api.Feature.NATIVE_PROTOCOL;
@@ -75,4 +81,47 @@ public class CountersTest extends TestBaseImpl
             }
         }
     }
+
+    @Test
+    public void testEmptyContext() throws IOException
+    {
+        try (Cluster cluster = init(Cluster.build(3)
+                                           .withConfig(c -> c.with(GOSSIP, 
NATIVE_PROTOCOL)
+                                                             
.set("repaired_data_tracking_for_partition_reads_enabled", true)
+                                                             
.set("repaired_data_tracking_for_range_reads_enabled", true))
+                                           .start()))
+        {
+            cluster.schemaChange(withKeyspace("CREATE TABLE %s.t (a ascii, b 
ascii, c counter, d counter, PRIMARY KEY(a, b))"));
+            cluster.get(1).executeInternal(withKeyspace("UPDATE %s.t SET c = c 
+ 1, d = d + 1 WHERE a = 'a1' AND b = 'b1'"));
+            cluster.get(2).executeInternal(withKeyspace("UPDATE %s.t SET c = c 
+ 2, d = d + 2 WHERE a = 'a1' AND b = 'b1'"));
+            cluster.get(3).executeInternal(withKeyspace("UPDATE %s.t SET c = c 
+ 3, d = d + 3 WHERE a = 'a1' AND b = 'b1'"));
+
+            cluster.forEach(i -> i.flush(KEYSPACE));
+
+            cluster.forEach(i -> i.runOnInstance(() -> {
+                Iterator<SSTableReader> sstables = Keyspace.open(KEYSPACE)
+                                                           
.getColumnFamilyStore("t")
+                                                           .getLiveSSTables()
+                                                           .iterator();
+                while (sstables.hasNext())
+                {
+                    SSTableReader sstable = sstables.next();
+                    Descriptor descriptor = sstable.descriptor;
+                    try
+                    {
+                        descriptor.getMetadataSerializer()
+                                  .mutateRepairMetadata(descriptor, 
System.currentTimeMillis(), null, false);
+                        sstable.reloadSSTableMetadata();
+                    }
+                    catch (IOException e)
+                    {
+                        throw new RuntimeException(e);
+                    }
+                }
+            }));
+            cluster.coordinator(1).execute(withKeyspace("select a,d from %s.t 
where a = 'a1'"), ConsistencyLevel.ALL);
+            cluster.coordinator(2).execute(withKeyspace("select a,d from %s.t 
where a = 'a1'"), ConsistencyLevel.QUORUM);
+            cluster.coordinator(3).execute(withKeyspace("select a,d from %s.t 
where a = 'a1'"), ConsistencyLevel.QUORUM);
+        }
+    }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to