Fix simulator after rebase

* avoid running python3-dependent tasks when running simulator tasks
  * fix a problem with simulated snitch rebase
  * add a distinction between short-lived daemon threads and infinite loop ones for cases when we need to simulate user-implemented infinite loops

Patch by Alex Petrov; reviewed by Benedict Elliott Smith for CASSANDRA-20542
This commit is contained in:
Alex Petrov 2025-04-08 14:15:17 +02:00 committed by David Capwell
parent 49740f87d2
commit 68aea4b15f
19 changed files with 149 additions and 100 deletions

View File

@ -18,15 +18,15 @@
package org.apache.cassandra.concurrent; package org.apache.cassandra.concurrent;
import org.apache.cassandra.concurrent.InfiniteLoopExecutor.Daemon;
import org.apache.cassandra.concurrent.InfiniteLoopExecutor.Interrupts; import org.apache.cassandra.concurrent.InfiniteLoopExecutor.Interrupts;
import org.apache.cassandra.concurrent.InfiniteLoopExecutor.SimulatorSafe; import org.apache.cassandra.concurrent.InfiniteLoopExecutor.SimulatorSafe;
import org.apache.cassandra.utils.JVMStabilityInspector; import org.apache.cassandra.utils.JVMStabilityInspector;
import org.apache.cassandra.utils.Shared; import org.apache.cassandra.utils.Shared;
import static java.lang.Thread.*; import static java.lang.Thread.NORM_PRIORITY;
import static java.lang.Thread.UncaughtExceptionHandler;
import static org.apache.cassandra.concurrent.ExecutorFactory.SimulatorSemantics.NORMAL; import static org.apache.cassandra.concurrent.ExecutorFactory.SimulatorSemantics.NORMAL;
import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.Daemon.DAEMON; import static org.apache.cassandra.concurrent.ExecutorFactory.SystemThreadTag.DAEMON;
import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.Interrupts.UNSYNCHRONIZED; import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.Interrupts.UNSYNCHRONIZED;
import static org.apache.cassandra.concurrent.NamedThreadFactory.createThread; import static org.apache.cassandra.concurrent.NamedThreadFactory.createThread;
import static org.apache.cassandra.concurrent.NamedThreadFactory.setupThread; import static org.apache.cassandra.concurrent.NamedThreadFactory.setupThread;
@ -78,6 +78,17 @@ public interface ExecutorFactory extends ExecutorBuilderFactory.Jmxable<Executor
NORMAL, DISCARD NORMAL, DISCARD
} }
/// Simulator Tag specifies the nature of the created thread:
/// - JOB threads are short-lived and simulation treats them as sub tasks of the task that creates them,
/// so that the strictly ordered property of the simulator ensures the thread terminates before the next
/// task of its parent is scheduled.
/// - DAEMON threads are treated as background tasks, and are neither linked to their parent task or the Work phase that creates them.
/// - INFINITE_LOOP threads detach from their parent task as they are expected to run forever, but unlike DAEMON threads must have
/// no active work for a given Work phase to complete.
public enum SimulatorThreadTag { JOB, DAEMON, INFINITE_LOOP }
public enum SystemThreadTag { DAEMON, NON_DAEMON }
/** /**
* @return a factory that configures executors that propagate {@link ExecutorLocals} to the executing thread * @return a factory that configures executors that propagate {@link ExecutorLocals} to the executing thread
*/ */
@ -124,10 +135,11 @@ public interface ExecutorFactory extends ExecutorBuilderFactory.Jmxable<Executor
* Create and start a new thread to execute {@code runnable} * Create and start a new thread to execute {@code runnable}
* @param name the name of the thread * @param name the name of the thread
* @param runnable the task to execute * @param runnable the task to execute
* @param daemon flag to indicate whether the thread should be a daemon or not * @param systemTag flag to indicate whether the loop thread should be a daemon thread or not
* @param simulatorTag flag to indicate the nature of the specific thread to help simulate it
* @return the new thread * @return the new thread
*/ */
Thread startThread(String name, Runnable runnable, Daemon daemon); Thread startThread(String name, Runnable runnable, SystemThreadTag systemTag, SimulatorThreadTag simulatorTag);
/** /**
* Create and start a new thread to execute {@code runnable}; this thread will be a daemon thread. * Create and start a new thread to execute {@code runnable}; this thread will be a daemon thread.
@ -137,7 +149,7 @@ public interface ExecutorFactory extends ExecutorBuilderFactory.Jmxable<Executor
*/ */
default Thread startThread(String name, Runnable runnable) default Thread startThread(String name, Runnable runnable)
{ {
return startThread(name, runnable, DAEMON); return startThread(name, runnable, DAEMON, SimulatorThreadTag.JOB);
} }
/** /**
@ -148,14 +160,14 @@ public interface ExecutorFactory extends ExecutorBuilderFactory.Jmxable<Executor
* @param name the name of the thread used to invoke the task repeatedly * @param name the name of the thread used to invoke the task repeatedly
* @param task the task to execute repeatedly * @param task the task to execute repeatedly
* @param simulatorSafe flag indicating if the loop thread can be intercepted / rescheduled during cluster simulation * @param simulatorSafe flag indicating if the loop thread can be intercepted / rescheduled during cluster simulation
* @param daemon flag to indicate whether the loop thread should be a daemon thread or not * @param systemTag flag to indicate whether the loop thread should be a daemon thread or not
* @param interrupts flag to indicate whether to synchronize interrupts of the task execution thread * @param interrupts flag to indicate whether to synchronize interrupts of the task execution thread
* using the task's monitor this can be used to prevent interruption while performing * using the task's monitor this can be used to prevent interruption while performing
* IO operations which forbid interrupted threads. * IO operations which forbid interrupted threads.
* See: {@link org.apache.cassandra.db.commitlog.AbstractCommitLogSegmentManager#start} * See: {@link org.apache.cassandra.db.commitlog.AbstractCommitLogSegmentManager#start}
* @return the new thread * @return the new thread
*/ */
Interruptible infiniteLoop(String name, Interruptible.Task task, SimulatorSafe simulatorSafe, Daemon daemon, Interrupts interrupts); Interruptible infiniteLoop(String name, Interruptible.Task task, SimulatorSafe simulatorSafe, SystemThreadTag systemTag, Interrupts interrupts);
/** /**
* Create and start a new InfiniteLoopExecutor to repeatedly invoke {@code runnable}. * Create and start a new InfiniteLoopExecutor to repeatedly invoke {@code runnable}.
@ -291,9 +303,9 @@ public interface ExecutorFactory extends ExecutorBuilderFactory.Jmxable<Executor
} }
@Override @Override
public Thread startThread(String name, Runnable runnable, Daemon daemon) public Thread startThread(String name, Runnable runnable, SystemThreadTag systemTag, SimulatorThreadTag simulatorTag)
{ {
Thread thread = setupThread(createThread(threadGroup, runnable, name, daemon == DAEMON), Thread thread = setupThread(createThread(threadGroup, runnable, name, systemTag == DAEMON),
Thread.NORM_PRIORITY, Thread.NORM_PRIORITY,
contextClassLoader, contextClassLoader,
uncaughtExceptionHandler); uncaughtExceptionHandler);
@ -302,9 +314,9 @@ public interface ExecutorFactory extends ExecutorBuilderFactory.Jmxable<Executor
} }
@Override @Override
public Interruptible infiniteLoop(String name, Interruptible.Task task, SimulatorSafe simulatorSafe, Daemon daemon, Interrupts interrupts) public Interruptible infiniteLoop(String name, Interruptible.Task task, SimulatorSafe simulatorSafe, SystemThreadTag systemTag, Interrupts interrupts)
{ {
return new InfiniteLoopExecutor(this, name, task, daemon, interrupts); return new InfiniteLoopExecutor(this, name, task, systemTag, interrupts);
} }
@Override @Override

View File

@ -28,6 +28,8 @@ import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.apache.cassandra.concurrent.ExecutorFactory.SimulatorThreadTag;
import org.apache.cassandra.concurrent.ExecutorFactory.SystemThreadTag;
import org.apache.cassandra.utils.Shared; import org.apache.cassandra.utils.Shared;
import org.apache.cassandra.utils.concurrent.Condition; import org.apache.cassandra.utils.concurrent.Condition;
import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException; import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException;
@ -52,14 +54,6 @@ public class InfiniteLoopExecutor implements Interruptible
@Shared(scope = Shared.Scope.SIMULATION) @Shared(scope = Shared.Scope.SIMULATION)
public enum SimulatorSafe { SAFE, UNSAFE } public enum SimulatorSafe { SAFE, UNSAFE }
/**
* Does this loop always block on some external work provision that is going to be simulator-controlled, or does
* it loop periodically? If the latter, it may prevent simulation making progress between phases, and should be
* marked as a DAEMON process.
*/
@Shared(scope = Shared.Scope.SIMULATION)
public enum Daemon { DAEMON, NON_DAEMON }
@Shared(scope = Shared.Scope.SIMULATION) @Shared(scope = Shared.Scope.SIMULATION)
public enum Interrupts { SYNCHRONIZED, UNSYNCHRONIZED } public enum Interrupts { SYNCHRONIZED, UNSYNCHRONIZED }
@ -70,20 +64,20 @@ public class InfiniteLoopExecutor implements Interruptible
private final Consumer<Thread> interruptHandler; private final Consumer<Thread> interruptHandler;
private final Condition isTerminated = newOneTimeCondition(); private final Condition isTerminated = newOneTimeCondition();
public InfiniteLoopExecutor(String name, Task task, Daemon daemon) public InfiniteLoopExecutor(String name, Task task, SystemThreadTag systemTag)
{ {
this(ExecutorFactory.Global.executorFactory(), name, task, daemon, UNSYNCHRONIZED); this(ExecutorFactory.Global.executorFactory(), name, task, systemTag, UNSYNCHRONIZED);
} }
public InfiniteLoopExecutor(ExecutorFactory factory, String name, Task task, Daemon daemon) public InfiniteLoopExecutor(ExecutorFactory factory, String name, Task task, SystemThreadTag systemTag)
{ {
this(factory, name, task, daemon, UNSYNCHRONIZED); this(factory, name, task, systemTag, UNSYNCHRONIZED);
} }
public InfiniteLoopExecutor(ExecutorFactory factory, String name, Task task, Daemon daemon, Interrupts interrupts) public InfiniteLoopExecutor(ExecutorFactory factory, String name, Task task, SystemThreadTag systemTag, Interrupts interrupts)
{ {
this.task = task; this.task = task;
this.thread = factory.startThread(name, this::loop, daemon); this.thread = factory.startThread(name, this::loop, systemTag, SimulatorThreadTag.INFINITE_LOOP);
this.interruptHandler = interrupts == SYNCHRONIZED this.interruptHandler = interrupts == SYNCHRONIZED
? interruptHandler(task) ? interruptHandler(task)
: Thread::interrupt; : Thread::interrupt;

View File

@ -56,7 +56,7 @@ import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException;
import org.apache.cassandra.utils.concurrent.WaitQueue; import org.apache.cassandra.utils.concurrent.WaitQueue;
import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory;
import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.Daemon.NON_DAEMON; import static org.apache.cassandra.concurrent.ExecutorFactory.SystemThreadTag.NON_DAEMON;
import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.Interrupts.SYNCHRONIZED; import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.Interrupts.SYNCHRONIZED;
import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.SimulatorSafe.SAFE; import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.SimulatorSafe.SAFE;
import static org.apache.cassandra.db.commitlog.CommitLogSegment.Allocation; import static org.apache.cassandra.db.commitlog.CommitLogSegment.Allocation;

View File

@ -38,7 +38,7 @@ import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.TimeUnit.NANOSECONDS;
import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory;
import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.Daemon.NON_DAEMON; import static org.apache.cassandra.concurrent.ExecutorFactory.SystemThreadTag.NON_DAEMON;
import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.Interrupts.SYNCHRONIZED; import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.Interrupts.SYNCHRONIZED;
import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.SimulatorSafe.SAFE; import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.SimulatorSafe.SAFE;
import static org.apache.cassandra.concurrent.Interruptible.State.NORMAL; import static org.apache.cassandra.concurrent.Interruptible.State.NORMAL;

View File

@ -39,7 +39,7 @@ import static java.lang.String.format;
import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.TimeUnit.NANOSECONDS;
import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory;
import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.Daemon.NON_DAEMON; import static org.apache.cassandra.concurrent.ExecutorFactory.SystemThreadTag.NON_DAEMON;
import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.Interrupts.SYNCHRONIZED; import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.Interrupts.SYNCHRONIZED;
import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.SimulatorSafe.SAFE; import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.SimulatorSafe.SAFE;
import static org.apache.cassandra.concurrent.Interruptible.State.NORMAL; import static org.apache.cassandra.concurrent.Interruptible.State.NORMAL;

View File

@ -64,7 +64,7 @@ import org.jctools.queues.MpscUnboundedArrayQueue;
import static java.lang.String.format; import static java.lang.String.format;
import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory;
import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.Daemon.NON_DAEMON; import static org.apache.cassandra.concurrent.ExecutorFactory.SystemThreadTag.NON_DAEMON;
import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.Interrupts.SYNCHRONIZED; import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.Interrupts.SYNCHRONIZED;
import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.SimulatorSafe.SAFE; import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.SimulatorSafe.SAFE;
import static org.apache.cassandra.concurrent.Interruptible.State.NORMAL; import static org.apache.cassandra.concurrent.Interruptible.State.NORMAL;

View File

@ -24,11 +24,14 @@ import java.util.function.Function;
import java.util.function.IntFunction; import java.util.function.IntFunction;
import accord.utils.Invariants; import accord.utils.Invariants;
import io.netty.util.collection.LongObjectHashMap; import io.netty.util.collection.LongObjectHashMap;
import org.apache.cassandra.service.accord.AccordExecutor.Mode; import org.apache.cassandra.service.accord.AccordExecutor.Mode;
import org.apache.cassandra.utils.concurrent.Condition; import org.apache.cassandra.utils.concurrent.Condition;
import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory; import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory;
import static org.apache.cassandra.concurrent.ExecutorFactory.SimulatorThreadTag.INFINITE_LOOP;
import static org.apache.cassandra.concurrent.ExecutorFactory.SystemThreadTag.NON_DAEMON;
import static org.apache.cassandra.service.accord.AccordExecutor.Mode.RUN_WITH_LOCK; import static org.apache.cassandra.service.accord.AccordExecutor.Mode.RUN_WITH_LOCK;
import static org.apache.cassandra.utils.Clock.Global.nanoTime; import static org.apache.cassandra.utils.Clock.Global.nanoTime;
@ -46,7 +49,7 @@ class AccordExecutorLoops
loops = new LongObjectHashMap<>(threads); loops = new LongObjectHashMap<>(threads);
for (int i = 0; i < threads; ++i) for (int i = 0; i < threads; ++i)
{ {
Thread thread = executorFactory().startThread(name.apply(i), wrap(loopFactory.apply(mode))); Thread thread = executorFactory().startThread(name.apply(i), wrap(loopFactory.apply(mode)), NON_DAEMON, INFINITE_LOOP);
Thread conflict = loops.putIfAbsent(thread.getId(), thread); Thread conflict = loops.putIfAbsent(thread.getId(), thread);
Invariants.require(conflict == null || !conflict.isAlive(), "Allocated two threads with the same threadId!"); Invariants.require(conflict == null || !conflict.isAlive(), "Allocated two threads with the same threadId!");
} }

View File

@ -70,7 +70,7 @@ import org.apache.cassandra.utils.JVMStabilityInspector;
import org.apache.cassandra.utils.concurrent.Condition; import org.apache.cassandra.utils.concurrent.Condition;
import org.apache.cassandra.utils.concurrent.WaitQueue; import org.apache.cassandra.utils.concurrent.WaitQueue;
import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.Daemon.NON_DAEMON; import static org.apache.cassandra.concurrent.ExecutorFactory.SystemThreadTag.NON_DAEMON;
import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.Interrupts.UNSYNCHRONIZED; import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.Interrupts.UNSYNCHRONIZED;
import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.SimulatorSafe.SAFE; import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.SimulatorSafe.SAFE;
import static org.apache.cassandra.tcm.Epoch.EMPTY; import static org.apache.cassandra.tcm.Epoch.EMPTY;

View File

@ -784,7 +784,8 @@ public class ClusterSimulation<S extends Simulation> implements AutoCloseable
.set("failure_detector", SimulatedFailureDetector.Instance.class.getName()) .set("failure_detector", SimulatedFailureDetector.Instance.class.getName())
.set("commitlog_compression", new ParameterizedClass(LZ4Compressor.class.getName(), emptyMap())) .set("commitlog_compression", new ParameterizedClass(LZ4Compressor.class.getName(), emptyMap()))
.set("commitlog_sync", "batch") .set("commitlog_sync", "batch")
.set("accord.journal.flush_mode", "BATCH"); .set("accord.journal.flush_mode", "BATCH")
.set("accord.command_store_shard_count", "4");
// TODO: Add remove() to IInstanceConfig // TODO: Add remove() to IInstanceConfig
if (config instanceof InstanceConfig) if (config instanceof InstanceConfig)
{ {

View File

@ -23,14 +23,20 @@ import java.util.concurrent.atomic.AtomicInteger;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.airlift.airline.Cli; import io.airlift.airline.Cli;
import io.airlift.airline.Command; import io.airlift.airline.Command;
import org.apache.cassandra.config.CassandraRelevantProperties; import org.apache.cassandra.config.CassandraRelevantProperties;
import org.apache.cassandra.simulator.SimulationRunner; import org.apache.cassandra.simulator.SimulationRunner;
import org.apache.cassandra.simulator.SimulatorUtils;
import org.apache.cassandra.utils.StorageCompatibilityMode; import org.apache.cassandra.utils.StorageCompatibilityMode;
public class AccordSimulationRunner extends SimulationRunner public class AccordSimulationRunner extends SimulationRunner
{ {
private static Logger logger = LoggerFactory.getLogger(AccordSimulationRunner.class);
@BeforeClass @BeforeClass
public static void beforeAll() public static void beforeAll()
{ {
@ -86,6 +92,7 @@ public class AccordSimulationRunner extends SimulationRunner
*/ */
public static void main(String[] args) throws IOException public static void main(String[] args) throws IOException
{ {
SimulatorUtils.verifyAndlogSimulatorArgs(logger, args);
AccordClusterSimulation.Builder builder = new AccordClusterSimulation.Builder(); AccordClusterSimulation.Builder builder = new AccordClusterSimulation.Builder();
builder.unique(uniqueNum.getAndIncrement()); builder.unique(uniqueNum.getAndIncrement());

View File

@ -30,13 +30,13 @@ import java.util.function.Consumer;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import accord.utils.UnhandledEnum;
import io.netty.util.concurrent.FastThreadLocal; import io.netty.util.concurrent.FastThreadLocal;
import org.apache.cassandra.concurrent.ExecutorBuilder; import org.apache.cassandra.concurrent.ExecutorBuilder;
import org.apache.cassandra.concurrent.ExecutorBuilderFactory; import org.apache.cassandra.concurrent.ExecutorBuilderFactory;
import org.apache.cassandra.concurrent.ExecutorFactory; import org.apache.cassandra.concurrent.ExecutorFactory;
import org.apache.cassandra.concurrent.ExecutorPlus; import org.apache.cassandra.concurrent.ExecutorPlus;
import org.apache.cassandra.concurrent.InfiniteLoopExecutor; import org.apache.cassandra.concurrent.InfiniteLoopExecutor;
import org.apache.cassandra.concurrent.InfiniteLoopExecutor.Daemon;
import org.apache.cassandra.concurrent.InfiniteLoopExecutor.Interrupts; import org.apache.cassandra.concurrent.InfiniteLoopExecutor.Interrupts;
import org.apache.cassandra.concurrent.InfiniteLoopExecutor.SimulatorSafe; import org.apache.cassandra.concurrent.InfiniteLoopExecutor.SimulatorSafe;
import org.apache.cassandra.concurrent.Interruptible.Task; import org.apache.cassandra.concurrent.Interruptible.Task;
@ -69,6 +69,8 @@ import org.apache.cassandra.utils.WithResources;
import org.apache.cassandra.utils.concurrent.RunnableFuture; import org.apache.cassandra.utils.concurrent.RunnableFuture;
import static org.apache.cassandra.simulator.systems.SimulatedAction.Kind.INFINITE_LOOP; import static org.apache.cassandra.simulator.systems.SimulatedAction.Kind.INFINITE_LOOP;
import static org.apache.cassandra.simulator.systems.SimulatedAction.Kind.SCHEDULED_DAEMON;
import static org.apache.cassandra.simulator.systems.SimulatedAction.Kind.THREAD;
public class InterceptingExecutorFactory implements ExecutorFactory, Closeable public class InterceptingExecutorFactory implements ExecutorFactory, Closeable
{ {
@ -327,9 +329,18 @@ public class InterceptingExecutorFactory implements ExecutorFactory, Closeable
return configurePooled(name, threads).build(); return configurePooled(name, threads).build();
} }
public Thread startThread(String name, Runnable runnable, Daemon daemon) public Thread startThread(String name, Runnable runnable, SystemThreadTag systemTag, SimulatorThreadTag simulatorTag)
{ {
return simulatedExecution.intercept().start(SimulatedAction.Kind.THREAD, factory(name)::newThread, runnable); SimulatedAction.Kind kind;
switch (simulatorTag)
{
default: throw UnhandledEnum.unknown(simulatorTag);
case INFINITE_LOOP: kind = INFINITE_LOOP; break;
case JOB: kind = THREAD; break;
case DAEMON: kind = SCHEDULED_DAEMON; break;
}
return simulatedExecution.intercept().start(kind, factory(name)::newThread, runnable);
} }
@VisibleForTesting @VisibleForTesting
@ -341,7 +352,7 @@ public class InterceptingExecutorFactory implements ExecutorFactory, Closeable
} }
@Override @Override
public Interruptible infiniteLoop(String name, Task task, SimulatorSafe simulatorSafe, Daemon daemon, Interrupts interrupts) public Interruptible infiniteLoop(String name, Task task, SimulatorSafe simulatorSafe, SystemThreadTag systemTag, Interrupts interrupts)
{ {
if (simulatorSafe != SimulatorSafe.SAFE) if (simulatorSafe != SimulatorSafe.SAFE)
{ {

View File

@ -28,49 +28,18 @@ import java.util.stream.IntStream;
import org.apache.cassandra.distributed.Cluster; import org.apache.cassandra.distributed.Cluster;
import org.apache.cassandra.distributed.api.IInstanceConfig; import org.apache.cassandra.distributed.api.IInstanceConfig;
import org.apache.cassandra.locator.*; import org.apache.cassandra.locator.Endpoint;
import org.apache.cassandra.locator.IEndpointSnitch;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.locator.Replica;
import org.apache.cassandra.locator.ReplicaCollection;
import org.apache.cassandra.simulator.cluster.NodeLookup; import org.apache.cassandra.simulator.cluster.NodeLookup;
import org.apache.cassandra.utils.Sortable; import org.apache.cassandra.utils.Sortable;
public class SimulatedSnitch extends NodeLookup public class SimulatedSnitch extends NodeLookup
{ {
private static class SimulatedProximity implements NodeProximity
{
@Override
public <C extends ReplicaCollection<? extends C>> C sortedByProximity(InetAddressAndPort address, C addresses)
{
return addresses.sorted(Comparator.comparingInt(SimulatedSnitch::asInt));
}
@Override
public int compareEndpoints(InetAddressAndPort target, Replica r1, Replica r2)
{
return Comparator.comparingInt(SimulatedSnitch::asInt).compare(r1, r2);
}
@Override
public boolean isWorthMergingForRangeQuery(ReplicaCollection<?> merged, ReplicaCollection<?> l1, ReplicaCollection<?> l2)
{
return false;
}
@Override
public boolean supportCompareByEndpoint()
{
return true;
}
@Override
public <C extends Sortable<? extends Endpoint, ? extends C>> Comparator<Endpoint> endpointComparator(InetAddressAndPort address, C addresses)
{
return Comparator.comparingInt(SimulatedSnitch::asInt);
}
}
public static class Instance implements IEndpointSnitch public static class Instance implements IEndpointSnitch
{ {
private final NodeProximity proximity = new SimulatedProximity();
private static volatile Function<InetSocketAddress, String> LOOKUP_DC; private static volatile Function<InetSocketAddress, String> LOOKUP_DC;
public String getRack(InetAddressAndPort endpoint) public String getRack(InetAddressAndPort endpoint)
@ -85,12 +54,12 @@ public class SimulatedSnitch extends NodeLookup
public <C extends ReplicaCollection<? extends C>> C sortedByProximity(InetAddressAndPort address, C addresses) public <C extends ReplicaCollection<? extends C>> C sortedByProximity(InetAddressAndPort address, C addresses)
{ {
return proximity.sortedByProximity(address, addresses); return addresses.sorted(Comparator.comparingInt(SimulatedSnitch::asInt));
} }
public int compareEndpoints(InetAddressAndPort target, Replica r1, Replica r2) public int compareEndpoints(InetAddressAndPort target, Replica r1, Replica r2)
{ {
return proximity.compareEndpoints(target, r1, r2); return Comparator.comparingInt(SimulatedSnitch::asInt).compare(r1, r2);
} }
public void gossiperStarting() public void gossiperStarting()
@ -99,13 +68,25 @@ public class SimulatedSnitch extends NodeLookup
public boolean isWorthMergingForRangeQuery(ReplicaCollection<?> merged, ReplicaCollection<?> l1, ReplicaCollection<?> l2) public boolean isWorthMergingForRangeQuery(ReplicaCollection<?> merged, ReplicaCollection<?> l1, ReplicaCollection<?> l2)
{ {
return proximity.isWorthMergingForRangeQuery(merged, l1, l2); return false;
} }
public static void setup(Function<InetSocketAddress, String> lookupDc) public static void setup(Function<InetSocketAddress, String> lookupDc)
{ {
LOOKUP_DC = lookupDc; LOOKUP_DC = lookupDc;
} }
@Override
public boolean supportCompareByEndpoint()
{
return true;
}
@Override
public <C extends Sortable<? extends Endpoint, ? extends C>> Comparator<Endpoint> endpointComparator(InetAddressAndPort address, C addresses)
{
return Comparator.comparingInt(SimulatedSnitch::asInt);
}
} }
final int[] numInDcs; final int[] numInDcs;

View File

@ -24,6 +24,7 @@ import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.apache.cassandra.distributed.api.ConsistencyLevel;
import org.apache.cassandra.harry.SchemaSpec; import org.apache.cassandra.harry.SchemaSpec;
import org.apache.cassandra.harry.gen.Generator; import org.apache.cassandra.harry.gen.Generator;
import org.apache.cassandra.harry.gen.SchemaGenerators; import org.apache.cassandra.harry.gen.SchemaGenerators;
@ -62,6 +63,11 @@ public class AccordHarrySimulationTest extends HarrySimulatorTest
return schedulers; return schedulers;
} }
protected ConsistencyLevel validateQueryConsistency()
{
return ConsistencyLevel.QUORUM;
}
public Generator<SchemaSpec> schemaSpecGen(String keyspace, String prefix) public Generator<SchemaSpec> schemaSpecGen(String keyspace, String prefix)
{ {
return SchemaGenerators.schemaSpecGen(keyspace, prefix, 1000, SchemaSpec.optionsBuilder().withTransactionalMode(TransactionalMode.full)); return SchemaGenerators.schemaSpecGen(keyspace, prefix, 1000, SchemaSpec.optionsBuilder().withTransactionalMode(TransactionalMode.full));

View File

@ -288,7 +288,7 @@ public class HarrySimulatorTest
work.add(interleave("Start generating", HarrySimulatorTest.generateWrites(rowsPerPhase, simulation, cl))); work.add(interleave("Start generating", HarrySimulatorTest.generateWrites(rowsPerPhase, simulation, cl)));
work.add(work("Validate all data locally", work.add(work("Validate all data locally",
lazy(() -> validateAllLocal(simulation, simulation.nodeState.ring, rf)))); lazy(() -> validateAllLocal(simulation, simulation.nodeState.ring, rf, validateQueryConsistency(), simulation.rng))));
return arr(work.toArray(new ActionSchedule.Work[0])); return arr(work.toArray(new ActionSchedule.Work[0]));
}, },
@ -339,8 +339,8 @@ public class HarrySimulatorTest
run(() -> simulation.nodeState.decommission(node)))); run(() -> simulation.nodeState.decommission(node))));
work.add(work("Check node state", assertNodeState(simulation.simulated, simulation.cluster, node, NodeState.LEFT))); work.add(work("Check node state", assertNodeState(simulation.simulated, simulation.cluster, node, NodeState.LEFT)));
} }
work.add(work("Validate data locally", work.add(work("Validate data with " + validateQueryConsistency(),
lazy(() -> validateAllLocal(simulation, simulation.nodeState.ring, rf)))); lazy(() -> validateAllLocal(simulation, simulation.nodeState.ring, rf, validateQueryConsistency(), simulation.rng))));
boolean tmp = shouldBootstrap; boolean tmp = shouldBootstrap;
work.add(work("Output message", work.add(work("Output message",
run(() -> logger.warn("Finished {} of {} and data validation!\n", tmp ? "bootstrap" : "decommission", node)))); run(() -> logger.warn("Finished {} of {} and data validation!\n", tmp ? "bootstrap" : "decommission", node))));
@ -846,7 +846,7 @@ public class HarrySimulatorTest
* Given you have used `generate` methods to generate data with Harry, you can use this method to check whether all * Given you have used `generate` methods to generate data with Harry, you can use this method to check whether all
* data has been propagated everywhere it should be, be it via streaming, read repairs, or regular writes. * data has been propagated everywhere it should be, be it via streaming, read repairs, or regular writes.
*/ */
public static Action validateAllLocal(HarrySimulation simulation, List<TokenPlacementModel.Node> owernship, TokenPlacementModel.ReplicationFactor rf) public static Action validateAllLocal(HarrySimulation simulation, List<TokenPlacementModel.Node> owernship, TokenPlacementModel.ReplicationFactor rf, ConsistencyLevel consistencyLevel, EntropySource rng)
{ {
return new Actions.LambdaAction("Validate", Action.Modifiers.RELIABLE_NO_TIMEOUTS, return new Actions.LambdaAction("Validate", Action.Modifiers.RELIABLE_NO_TIMEOUTS,
() -> { () -> {
@ -861,12 +861,19 @@ public class HarrySimulatorTest
Operations.SelectPartition select = new Operations.SelectPartition(Long.MAX_VALUE, pd); Operations.SelectPartition select = new Operations.SelectPartition(Long.MAX_VALUE, pd);
actions.add(new HarryValidatingQuery(simulation, simulation.cluster, rf, actions.add(new HarryValidatingQuery(simulation, simulation.cluster, rf,
owernship, new Visit(Long.MAX_VALUE, new Operations.Operation[]{ select }), owernship, new Visit(Long.MAX_VALUE, new Operations.Operation[]{ select }),
simulation.queryBuilder)); simulation.queryBuilder,
consistencyLevel,
rng));
} }
return ActionList.of(actions).setStrictlySequential(); return ActionList.of(actions).setStrictlySequential();
}); });
} }
protected ConsistencyLevel validateQueryConsistency()
{
return ConsistencyLevel.NODE_LOCAL;
}
private static Set<Long> visitedPds(HarrySimulation simulation) private static Set<Long> visitedPds(HarrySimulation simulation)
{ {
Set<Long> pds = new HashSet<>(); Set<Long> pds = new HashSet<>();

View File

@ -25,7 +25,9 @@ import org.slf4j.LoggerFactory;
import accord.utils.Invariants; import accord.utils.Invariants;
import org.apache.cassandra.distributed.Cluster; import org.apache.cassandra.distributed.Cluster;
import org.apache.cassandra.distributed.api.ConsistencyLevel;
import org.apache.cassandra.distributed.api.IInstance; import org.apache.cassandra.distributed.api.IInstance;
import org.apache.cassandra.harry.gen.EntropySource;
import org.apache.cassandra.harry.op.Visit; import org.apache.cassandra.harry.op.Visit;
import org.apache.cassandra.harry.op.Operations; import org.apache.cassandra.harry.op.Operations;
import org.apache.cassandra.harry.execution.CompiledStatement; import org.apache.cassandra.harry.execution.CompiledStatement;
@ -39,6 +41,8 @@ import org.apache.cassandra.simulator.systems.InterceptedExecution;
import org.apache.cassandra.simulator.systems.InterceptingExecutor; import org.apache.cassandra.simulator.systems.InterceptingExecutor;
import org.apache.cassandra.simulator.systems.SimulatedAction; import org.apache.cassandra.simulator.systems.SimulatedAction;
import static org.apache.cassandra.simulator.SimulatorUtils.failWithOOM;
public class HarryValidatingQuery extends SimulatedAction public class HarryValidatingQuery extends SimulatedAction
{ {
private static final Logger logger = LoggerFactory.getLogger(HarryValidatingQuery.class); private static final Logger logger = LoggerFactory.getLogger(HarryValidatingQuery.class);
@ -51,13 +55,17 @@ public class HarryValidatingQuery extends SimulatedAction
private final HarrySimulatorTest.HarrySimulation simulation; private final HarrySimulatorTest.HarrySimulation simulation;
private final Visit visit; private final Visit visit;
private final QueryBuildingVisitExecutor queryBuilder; private final QueryBuildingVisitExecutor queryBuilder;
private final ConsistencyLevel consistencyLevel;
private final EntropySource rng;
public HarryValidatingQuery(HarrySimulatorTest.HarrySimulation simulation, public HarryValidatingQuery(HarrySimulatorTest.HarrySimulation simulation,
Cluster cluster, Cluster cluster,
TokenPlacementModel.ReplicationFactor rf, TokenPlacementModel.ReplicationFactor rf,
List<TokenPlacementModel.Node> owernship, List<TokenPlacementModel.Node> owernship,
Visit visit, Visit visit,
QueryBuildingVisitExecutor queryBuilder) QueryBuildingVisitExecutor queryBuilder,
ConsistencyLevel consistencyLevel,
EntropySource rng)
{ {
super(visit, Modifiers.RELIABLE_NO_TIMEOUTS, Modifiers.RELIABLE_NO_TIMEOUTS, null, simulation.simulated); super(visit, Modifiers.RELIABLE_NO_TIMEOUTS, Modifiers.RELIABLE_NO_TIMEOUTS, null, simulation.simulated);
this.rf = rf; this.rf = rf;
@ -67,7 +75,8 @@ public class HarryValidatingQuery extends SimulatedAction
this.visit = visit; this.visit = visit;
this.queryBuilder = queryBuilder; this.queryBuilder = queryBuilder;
this.simulation = simulation; this.simulation = simulation;
this.consistencyLevel = consistencyLevel;
this.rng = rng;
} }
protected InterceptedExecution task() protected InterceptedExecution task()
@ -78,14 +87,25 @@ public class HarryValidatingQuery extends SimulatedAction
{ {
try try
{ {
TokenPlacementModel.ReplicatedRanges ring = rf.replicate(owernship); if (consistencyLevel == ConsistencyLevel.NODE_LOCAL)
Invariants.require(visit.operations.length == 1);
Invariants.require(visit.operations[0] instanceof Operations.SelectStatement);
Operations.SelectStatement select = (Operations.SelectStatement) visit.operations[0];
for (TokenPlacementModel.Replica replica : ring.replicasFor(token(select.pd)))
{ {
TokenPlacementModel.ReplicatedRanges ring = rf.replicate(owernship);
Invariants.require(visit.operations.length == 1);
Invariants.require(visit.operations[0] instanceof Operations.SelectStatement);
Operations.SelectStatement select = (Operations.SelectStatement) visit.operations[0];
for (TokenPlacementModel.Replica replica : ring.replicasFor(token(select.pd)))
{
CompiledStatement compiled = queryBuilder.compile(visit);
Object[][] objects = executeNodeLocal(compiled.cql(), replica.node(), compiled.bindings());
List<ResultSetRow> actualRows = InJvmDTestVisitExecutor.rowsToResultSet(simulation.schema, select, objects);
simulation.model.validate(select, actualRows);
}
}
else
{
Operations.SelectStatement select = (Operations.SelectStatement) visit.operations[0];
CompiledStatement compiled = queryBuilder.compile(visit); CompiledStatement compiled = queryBuilder.compile(visit);
Object[][] objects = executeNodeLocal(compiled.cql(), replica.node(), compiled.bindings()); Object[][] objects = execute(compiled.cql(), rng.nextInt(cluster.size()) + 1, compiled.bindings());
List<ResultSetRow> actualRows = InJvmDTestVisitExecutor.rowsToResultSet(simulation.schema, select, objects); List<ResultSetRow> actualRows = InJvmDTestVisitExecutor.rowsToResultSet(simulation.schema, select, objects);
simulation.model.validate(select, actualRows); simulation.model.validate(select, actualRows);
} }
@ -93,6 +113,7 @@ public class HarryValidatingQuery extends SimulatedAction
catch (Throwable t) catch (Throwable t)
{ {
logger.error("Caught an exception while validating", t); logger.error("Caught an exception while validating", t);
failWithOOM();
throw t; throw t;
} }
} }
@ -113,4 +134,10 @@ public class HarryValidatingQuery extends SimulatedAction
.get(); .get();
return instance.executeInternal(statement, bindings); return instance.executeInternal(statement, bindings);
} }
protected Object[][] execute(String statement, int id, Object... bindings)
{
IInstance instance = cluster.get(id);
return instance.coordinator().execute(statement, consistencyLevel, bindings);
}
} }

View File

@ -117,15 +117,15 @@ public class ForwardingExecutorFactory implements ExecutorFactory
} }
@Override @Override
public Thread startThread(String name, Runnable runnable, InfiniteLoopExecutor.Daemon daemon) public Thread startThread(String name, Runnable runnable, SystemThreadTag systemTag, SimulatorThreadTag simulatorTag)
{ {
return delegate().startThread(name, runnable, daemon); return delegate().startThread(name, runnable, systemTag, simulatorTag);
} }
@Override @Override
public Interruptible infiniteLoop(String name, Interruptible.Task task, InfiniteLoopExecutor.SimulatorSafe simulatorSafe, InfiniteLoopExecutor.Daemon daemon, InfiniteLoopExecutor.Interrupts interrupts) public Interruptible infiniteLoop(String name, Interruptible.Task task, InfiniteLoopExecutor.SimulatorSafe simulatorSafe, SystemThreadTag systemTag, InfiniteLoopExecutor.Interrupts interrupts)
{ {
return delegate().infiniteLoop(name, task, simulatorSafe, daemon, interrupts); return delegate().infiniteLoop(name, task, simulatorSafe, systemTag, interrupts);
} }
@Override @Override

View File

@ -30,7 +30,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import static org.apache.cassandra.concurrent.InfiniteLoopExecutor.Daemon.DAEMON; import static org.apache.cassandra.concurrent.ExecutorFactory.SystemThreadTag.DAEMON;
public class InfiniteLoopExecutorTest public class InfiniteLoopExecutorTest
{ {

View File

@ -204,7 +204,7 @@ public class SimulatedExecutorFactory implements ExecutorFactory, Clock
} }
@Override @Override
public Thread startThread(String name, Runnable runnable, InfiniteLoopExecutor.Daemon daemon) public Thread startThread(String name, Runnable runnable, SystemThreadTag systemTag, SimulatorThreadTag simulatorTag)
{ {
throw new UnsupportedOperationException("Thread can't be simualted"); throw new UnsupportedOperationException("Thread can't be simualted");
} }
@ -213,7 +213,7 @@ public class SimulatedExecutorFactory implements ExecutorFactory, Clock
public Interruptible infiniteLoop(String name, public Interruptible infiniteLoop(String name,
Interruptible.Task task, Interruptible.Task task,
InfiniteLoopExecutor.SimulatorSafe simulatorSafe, InfiniteLoopExecutor.SimulatorSafe simulatorSafe,
InfiniteLoopExecutor.Daemon daemon, SystemThreadTag systemTag,
InfiniteLoopExecutor.Interrupts interrupts) InfiniteLoopExecutor.Interrupts interrupts)
{ {
var delegate = new UnorderedScheduledExecutorService(); var delegate = new UnorderedScheduledExecutorService();

View File

@ -243,16 +243,16 @@ public abstract class FuzzTestBase extends CQLTester.InMemory
} }
@Override @Override
public Thread startThread(String name, Runnable runnable, InfiniteLoopExecutor.Daemon daemon) public Thread startThread(String name, Runnable runnable, SystemThreadTag systemTag, SimulatorThreadTag simulatorTag)
{ {
if (shouldMock()) return new Thread(); if (shouldMock()) return new Thread();
return delegate.startThread(name, runnable, daemon); return delegate.startThread(name, runnable, systemTag, simulatorTag);
} }
@Override @Override
public Interruptible infiniteLoop(String name, Interruptible.Task task, InfiniteLoopExecutor.SimulatorSafe simulatorSafe, InfiniteLoopExecutor.Daemon daemon, InfiniteLoopExecutor.Interrupts interrupts) public Interruptible infiniteLoop(String name, Interruptible.Task task, InfiniteLoopExecutor.SimulatorSafe simulatorSafe, SystemThreadTag systemTag, InfiniteLoopExecutor.Interrupts interrupts)
{ {
return delegate.infiniteLoop(name, task, simulatorSafe, daemon, interrupts); return delegate.infiniteLoop(name, task, simulatorSafe, systemTag, interrupts);
} }
@Override @Override