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));