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

jchovatia 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 077b7ebe22 No need to evict already prepared statements, as it creates 
a race condition between multiple threads
077b7ebe22 is described below

commit 077b7ebe22c750b9f0c4a83f7979356b5095d6a5
Author: jaydeepkumar1984 <[email protected]>
AuthorDate: Mon Dec 22 15:49:35 2025 -0800

    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 +
 .../org/apache/cassandra/cql3/QueryProcessor.java  |  5 +-
 .../miscellaneous/PreparedStatementTest.java       | 85 ++++++++++++++++++++++
 3 files changed, 90 insertions(+), 1 deletion(-)

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());
+    }
+}


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

Reply via email to