From 077b7ebe22c750b9f0c4a83f7979356b5095d6a5 Mon Sep 17 00:00:00 2001 From: jaydeepkumar1984 Date: Mon, 22 Dec 2025 15:49:35 -0800 Subject: [PATCH 1/3] No need to evict already prepared statements, as it creates a race condition between multiple threads patch by Jaydeepkumar Chovatia; reviewed by Alex Petrov for CASSANDRA-17401 --- CHANGES.txt | 1 + .../apache/cassandra/cql3/QueryProcessor.java | 5 +- .../miscellaneous/PreparedStatementTest.java | 85 +++++++++++++++++++ 3 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 test/unit/org/apache/cassandra/cql3/validation/miscellaneous/PreparedStatementTest.java diff --git a/CHANGES.txt b/CHANGES.txt index 9032d3276d..ae4ff28dd4 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 4.0.20 + * No need to evict already prepared statements, as it creates a race condition between multiple threads (CASSANDRA-17401) * Switch lz4-java to at.yawk.lz4 version due to CVE (CASSANDRA-20152) * Restrict BytesType compatibility to scalar types only (CASSANDRA-20982) * Backport fix to nodetool gcstats output for direct memory (CASSANDRA-21037) diff --git a/src/java/org/apache/cassandra/cql3/QueryProcessor.java b/src/java/org/apache/cassandra/cql3/QueryProcessor.java index 861222890a..c447059471 100644 --- a/src/java/org/apache/cassandra/cql3/QueryProcessor.java +++ b/src/java/org/apache/cassandra/cql3/QueryProcessor.java @@ -649,8 +649,11 @@ public class QueryProcessor implements QueryHandler return createResultMessage(hashWithKeyspace, cachedWithKeyspace); } } - else + else if (cachedWithoutKeyspace != null || cachedWithKeyspace != null) { + // only evict if we know one of the statements is cached + // This can happen during upgrade when switching prepared statement behaviour + // So only if one of the cache is missing then it means we need to re-prepare // Make sure the missing one is going to be eventually re-prepared evictPrepared(hashWithKeyspace); evictPrepared(hashWithoutKeyspace); diff --git a/test/unit/org/apache/cassandra/cql3/validation/miscellaneous/PreparedStatementTest.java b/test/unit/org/apache/cassandra/cql3/validation/miscellaneous/PreparedStatementTest.java new file mode 100644 index 0000000000..a0bebed43c --- /dev/null +++ b/test/unit/org/apache/cassandra/cql3/validation/miscellaneous/PreparedStatementTest.java @@ -0,0 +1,85 @@ +/* + * 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.cql3.validation.miscellaneous; + +import org.junit.Assert; +import org.junit.Test; + +import org.apache.cassandra.cql3.CQLTester; +import org.apache.cassandra.cql3.QueryProcessor; +import org.apache.cassandra.service.ClientState; +import org.apache.cassandra.transport.messages.ResultMessage; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; + +public class PreparedStatementTest extends CQLTester +{ + private static final int NUM_THREADS = 50; + private final CountDownLatch startLatch = new CountDownLatch(1); + private final CountDownLatch finishLatch = new CountDownLatch(NUM_THREADS); + + @Test + public void testPreparedStatementStaysInCache() throws Throwable + { + execute("CREATE TABLE " + KEYSPACE + ".test_fullyqualified(a int primary key, b int)"); + + ClientState state = ClientState.forInternalCalls(); + Assert.assertEquals(0, QueryProcessor.instance.getPreparedStatements().size()); + final ResultMessage.Prepared[] preparedSelect = new ResultMessage.Prepared[NUM_THREADS]; + AtomicBoolean preparedStatementPresentInCache = new AtomicBoolean(true); + for (int i = 0; i < NUM_THREADS; i++) + { + int threadId = i; + Thread thread = new Thread(() -> { + try + { + // Wait until the start signal is given + startLatch.await(); + + // Code to be executed in each thread + preparedSelect[threadId] = QueryProcessor.instance.prepare( + String.format("SELECT b FROM %s.test_fullyqualified where a = 10", KEYSPACE), state); + Assert.assertNotNull(preparedSelect[threadId].statementId); + if(!QueryProcessor.instance.getPreparedStatements().containsKey(preparedSelect[threadId].statementId)) + { + preparedStatementPresentInCache.set(false); + } + } + catch (InterruptedException e) + { + Thread.currentThread().interrupt(); + } + finally + { + // Signal that this thread has finished + finishLatch.countDown(); + } + Assert.fail(); + }); + thread.start(); + } + + // Signal all threads to start + startLatch.countDown(); + + // Wait for all threads to finish + finishLatch.await(); + Assert.assertTrue(preparedStatementPresentInCache.get()); + } +} From 4ada37e940431686cbc82e2d0b01f482f0041a3f Mon Sep 17 00:00:00 2001 From: Jaydeepkumar Chovatia Date: Sun, 21 Jan 2024 12:44:56 -0800 Subject: [PATCH 2/3] No need to evict already prepared statements, as it creates a race condition between multiple threads patch by Jaydeepkumar Chovatia; reviewed by Alex Petrov for CASSANDRA-17401 --- CHANGES.txt | 1 + .../apache/cassandra/cql3/QueryProcessor.java | 7 -- .../miscellaneous/PreparedStatementTest.java | 85 +++++++++++++++++++ 3 files changed, 86 insertions(+), 7 deletions(-) create mode 100644 test/unit/org/apache/cassandra/cql3/validation/miscellaneous/PreparedStatementTest.java diff --git a/CHANGES.txt b/CHANGES.txt index 97268583ba..5f8be1232c 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 4.1.11 + * No need to evict already prepared statements, as it creates a race condition between multiple threads (CASSANDRA-17401) * Disk usage guardrail cannot be disabled when failure threshold is reached (CASSANDRA-21057) * ReadCommandController should close fast to avoid deadlock when building secondary index (CASSANDRA-19564) * Redact security-sensitive information in system_views.settings (CASSANDRA-20856) diff --git a/src/java/org/apache/cassandra/cql3/QueryProcessor.java b/src/java/org/apache/cassandra/cql3/QueryProcessor.java index 83ae821ba3..bfb281daa4 100644 --- a/src/java/org/apache/cassandra/cql3/QueryProcessor.java +++ b/src/java/org/apache/cassandra/cql3/QueryProcessor.java @@ -752,13 +752,6 @@ public class QueryProcessor implements QueryHandler return createResultMessage(hashWithKeyspace, cachedWithKeyspace); } } - else - { - // Make sure the missing one is going to be eventually re-prepared - evictPrepared(hashWithKeyspace); - evictPrepared(hashWithoutKeyspace); - } - Prepared prepared = parseAndPrepare(queryString, clientState, false); CQLStatement statement = prepared.statement; diff --git a/test/unit/org/apache/cassandra/cql3/validation/miscellaneous/PreparedStatementTest.java b/test/unit/org/apache/cassandra/cql3/validation/miscellaneous/PreparedStatementTest.java new file mode 100644 index 0000000000..8b6185389b --- /dev/null +++ b/test/unit/org/apache/cassandra/cql3/validation/miscellaneous/PreparedStatementTest.java @@ -0,0 +1,85 @@ +/* + * 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.cql3.validation.miscellaneous; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.junit.Assert; +import org.junit.Test; + +import org.apache.cassandra.cql3.CQLTester; +import org.apache.cassandra.cql3.QueryProcessor; +import org.apache.cassandra.service.ClientState; +import org.apache.cassandra.transport.messages.ResultMessage; + +public class PreparedStatementTest extends CQLTester +{ + private static final int NUM_THREADS = 50; + private final CountDownLatch startLatch = new CountDownLatch(1); + private final CountDownLatch finishLatch = new CountDownLatch(NUM_THREADS); + + @Test + public void testPreparedStatementStaysInCache() throws Throwable + { + execute("CREATE TABLE " + KEYSPACE + ".test_fullyqualified(a int primary key, b int)"); + + ClientState state = ClientState.forInternalCalls(); + Assert.assertEquals(0, QueryProcessor.instance.getPreparedStatements().size()); + final ResultMessage.Prepared[] preparedSelect = new ResultMessage.Prepared[NUM_THREADS]; + AtomicBoolean preparedStatementPresentInCache = new AtomicBoolean(true); + for (int i = 0; i < NUM_THREADS; i++) + { + int threadId = i; + Thread thread = new Thread(() -> { + try + { + // Wait until the start signal is given + startLatch.await(); + + // Code to be executed in each thread + preparedSelect[threadId] = QueryProcessor.instance.prepare( + String.format("SELECT b FROM %s.test_fullyqualified where a = 10", KEYSPACE), state); + Assert.assertNotNull(preparedSelect[threadId].statementId); + if(!QueryProcessor.instance.getPreparedStatements().containsKey(preparedSelect[threadId].statementId)) + { + preparedStatementPresentInCache.set(false); + } + } + catch (InterruptedException e) + { + Thread.currentThread().interrupt(); + } + finally + { + // Signal that this thread has finished + finishLatch.countDown(); + } + Assert.fail(); + }); + thread.start(); + } + + // Signal all threads to start + startLatch.countDown(); + + // Wait for all threads to finish + finishLatch.await(); + Assert.assertTrue(preparedStatementPresentInCache.get()); + } +} From 1461e754e8969fc56c14206bcfafb6d91bdb5fef Mon Sep 17 00:00:00 2001 From: Bernardo Botella Corbi Date: Mon, 16 Feb 2026 10:52:00 -0800 Subject: [PATCH 3/3] Update maven-shade-plugin to version 3.6.1 patch by Bernardo Botella Corbi; reviewed by Shailaja Koppu, Stefan Miklosovic, Doug Rohrer for CASSANDRA-21176 --- relocate-dependencies.pom | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/relocate-dependencies.pom b/relocate-dependencies.pom index 96d8d16ee9..a27e46cf16 100644 --- a/relocate-dependencies.pom +++ b/relocate-dependencies.pom @@ -67,7 +67,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.4.1 + 3.6.1 false