This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch cassandra-6.0
in repository https://gitbox.apache.org/repos/asf/cassandra.git
The following commit(s) were added to refs/heads/cassandra-6.0 by this push:
new 03422edb4b Relax assertion on partitioner instances in
SinglePartitionReadCommand
03422edb4b is described below
commit 03422edb4bc13c34dffca5cce2e29603db436513
Author: Sam Tunnicliffe <[email protected]>
AuthorDate: Wed Jun 24 11:23:39 2026 +0100
Relax assertion on partitioner instances in SinglePartitionReadCommand
Patch by Sam Tunnicliffe; reviewed by Marcus Eriksson for CASSANDRA-21251
---
CHANGES.txt | 1 +
.../cassandra/db/SinglePartitionReadCommand.java | 4 +-
.../org/apache/cassandra/dht/IPartitioner.java | 9 ++
...ndexTablePartitionerAfterForceSnapshotTest.java | 141 +++++++++++++++++++++
.../upgrade/ClusterMetadata2iInitializeTest.java | 112 ++++++++++++++++
5 files changed, 266 insertions(+), 1 deletion(-)
diff --git a/CHANGES.txt b/CHANGES.txt
index 36dd204c0f..eef9e5b576 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
6.0-alpha2
+ * Relax assertion on partitioner instances in SinglePartitionReadCommand
(CASSANDRA-21251)
* Report cancelled read command execution to coordinator as a
RequestFailure.TIMEOUT (CASSANDRA-21468)
* Fix TCM log catchup from peer with snapshots and gaps in the log sequence
(CASSANDRA-21455)
* Speed up nodetool doc generation by producing all command help in a single
jvm (CASSANDRA-21444)
diff --git a/src/java/org/apache/cassandra/db/SinglePartitionReadCommand.java
b/src/java/org/apache/cassandra/db/SinglePartitionReadCommand.java
index 28ae9b786a..567d9f066c 100644
--- a/src/java/org/apache/cassandra/db/SinglePartitionReadCommand.java
+++ b/src/java/org/apache/cassandra/db/SinglePartitionReadCommand.java
@@ -70,6 +70,7 @@ import org.apache.cassandra.db.transform.Transformation;
import org.apache.cassandra.db.virtual.VirtualKeyspaceRegistry;
import org.apache.cassandra.db.virtual.VirtualTable;
import org.apache.cassandra.dht.Bounds;
+import org.apache.cassandra.dht.IPartitioner;
import org.apache.cassandra.exceptions.RequestExecutionException;
import org.apache.cassandra.index.Index;
import org.apache.cassandra.io.sstable.SSTableReadsListener;
@@ -121,7 +122,8 @@ public class SinglePartitionReadCommand extends ReadCommand
implements SinglePar
DataRange dataRange)
{
super(serializedAtEpoch, Kind.SINGLE_PARTITION, isDigest,
digestVersion, acceptsTransient, potentialTxnConflicts, metadata, nowInSec,
columnFilter, rowFilter, limits, indexQueryPlan, trackWarnings, dataRange);
- assert partitionKey.getPartitioner() == metadata.partitioner;
+ assert IPartitioner.equivalent(partitionKey.getPartitioner(),
metadata.partitioner) : String.format("Mismatching partitioners for key (%s)
and table metadata (%s)",
+
partitionKey.getPartitioner(),
metadata.partitioner);
this.partitionKey = partitionKey;
this.clusteringIndexFilter = clusteringIndexFilter;
}
diff --git a/src/java/org/apache/cassandra/dht/IPartitioner.java
b/src/java/org/apache/cassandra/dht/IPartitioner.java
index 6adc9d92fa..dfd26f6165 100644
--- a/src/java/org/apache/cassandra/dht/IPartitioner.java
+++ b/src/java/org/apache/cassandra/dht/IPartitioner.java
@@ -41,6 +41,15 @@ public interface IPartitioner
return DatabaseDescriptor.getPartitioner();
}
+ static boolean equivalent(IPartitioner p1, IPartitioner p2)
+ {
+ if (p1 == p2) return true;
+ if (p1.getClass() != p2.getClass()) return false;
+ if (p1.getClass() == LocalPartitioner.class)
+ return ((LocalPartitioner)p1).comparator ==
((LocalPartitioner)p2).comparator;
+ return true;
+ }
+
/**
* @return a new instance of a reusable key
*/
diff --git
a/test/distributed/org/apache/cassandra/distributed/test/tcm/IndexTablePartitionerAfterForceSnapshotTest.java
b/test/distributed/org/apache/cassandra/distributed/test/tcm/IndexTablePartitionerAfterForceSnapshotTest.java
new file mode 100644
index 0000000000..ed0cbfd46d
--- /dev/null
+++
b/test/distributed/org/apache/cassandra/distributed/test/tcm/IndexTablePartitionerAfterForceSnapshotTest.java
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.distributed.test.tcm;
+
+import java.io.IOException;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import com.google.common.util.concurrent.Uninterruptibles;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.MethodDelegation;
+import net.bytebuddy.implementation.bind.annotation.SuperCall;
+
+import org.awaitility.Awaitility;
+import org.junit.Test;
+
+import org.apache.cassandra.db.DecoratedKey;
+import org.apache.cassandra.db.ReadCommand;
+import org.apache.cassandra.db.ReadExecutionController;
+import org.apache.cassandra.db.rows.UnfilteredRowIterator;
+import org.apache.cassandra.distributed.Cluster;
+import org.apache.cassandra.distributed.api.ConsistencyLevel;
+import org.apache.cassandra.distributed.shared.ClusterUtils;
+import org.apache.cassandra.distributed.test.TestBaseImpl;
+import org.apache.cassandra.index.internal.CassandraIndexSearcher;
+import org.apache.cassandra.net.Verb;
+import org.apache.cassandra.tcm.Epoch;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.apache.cassandra.distributed.api.Feature.GOSSIP;
+import static org.apache.cassandra.distributed.api.Feature.NETWORK;
+import static org.junit.Assert.assertEquals;
+
+public class IndexTablePartitionerAfterForceSnapshotTest extends TestBaseImpl
+{
+ @Test
+ public void indexQueryAfterSnapshotTest() throws IOException,
InterruptedException
+ {
+ try (Cluster cluster = Cluster.build(3)
+
.withInstanceInitializer(BBInstaller::install)
+ .withConfig(config ->
config.with(GOSSIP, NETWORK))
+ .start())
+ {
+ init(cluster);
+ cluster.schemaChange("CREATE TABLE " + KEYSPACE + ".tbl (pk int,
ck int, v int, PRIMARY KEY (pk, ck))");
+ cluster.schemaChange(withKeyspace("create index iii2 on %s.tbl
(v)"));
+ for (int i = 0; i < 10000; i++)
+ cluster.coordinator(1).execute(withKeyspace("insert into
%s.tbl (pk, ck, v) values (?, ?, ?)"), ConsistencyLevel.ALL, 1, i, i % 5);
+
+ // drop metadata change replication messages from node 1 to nodes
2 & 3
+ cluster.filters().verbs(Verb.TCM_REPLICATION.id,
+ Verb.TCM_FETCH_CMS_LOG_RSP.id,
+ Verb.TCM_FETCH_PEER_LOG_RSP.id,
+ Verb.TCM_CURRENT_EPOCH_REQ.id)
+ .from(1)
+ .drop()
+ .on();
+
+ // node1 makes some metadata changes interspersed with snapshots.
When the message filters are dropped, this
+ // will cause nodes 2 & 3 to try and catchup. We want multiple
snapshots to be taken to ensure that the
+ // catchup responses contain one, rather than just a list of
entries.
+ cluster.get(1).nodetoolResult("cms",
"snapshot").asserts().success();
+ cluster.get(1).nodetoolResult("cms",
"snapshot").asserts().success();
+ final Epoch epoch = ClusterUtils.getCurrentEpoch(cluster.get(1));
+ // start executing
+ AtomicBoolean stop = new AtomicBoolean();
+ AtomicInteger queryFailures = new AtomicInteger();
+ Thread t = new Thread(() -> {
+ while (!stop.get())
+ {
+ try
+ {
+ cluster.coordinator(1).execute(withKeyspace("select *
from %s.tbl where pk=1 and v=4"), ConsistencyLevel.ALL);
+ }
+ catch (Throwable e)
+ {
+ queryFailures.incrementAndGet();
+ }
+ }
+ });
+ t.start();
+ TimeUnit.SECONDS.sleep(1);
+ // drop the filters and wait for nodes 2 & 3 to catch up
+ cluster.filters().reset();
+ Awaitility.waitAtMost(30, TimeUnit.SECONDS)
+ .pollInterval(1, TimeUnit.SECONDS)
+ .until(() ->
ClusterUtils.getCurrentEpoch(cluster.get(2)).isEqualOrAfter(epoch) &&
+
ClusterUtils.getCurrentEpoch(cluster.get(3)).isEqualOrAfter(epoch));
+
+ // give it another second of querying
+ TimeUnit.SECONDS.sleep(1);
+ stop.set(true);
+ t.join();
+ assertEquals(0, queryFailures.get());
+ }
+ }
+
+ public static class BBInstaller
+ {
+ public static void install(ClassLoader classLoader, int inst)
+ {
+ if (inst == 1)
+ return;
+ new ByteBuddy().rebase(CassandraIndexSearcher.class)
+ .method(named("queryIndex"))
+ .intercept(MethodDelegation.to(BBInterceptor.class))
+ .make()
+ .load(classLoader,
ClassLoadingStrategy.Default.INJECTION);
+ }
+ }
+
+ public static class BBInterceptor
+ {
+ public static UnfilteredRowIterator queryIndex(DecoratedKey indexKey,
ReadCommand command, ReadExecutionController executionController, @SuperCall
Callable<UnfilteredRowIterator> zuper) throws Exception
+ {
+ // this makes it more likely that we decorate the key with one
partitioner and execute the SinglePartitionReadCommand with another
+ Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);
+ return zuper.call();
+ }
+ }
+}
diff --git
a/test/distributed/org/apache/cassandra/distributed/upgrade/ClusterMetadata2iInitializeTest.java
b/test/distributed/org/apache/cassandra/distributed/upgrade/ClusterMetadata2iInitializeTest.java
new file mode 100644
index 0000000000..8d4ee9f480
--- /dev/null
+++
b/test/distributed/org/apache/cassandra/distributed/upgrade/ClusterMetadata2iInitializeTest.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.distributed.upgrade;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+
+import com.google.common.util.concurrent.Uninterruptibles;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.MethodDelegation;
+import net.bytebuddy.implementation.bind.annotation.SuperCall;
+
+import org.junit.Test;
+
+import org.apache.cassandra.db.DecoratedKey;
+import org.apache.cassandra.db.ReadCommand;
+import org.apache.cassandra.db.ReadExecutionController;
+import org.apache.cassandra.db.rows.UnfilteredRowIterator;
+import org.apache.cassandra.distributed.UpgradeableCluster;
+import org.apache.cassandra.distributed.api.ConsistencyLevel;
+import org.apache.cassandra.distributed.api.Feature;
+import org.apache.cassandra.index.internal.CassandraIndexSearcher;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static org.junit.Assert.assertEquals;
+
+public class ClusterMetadata2iInitializeTest extends UpgradeTestBase
+{
+ @Test
+ public void initializeCMSWithConcurrentIndexReadsTest() throws Throwable
+ {
+ Consumer<UpgradeableCluster.Builder > builderUpdater = builder ->
builder.withInstanceInitializer(BBInstaller::install);
+ new TestCase()
+ .nodes(3)
+ .withConfig((cfg) -> cfg.with(Feature.GOSSIP))
+ .withBuilder(builderUpdater)
+ .upgradesToCurrentFrom(v41)
+ .setup((cluster) -> {
+ cluster.schemaChange("CREATE TABLE " + KEYSPACE + ".tbl (pk int,
ck int, v int, PRIMARY KEY (pk, ck))");
+ cluster.schemaChange(withKeyspace("create index iii2 on %s.tbl
(v)"));
+ for (int i = 0; i < 10000; i++)
+ cluster.coordinator(1).execute(withKeyspace("insert into
%s.tbl (pk, ck, v) values (?, ?, ?)"), ConsistencyLevel.ALL, 1, i, i%5);
+ })
+ .runAfterClusterUpgrade((cluster) -> {
+ AtomicBoolean stop = new AtomicBoolean();
+ AtomicInteger queryFailures = new AtomicInteger();
+ Thread t = new Thread(() -> {
+ while (!stop.get())
+ {
+ try
+ {
+ cluster.coordinator(1).execute(withKeyspace("select *
from %s.tbl where pk=1 and v=4"), ConsistencyLevel.ALL);
+ }
+ catch (Throwable e)
+ {
+ queryFailures.incrementAndGet();
+ }
+ }
+ });
+ t.start();
+ cluster.get(1).nodetoolResult("cms",
"initialize").asserts().success();
+ stop.set(true);
+ t.join();
+ assertEquals(0, queryFailures.get());
+ }).run();
+ }
+
+ public static class BBInstaller
+ {
+ public static void install(ClassLoader classLoader, int inst)
+ {
+ if (inst == 1)
+ return;
+ new ByteBuddy().rebase(CassandraIndexSearcher.class)
+ .method(named("queryIndex"))
+ .intercept(MethodDelegation.to(BBInterceptor.class))
+ .make()
+ .load(classLoader,
ClassLoadingStrategy.Default.INJECTION);
+ }
+ }
+
+ public static class BBInterceptor
+ {
+ public static UnfilteredRowIterator queryIndex(DecoratedKey indexKey,
ReadCommand command, ReadExecutionController executionController, @SuperCall
Callable<UnfilteredRowIterator> zuper) throws Exception
+ {
+ // this makes it more likely that we decorate the key with one
partitioner and execute the SinglePartitionReadCommand with another
+ Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);
+ return zuper.call();
+ }
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]