diff --git a/test/distributed/org/apache/cassandra/distributed/test/cql3/StatefulASTBase.java b/test/distributed/org/apache/cassandra/distributed/test/cql3/StatefulASTBase.java
index cec98dcbea6..3f0d50b4e5e 100644
--- a/test/distributed/org/apache/cassandra/distributed/test/cql3/StatefulASTBase.java
+++ b/test/distributed/org/apache/cassandra/distributed/test/cql3/StatefulASTBase.java
@@ -49,6 +49,7 @@
import org.apache.cassandra.config.CassandraRelevantProperties;
import org.apache.cassandra.config.DatabaseDescriptor;
+import org.apache.cassandra.config.TestDatabaseDescriptor;
import org.apache.cassandra.cql3.KnownIssue;
import org.apache.cassandra.cql3.ast.Bind;
import org.apache.cassandra.cql3.ast.CQLFormatter;
@@ -442,7 +443,7 @@ protected BaseState(RandomSource rs, Cluster cluster, TableMetadata metadata)
createTable(metadata);
String sstableFormatName = this.sstableFormatName = Generators.toGen(CassandraGenerators.sstableFormatNames()).next(rs);
- cluster.forEach(i -> i.runOnInstance(() -> DatabaseDescriptor.setSelectedSSTableFormat(sstableFormatName)));
+ cluster.forEach(i -> i.runOnInstance(() -> TestDatabaseDescriptor.setUnsafeSelectedSSTableFormat(sstableFormatName)));
}
public boolean hasPartitions()
diff --git a/test/unit/org/apache/cassandra/config/TestDatabaseDescriptor.java b/test/unit/org/apache/cassandra/config/TestDatabaseDescriptor.java
new file mode 100644
index 00000000000..d7f9aaaf1dc
--- /dev/null
+++ b/test/unit/org/apache/cassandra/config/TestDatabaseDescriptor.java
@@ -0,0 +1,120 @@
+/*
+ * 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.config;
+
+import java.util.ArrayList;
+
+import org.apache.cassandra.db.ColumnFamilyStore;
+import org.apache.cassandra.db.compaction.CompactionManager;
+import org.apache.cassandra.io.sstable.format.SSTableFormat;
+
+import static org.apache.cassandra.utils.Throwables.maybeFail;
+import static org.apache.cassandra.utils.Throwables.merge;
+
+/**
+ * Test utilities for DatabaseDescriptor that require server-side classes.
+ * These methods are separated from DatabaseDescriptor to avoid loading
+ * server classes during initialization.
+ */
+public class TestDatabaseDescriptor
+{
+ /**
+ * Sets the global SSTable format after safely pausing all compactions.
+ *
+ * @param name the SSTable format name (e.g., "big" or "bti")
+ */
+ public static void setUnsafeSelectedSSTableFormat(String name)
+ {
+ SSTableFormat, ?> format = DatabaseDescriptor.getSSTableFormats().get(name);
+ if (format == null)
+ throw new IllegalArgumentException("Unknown sstable format: " + name);
+ setUnsafeSelectedSSTableFormat(format);
+ }
+
+ /**
+ * Sets the global SSTable format after safely pausing all compactions.
+ *
+ * This method:
+ * 1. Pauses global compactions
+ * 2. Pauses all table compaction strategies
+ * 3. Waits for in-flight compactions to complete
+ * 4. Changes the SSTable format
+ * 5. Resumes compactions
+ *
+ * @param format the SSTable format to set
+ */
+ public static void setUnsafeSelectedSSTableFormat(SSTableFormat, ?> format)
+ {
+ // Get all CFSs across all keyspaces since SSTable format is global
+ Iterable allCfs = ColumnFamilyStore.all();
+
+ // Pause both global compactions and all table compaction strategies
+ // This prevents NEW compactions from starting
+ try (CompactionManager.CompactionPauser globalPause = CompactionManager.instance.pauseGlobalCompaction();
+ CompactionManager.CompactionPauser strategiesPause = pauseAllCompactionStrategies(allCfs))
+ {
+ // Wait for all existing in-flight compactions to complete naturally (don't interrupt)
+ // Uses 1-minute timeout per waitForCessation implementation
+ CompactionManager.instance.waitForCessation(allCfs, sstable -> true);
+
+ // Now safe to change the global SSTable format
+ DatabaseDescriptor.setSelectedSSTableFormat(format);
+ }
+ // Compactions auto-resume when pausers are closed
+ }
+
+ /**
+ * Pauses compaction strategies for all given ColumnFamilyStores.
+ * Pattern matches {@link ColumnFamilyStore#pauseCompactionStrategies}.
+ */
+ private static CompactionManager.CompactionPauser pauseAllCompactionStrategies(Iterable toPause)
+ {
+ ArrayList successfullyPaused = new ArrayList<>();
+ try
+ {
+ for (ColumnFamilyStore cfs : toPause)
+ {
+ successfullyPaused.ensureCapacity(successfullyPaused.size() + 1); // to avoid OOM after pausing the strategies
+ cfs.getCompactionStrategyManager().pause();
+ successfullyPaused.add(cfs);
+ }
+ return () -> maybeFail(resumeAll(null, toPause));
+ }
+ catch (Throwable t)
+ {
+ resumeAll(t, successfullyPaused);
+ throw t;
+ }
+ }
+
+ private static Throwable resumeAll(Throwable accumulate, Iterable cfss)
+ {
+ for (ColumnFamilyStore cfs : cfss)
+ {
+ try
+ {
+ cfs.getCompactionStrategyManager().resume();
+ }
+ catch (Throwable t)
+ {
+ accumulate = merge(accumulate, t);
+ }
+ }
+ return accumulate;
+ }
+}
diff --git a/test/unit/org/apache/cassandra/cql3/RandomSchemaTest.java b/test/unit/org/apache/cassandra/cql3/RandomSchemaTest.java
index c4c8815866d..f137bdd9341 100644
--- a/test/unit/org/apache/cassandra/cql3/RandomSchemaTest.java
+++ b/test/unit/org/apache/cassandra/cql3/RandomSchemaTest.java
@@ -37,6 +37,7 @@
import org.apache.cassandra.config.CassandraRelevantProperties;
import org.apache.cassandra.config.DatabaseDescriptor;
+import org.apache.cassandra.config.TestDatabaseDescriptor;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.compaction.CursorCompactor;
import org.apache.cassandra.io.sstable.format.SSTableFormat;
@@ -79,7 +80,7 @@ public void test()
resetSchema();
// TODO : when table level override of sstable format is allowed, migrate to that
- if (!STRESS_CURSOR_COMPACTION) DatabaseDescriptor.setSelectedSSTableFormat(sstableFormatGen.generate(random));
+ if (!STRESS_CURSOR_COMPACTION) TestDatabaseDescriptor.setUnsafeSelectedSSTableFormat(sstableFormatGen.generate(random));
Gen udtName = Generators.unique(IDENTIFIER_GEN);
diff --git a/test/unit/org/apache/cassandra/db/virtual/PartitionKeyStatsTableTest.java b/test/unit/org/apache/cassandra/db/virtual/PartitionKeyStatsTableTest.java
index e78c46ebb3c..e61a3b17ad6 100644
--- a/test/unit/org/apache/cassandra/db/virtual/PartitionKeyStatsTableTest.java
+++ b/test/unit/org/apache/cassandra/db/virtual/PartitionKeyStatsTableTest.java
@@ -41,7 +41,7 @@
import org.junit.runners.Parameterized.Parameters;
import org.apache.cassandra.Util;
-import org.apache.cassandra.config.DatabaseDescriptor;
+import org.apache.cassandra.config.TestDatabaseDescriptor;
import org.apache.cassandra.cql3.CQLTester;
import org.apache.cassandra.dht.Murmur3Partitioner;
import org.apache.cassandra.io.sstable.format.bti.BtiFormat;
@@ -73,7 +73,7 @@ public PartitionKeyStatsTableTest(boolean useBtiFormat)
public void before()
{
if (useBtiFormat)
- DatabaseDescriptor.setSelectedSSTableFormat(new BtiFormat.BtiFormatFactory().getInstance(Collections.emptyMap()));
+ TestDatabaseDescriptor.setUnsafeSelectedSSTableFormat(new BtiFormat.BtiFormatFactory().getInstance(Collections.emptyMap()));
PartitionKeyStatsTable primaryIdTable = new PartitionKeyStatsTable(KS_NAME);
scanned = new AtomicInteger();
diff --git a/test/unit/org/apache/cassandra/repair/autorepair/RepairTokenRangeSplitterTest.java b/test/unit/org/apache/cassandra/repair/autorepair/RepairTokenRangeSplitterTest.java
index 989573824ed..2383a1eb680 100644
--- a/test/unit/org/apache/cassandra/repair/autorepair/RepairTokenRangeSplitterTest.java
+++ b/test/unit/org/apache/cassandra/repair/autorepair/RepairTokenRangeSplitterTest.java
@@ -38,6 +38,7 @@
import org.apache.cassandra.auth.AuthKeyspace;
import org.apache.cassandra.config.DataStorageSpec.LongMebibytesBound;
import org.apache.cassandra.config.DatabaseDescriptor;
+import org.apache.cassandra.config.TestDatabaseDescriptor;
import org.apache.cassandra.cql3.CQLTester;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.lifecycle.SSTableSet;
@@ -94,7 +95,7 @@ public static void setUpClass()
public void setUp()
{
AutoRepairService.instance.getAutoRepairConfig().setRepairByKeyspace(RepairType.FULL, true);
- DatabaseDescriptor.setSelectedSSTableFormat(DatabaseDescriptor.getSSTableFormats().get(sstableFormat));
+ TestDatabaseDescriptor.setUnsafeSelectedSSTableFormat(DatabaseDescriptor.getSSTableFormats().get(sstableFormat));
repairRangeSplitter = new RepairTokenRangeSplitter(RepairType.FULL, Collections.emptyMap());
tableName = createTable("CREATE TABLE %s (k INT PRIMARY KEY, v INT)");
// ensure correct format is selected.
diff --git a/test/unit/org/apache/cassandra/service/accord/AccordKeyspaceTest.java b/test/unit/org/apache/cassandra/service/accord/AccordKeyspaceTest.java
index cb3c6147c58..b467edce537 100644
--- a/test/unit/org/apache/cassandra/service/accord/AccordKeyspaceTest.java
+++ b/test/unit/org/apache/cassandra/service/accord/AccordKeyspaceTest.java
@@ -75,7 +75,7 @@
import static accord.local.Command.Committed.committed;
import static accord.utils.Property.qt;
import static org.apache.cassandra.config.DatabaseDescriptor.getPartitioner;
-import static org.apache.cassandra.config.DatabaseDescriptor.setSelectedSSTableFormat;
+import static org.apache.cassandra.config.TestDatabaseDescriptor.setUnsafeSelectedSSTableFormat;
import static org.apache.cassandra.db.ColumnFamilyStore.FlushReason.UNIT_TESTS;
import static org.apache.cassandra.distributed.test.log.ClusterMetadataTestHelper.setMemtable;
import static org.apache.cassandra.schema.SchemaConstants.ACCORD_KEYSPACE_NAME;
@@ -147,7 +147,7 @@ public void findOverlappingKeys()
qt().check(rs -> {
AccordKeyspace.unsafeClear();
// control SSTable format
- setSelectedSSTableFormat(sstableFormats.get(rs.pick(sstableFormatNames)));
+ setUnsafeSelectedSSTableFormat(sstableFormats.get(rs.pick(sstableFormatNames)));
// control memtable format
setMemtable(ACCORD_KEYSPACE_NAME, "commands_for_key", rs.pick(memtableFormats));