Artificial Latency Injection

patch by Benedict; reviewed by Alex Petrov and Ariel Weisberg for CASSANDRA-17024
This commit is contained in:
Benedict Elliott Smith 2021-01-19 18:23:27 +00:00 committed by Benedict Elliott Smith
parent 51a58ef22e
commit 8ad8e56378
35 changed files with 1035 additions and 247 deletions

View File

@ -58,15 +58,9 @@
</module> </module>
<module name="SuppressWithNearbyCommentFilter"> <module name="SuppressWithNearbyCommentFilter">
<property name="commentFormat" value="checkstyle: permit this invocation"/> <property name="commentFormat" value="checkstyle: permit this invocation"/>
<property name="idFormat" value="blockPathToFile"/> <property name="idFormat" value="blockInstantNow|blockPathToFile|blockToCases"/>
<property name="influenceFormat" value="0"/> <property name="influenceFormat" value="0"/>
</module>
<module name="SuppressWithNearbyCommentFilter">
<property name="commentFormat" value="checkstyle: permit this invocation"/>
<property name="idFormat" value="blockToCases"/>
<property name="influenceFormat" value="0"/>
</module> </module>
<module name="RegexpSinglelineJava"> <module name="RegexpSinglelineJava">

View File

@ -33,7 +33,7 @@ import io.netty.util.concurrent.FastThreadLocal;
*/ */
public class ExecutorLocals implements WithResources, Closeable public class ExecutorLocals implements WithResources, Closeable
{ {
private static final ExecutorLocals none = new ExecutorLocals(null, null); private static final ExecutorLocals none = new ExecutorLocals(null, null, false);
private static final FastThreadLocal<ExecutorLocals> locals = new FastThreadLocal<ExecutorLocals>() private static final FastThreadLocal<ExecutorLocals> locals = new FastThreadLocal<ExecutorLocals>()
{ {
@Override @Override
@ -45,20 +45,23 @@ public class ExecutorLocals implements WithResources, Closeable
public static class Impl public static class Impl
{ {
protected static void set(TraceState traceState, ClientWarn.State clientWarnState) @SuppressWarnings("resource")
protected static void set(TraceState traceState, ClientWarn.State clientWarnState, boolean eligibleForArtificialLatency)
{ {
if (traceState == null && clientWarnState == null) locals.set(none); if (traceState == null && clientWarnState == null && !eligibleForArtificialLatency) locals.set(none);
else locals.set(new ExecutorLocals(traceState, clientWarnState)); else locals.set(new ExecutorLocals(traceState, clientWarnState, eligibleForArtificialLatency));
} }
} }
public final TraceState traceState; public final TraceState traceState;
public final ClientWarn.State clientWarnState; public final ClientWarn.State clientWarnState;
public final boolean eligibleForArtificialLatency;
protected ExecutorLocals(TraceState traceState, ClientWarn.State clientWarnState) protected ExecutorLocals(TraceState traceState, ClientWarn.State clientWarnState, boolean eligibleForArtificialLatency)
{ {
this.traceState = traceState; this.traceState = traceState;
this.clientWarnState = clientWarnState; this.clientWarnState = clientWarnState;
this.eligibleForArtificialLatency = eligibleForArtificialLatency;
} }
/** /**
@ -82,7 +85,7 @@ public class ExecutorLocals implements WithResources, Closeable
public static ExecutorLocals create(TraceState traceState) public static ExecutorLocals create(TraceState traceState)
{ {
ExecutorLocals current = locals.get(); ExecutorLocals current = locals.get();
return current.traceState == traceState ? current : new ExecutorLocals(traceState, current.clientWarnState); return current.traceState == traceState ? current : new ExecutorLocals(traceState, current.clientWarnState, current.eligibleForArtificialLatency);
} }
public static void clear() public static void clear()

View File

@ -195,6 +195,7 @@ public class AccordSpec
* default transactional mode for tables created by this node when no transactional mode has been specified in the DDL * default transactional mode for tables created by this node when no transactional mode has been specified in the DDL
*/ */
public TransactionalMode default_transactional_mode = TransactionalMode.off; public TransactionalMode default_transactional_mode = TransactionalMode.off;
public boolean ephemeralReadEnabled = true; public boolean ephemeralReadEnabled = true;
public boolean state_cache_listener_jfr_enabled = false; public boolean state_cache_listener_jfr_enabled = false;

View File

@ -62,6 +62,10 @@ public enum CassandraRelevantProperties
ALLOW_UNSAFE_REPLACE("cassandra.allow_unsafe_replace"), ALLOW_UNSAFE_REPLACE("cassandra.allow_unsafe_replace"),
ALLOW_UNSAFE_TRANSIENT_CHANGES("cassandra.allow_unsafe_transient_changes"), ALLOW_UNSAFE_TRANSIENT_CHANGES("cassandra.allow_unsafe_transient_changes"),
APPROXIMATE_TIME_PRECISION_MS("cassandra.approximate_time_precision_ms", "2"), APPROXIMATE_TIME_PRECISION_MS("cassandra.approximate_time_precision_ms", "2"),
ARTIFICIAL_LATENCIES("cassandra.artificial_latencies"),
ARTIFICIAL_LATENCIES_UNSAFE("cassandra.artificial_latencies_unsafe"),
ARTIFICIAL_LATENCY_LIMIT("cassandra.artificial_latency_limit", "200ms"),
ARTIFICIAL_LATENCY_VERBS("cassandra.artificial_latency_verbs"),
ASYNC_PROFILER_ENABLED("cassandra.async_profiler.enabled", "false"), ASYNC_PROFILER_ENABLED("cassandra.async_profiler.enabled", "false"),
ASYNC_PROFILER_UNSAFE_MODE("cassandra.async_profiler.unsafe_mode", "false"), ASYNC_PROFILER_UNSAFE_MODE("cassandra.async_profiler.unsafe_mode", "false"),
/** 2 ** GENSALT_LOG2_ROUNDS rounds of hashing will be performed. */ /** 2 ** GENSALT_LOG2_ROUNDS rounds of hashing will be performed. */

View File

@ -116,7 +116,7 @@ public abstract class QueryOptions
values, values,
ByteArrayUtil.EMPTY_ARRAY_OF_BYTE_ARRAYS, ByteArrayUtil.EMPTY_ARRAY_OF_BYTE_ARRAYS,
skipMetadata, skipMetadata,
new SpecificOptions(pageSize, pagingState, serialConsistency, timestamp, keyspace, nowInSeconds), new SpecificOptions(pageSize, pagingState, serialConsistency, timestamp, keyspace, nowInSeconds, false),
version); version);
} }
@ -264,6 +264,11 @@ public abstract class QueryOptions
return nowInSeconds != UNSET_NOWINSEC ? nowInSeconds : state.getNowInSeconds(); return nowInSeconds != UNSET_NOWINSEC ? nowInSeconds : state.getNowInSeconds();
} }
public boolean isEligibleForArtificialLatency()
{
return getSpecificOptions().eligibleForArtificialLatency;
}
/** The keyspace that this query is bound to, or null if not relevant. */ /** The keyspace that this query is bound to, or null if not relevant. */
public String getKeyspace() { return getSpecificOptions().keyspace; } public String getKeyspace() { return getSpecificOptions().keyspace; }
@ -631,7 +636,7 @@ public abstract class QueryOptions
// Options that are likely to not be present in most queries // Options that are likely to not be present in most queries
static class SpecificOptions static class SpecificOptions
{ {
private static final SpecificOptions DEFAULT = new SpecificOptions(-1, null, null, Long.MIN_VALUE, null, UNSET_NOWINSEC); private static final SpecificOptions DEFAULT = new SpecificOptions(-1, null, null, Long.MIN_VALUE, null, UNSET_NOWINSEC, false);
private final int pageSize; private final int pageSize;
private final PagingState state; private final PagingState state;
@ -639,13 +644,15 @@ public abstract class QueryOptions
private final long timestamp; private final long timestamp;
private final String keyspace; private final String keyspace;
private final long nowInSeconds; private final long nowInSeconds;
private final boolean eligibleForArtificialLatency;
private SpecificOptions(int pageSize, private SpecificOptions(int pageSize,
PagingState state, PagingState state,
ConsistencyLevel serialConsistency, ConsistencyLevel serialConsistency,
long timestamp, long timestamp,
String keyspace, String keyspace,
long nowInSeconds) long nowInSeconds,
boolean eligibleForArtificialLatency)
{ {
this.pageSize = pageSize; this.pageSize = pageSize;
this.state = state; this.state = state;
@ -653,11 +660,12 @@ public abstract class QueryOptions
this.timestamp = timestamp; this.timestamp = timestamp;
this.keyspace = keyspace; this.keyspace = keyspace;
this.nowInSeconds = nowInSeconds; this.nowInSeconds = nowInSeconds;
this.eligibleForArtificialLatency = eligibleForArtificialLatency;
} }
public SpecificOptions withNowInSec(long nowInSec) public SpecificOptions withNowInSec(long nowInSec)
{ {
return new SpecificOptions(pageSize, state, serialConsistency, timestamp, keyspace, nowInSec); return new SpecificOptions(pageSize, state, serialConsistency, timestamp, keyspace, nowInSec, eligibleForArtificialLatency);
} }
} }
@ -674,7 +682,9 @@ public abstract class QueryOptions
TIMESTAMP, TIMESTAMP,
NAMES_FOR_VALUES, NAMES_FOR_VALUES,
KEYSPACE, KEYSPACE,
NOW_IN_SECONDS; NOW_IN_SECONDS,
ELIGIBLE_FOR_ARTIFICIAL_LATENCY,
;
private final int mask; private final int mask;
@ -755,7 +765,8 @@ public abstract class QueryOptions
String keyspace = Flag.contains(flags, Flag.KEYSPACE) ? CBUtil.readString(body) : null; String keyspace = Flag.contains(flags, Flag.KEYSPACE) ? CBUtil.readString(body) : null;
long nowInSeconds = Flag.contains(flags, Flag.NOW_IN_SECONDS) ? CassandraUInt.toLong(body.readInt()) long nowInSeconds = Flag.contains(flags, Flag.NOW_IN_SECONDS) ? CassandraUInt.toLong(body.readInt())
: UNSET_NOWINSEC; : UNSET_NOWINSEC;
options = new SpecificOptions(pageSize, pagingState, serialConsistency, timestamp, keyspace, nowInSeconds); boolean eligibleForArtificialLatency = Flag.contains(flags, Flag.ELIGIBLE_FOR_ARTIFICIAL_LATENCY);
options = new SpecificOptions(pageSize, pagingState, serialConsistency, timestamp, keyspace, nowInSeconds, eligibleForArtificialLatency);
} }
DefaultQueryOptions opts = new DefaultQueryOptions(consistency, null, values, skipMetadata, options, version); DefaultQueryOptions opts = new DefaultQueryOptions(consistency, null, values, skipMetadata, options, version);

View File

@ -45,7 +45,12 @@ public enum ConsistencyLevel
SERIAL (8), SERIAL (8),
LOCAL_SERIAL(9, true), LOCAL_SERIAL(9, true),
LOCAL_ONE (10, true), LOCAL_ONE (10, true),
NODE_LOCAL (11, true); NODE_LOCAL (11, true),
UNSAFE_DELAY_QUORUM(99, false),
UNSAFE_DELAY_SERIAL(100, false),
UNSAFE_DELAY_LOCAL_QUORUM(101, true),
UNSAFE_DELAY_LOCAL_SERIAL(102, true);
// Used by the binary protocol // Used by the binary protocol
public final int code; public final int code;
@ -143,12 +148,16 @@ public enum ConsistencyLevel
return 2; return 2;
case THREE: case THREE:
return 3; return 3;
case UNSAFE_DELAY_QUORUM:
case QUORUM: case QUORUM:
case UNSAFE_DELAY_SERIAL:
case SERIAL: case SERIAL:
return quorumFor(replicationStrategy); return quorumFor(replicationStrategy);
case ALL: case ALL:
return replicationStrategy.getReplicationFactor().allReplicas; return replicationStrategy.getReplicationFactor().allReplicas;
case UNSAFE_DELAY_LOCAL_QUORUM:
case LOCAL_QUORUM: case LOCAL_QUORUM:
case UNSAFE_DELAY_LOCAL_SERIAL:
case LOCAL_SERIAL: case LOCAL_SERIAL:
return localQuorumForOurDc(replicationStrategy); return localQuorumForOurDc(replicationStrategy);
case EACH_QUORUM: case EACH_QUORUM:
@ -178,13 +187,16 @@ public enum ConsistencyLevel
{ {
case ANY: case ANY:
break; break;
case LOCAL_ONE: case LOCAL_QUORUM: case LOCAL_SERIAL: case LOCAL_ONE:
case UNSAFE_DELAY_LOCAL_QUORUM: case LOCAL_QUORUM:
case UNSAFE_DELAY_LOCAL_SERIAL: case LOCAL_SERIAL:
// we will only count local replicas towards our response count, as these queries only care about local guarantees // we will only count local replicas towards our response count, as these queries only care about local guarantees
blockFor += pending.count(InOurDc.replicas()); blockFor += pending.count(InOurDc.replicas());
break; break;
case ONE: case TWO: case THREE: case ONE: case TWO: case THREE:
case QUORUM: case EACH_QUORUM: case UNSAFE_DELAY_QUORUM: case QUORUM:
case SERIAL: case UNSAFE_DELAY_SERIAL: case SERIAL:
case EACH_QUORUM:
case ALL: case ALL:
blockFor += pending.size(); blockFor += pending.size();
} }
@ -219,7 +231,9 @@ public enum ConsistencyLevel
switch (this) switch (this)
{ {
case SERIAL: case SERIAL:
case UNSAFE_DELAY_SERIAL:
case LOCAL_SERIAL: case LOCAL_SERIAL:
case UNSAFE_DELAY_LOCAL_SERIAL:
throw new InvalidRequestException("You must use conditional updates for serializable writes"); throw new InvalidRequestException("You must use conditional updates for serializable writes");
} }
} }
@ -233,7 +247,9 @@ public enum ConsistencyLevel
requireNetworkTopologyStrategy(replicationStrategy); requireNetworkTopologyStrategy(replicationStrategy);
break; break;
case SERIAL: case SERIAL:
case UNSAFE_DELAY_SERIAL:
case LOCAL_SERIAL: case LOCAL_SERIAL:
case UNSAFE_DELAY_LOCAL_SERIAL:
throw new InvalidRequestException(this + " is not supported as conditional update commit consistency. Use ANY if you mean \"make sure it is accepted but I don't care how many replicas commit it for non-SERIAL reads\""); throw new InvalidRequestException(this + " is not supported as conditional update commit consistency. Use ANY if you mean \"make sure it is accepted but I don't care how many replicas commit it for non-SERIAL reads\"");
} }
} }
@ -246,7 +262,16 @@ public enum ConsistencyLevel
public boolean isSerialConsistency() public boolean isSerialConsistency()
{ {
return this == SERIAL || this == LOCAL_SERIAL; switch (this)
{
case SERIAL:
case UNSAFE_DELAY_SERIAL:
case LOCAL_SERIAL:
case UNSAFE_DELAY_LOCAL_SERIAL:
return true;
default:
return false;
}
} }
public void validateCounterForWrite(TableMetadata metadata) throws InvalidRequestException public void validateCounterForWrite(TableMetadata metadata) throws InvalidRequestException

View File

@ -925,12 +925,12 @@ public abstract class ReadCommand extends AbstractReadQuery
*/ */
public Message<ReadCommand> createMessage(boolean trackRepairedData, Dispatcher.RequestTime requestTime) public Message<ReadCommand> createMessage(boolean trackRepairedData, Dispatcher.RequestTime requestTime)
{ {
List<MessageFlag> flags = new ArrayList<>(3); int flags = MessageFlag.CALL_BACK_ON_FAILURE.addTo(0);
flags.add(MessageFlag.CALL_BACK_ON_FAILURE);
if (trackWarnings) if (trackWarnings)
flags.add(MessageFlag.TRACK_WARNINGS); flags = MessageFlag.TRACK_WARNINGS.addTo(flags);
if (trackRepairedData) if (trackRepairedData)
flags.add(MessageFlag.TRACK_REPAIRED_DATA); flags = MessageFlag.TRACK_REPAIRED_DATA.addTo(flags);
return Message.outWithFlags(verb(), return Message.outWithFlags(verb(),
this, this,

View File

@ -110,6 +110,7 @@ public class ReplicaPlans
return true; return true;
case LOCAL_ONE: case LOCAL_ONE:
return countInOurDc(liveReplicas).hasAtleast(1, 1); return countInOurDc(liveReplicas).hasAtleast(1, 1);
case UNSAFE_DELAY_LOCAL_QUORUM:
case LOCAL_QUORUM: case LOCAL_QUORUM:
return countInOurDc(liveReplicas).hasAtleast(localQuorumForOurDc(replicationStrategy), 1); return countInOurDc(liveReplicas).hasAtleast(localQuorumForOurDc(replicationStrategy), 1);
case EACH_QUORUM: case EACH_QUORUM:
@ -157,6 +158,7 @@ public class ReplicaPlans
throw UnavailableException.create(consistencyLevel, 1, blockForFullReplicas, localLive.allReplicas(), localLive.fullReplicas()); throw UnavailableException.create(consistencyLevel, 1, blockForFullReplicas, localLive.allReplicas(), localLive.fullReplicas());
break; break;
} }
case UNSAFE_DELAY_LOCAL_QUORUM:
case LOCAL_QUORUM: case LOCAL_QUORUM:
{ {
Replicas.ReplicaCount localLive = countInOurDc(allLive); Replicas.ReplicaCount localLive = countInOurDc(allLive);
@ -753,7 +755,7 @@ public class ReplicaPlans
Replicas.temporaryAssertFull(liveAndDown.all()); // TODO CASSANDRA-14547 Replicas.temporaryAssertFull(liveAndDown.all()); // TODO CASSANDRA-14547
if (consistencyForPaxos == ConsistencyLevel.LOCAL_SERIAL) if (consistencyForPaxos.isDatacenterLocal())
{ {
// TODO: we should cleanup our semantics here, as we're filtering ALL nodes to localDC which is unexpected for ReplicaPlan // TODO: we should cleanup our semantics here, as we're filtering ALL nodes to localDC which is unexpected for ReplicaPlan
// Restrict natural and pending to node in the local DC only // Restrict natural and pending to node in the local DC only

View File

@ -0,0 +1,397 @@
/*
* 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.net;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.PriorityQueue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
import java.util.concurrent.locks.LockSupport;
import java.util.function.ToLongFunction;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import org.agrona.collections.Object2LongHashMap;
import org.apache.cassandra.concurrent.ExecutorLocals;
import org.apache.cassandra.concurrent.Interruptible;
import org.apache.cassandra.config.CassandraRelevantProperties;
import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.tcm.ClusterMetadata;
import org.apache.cassandra.tcm.membership.Directory;
import org.apache.cassandra.tcm.membership.Location;
import org.apache.cassandra.tcm.membership.NodeId;
import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException;
import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFactory;
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.SimulatorSafe.SAFE;
import static org.apache.cassandra.config.CassandraRelevantProperties.ARTIFICIAL_LATENCY_LIMIT;
import static org.apache.cassandra.net.MessagingService.instance;
import static org.apache.cassandra.utils.Clock.Global.nanoTime;
/*
* Mechanism to delay the sending of messages to peers
*/
public class ArtificialLatency extends ExecutorLocals.Impl
{
private static volatile Set<Verb> artificialLatencyVerbs;
private static volatile boolean artificialLatencyOnlyPermittedConsistencyLevels = true;
private static volatile ToLongFunction<InetAddressAndPort> artificialLatencyNanos;
private static String artificialLatencies;
private static Sink running;
static
{
setArtificialLatencyVerbs(CassandraRelevantProperties.ARTIFICIAL_LATENCY_VERBS.getString(""));
String latencies = CassandraRelevantProperties.ARTIFICIAL_LATENCIES.getString();
String unsafeLatencies = CassandraRelevantProperties.ARTIFICIAL_LATENCIES_UNSAFE.getString();
if (latencies != null) setArtificialLatencies(latencies);
else if (unsafeLatencies != null) unsafeSetArtificialLatencies(unsafeLatencies);
if (artificialLatencyNanos != null && !artificialLatencyVerbs.isEmpty())
setEnabled(true);
}
// ensure initialised
public static void touch() {}
public static synchronized boolean isEnabled()
{
return running != null;
}
public static synchronized void setEnabled(boolean enabled)
{
if (enabled) start();
else stop();
}
public static synchronized void start()
{
if (running == null)
running = Sink.start();
}
public static synchronized void stop()
{
if (running != null)
{
running.stop();
running = null;
}
}
public static boolean isEligibleForArtificialLatency()
{
return ExecutorLocals.current().eligibleForArtificialLatency;
}
public static void setEligibleForArtificialLatency(boolean eligibleForArtificialLatency)
{
ExecutorLocals current = ExecutorLocals.current();
set(current.traceState, current.clientWarnState, eligibleForArtificialLatency);
}
static class Sink implements OutboundSink.AsyncFilter, Interruptible.Task
{
static class Delayed implements Comparable<Delayed>
{
final Message<?> message;
final InetAddressAndPort to;
final ConnectionType type;
final long deadline;
final OutboundSink.Sink sink;
Delayed(Message<?> message, InetAddressAndPort to, ConnectionType type, long deadline, OutboundSink.Sink sink)
{
this.message = message;
this.to = to;
this.type = type;
this.deadline = deadline;
this.sink = sink;
}
@Override
public int compareTo(Delayed that)
{
return Long.compare(this.deadline, that.deadline);
}
}
volatile boolean isShutdown;
final ConcurrentLinkedQueue<Delayed> in = new ConcurrentLinkedQueue<>();
// messages we have stashed in order to apply an artificial delay
// note that this queue is not ordered, so that if the artificial delay is modified
// it may not take effect until the difference between the two delays elapses
final PriorityQueue<Delayed> out = new PriorityQueue<>();
final Interruptible executor = executorFactory().infiniteLoop("ArtificialLatency", this, SAFE, DAEMON, UNSYNCHRONIZED);
volatile Thread waiting;
volatile long waitingUntil;
private static final AtomicLongFieldUpdater<Sink> waitingUntilUpdater = AtomicLongFieldUpdater.newUpdater(Sink.class, "waitingUntil");
static Sink start()
{
Sink sink = new Sink();
instance().outboundSink.add(sink);
return sink;
}
void stop()
{
isShutdown = true;
artificialLatencyNanos = ignore -> 0;
instance().outboundSink.remove(this);
executor.shutdownNow();
try
{
executor.awaitTermination(1, TimeUnit.DAYS);
}
catch (InterruptedException e)
{
throw new UncheckedInterruptedException(e);
}
}
@Override
public void filter(Message<?> message, InetAddressAndPort to, ConnectionType type, OutboundSink.Sink next)
{
if (artificialLatencyOnlyPermittedConsistencyLevels && !message.header.permitsArtificialLatency())
{
next.accept(message, to, type);
return;
}
if (!artificialLatencyVerbs.contains(message.verb()))
{
next.accept(message, to, type);
return;
}
long addNanos = artificialLatencyNanos.applyAsLong(to);
if (addNanos <= 0)
{
next.accept(message, to, type);
return;
}
long deadline = nanoTime() + addNanos;
Delayed delay = new Delayed(message, to, type, deadline, next);
in.add(delay);
while (true)
{
long curWaitingUntil = waitingUntil;
if (deadline >= curWaitingUntil)
break;
if (waitingUntilUpdater.compareAndSet(this, curWaitingUntil, Long.MIN_VALUE))
{
Thread thread = waiting;
if (thread != null)
LockSupport.unpark(thread);
}
}
if (isShutdown && in.remove(delay))
next.accept(message, to, type);
}
public void run(Interruptible.State state) throws InterruptedException
{
switch (state)
{
default: throw new IllegalStateException();
case SHUTTING_DOWN:
{
drainIn();
out.forEach(d -> instance().send(d.message, d.to, d.type));
return;
}
case NORMAL:
{
waiting = Thread.currentThread();
while (true)
{
long deadline;
while (true)
{
drainIn();
deadline = out.isEmpty() ? Long.MAX_VALUE : out.peek().deadline;
if (waitingUntil == deadline)
break;
waitingUntil = deadline;
}
long waitNanos = deadline - nanoTime();
if (waitNanos <= 0)
break;
LockSupport.parkNanos(waitNanos);
}
}
case INTERRUPTED:
{
Delayed delayed;
long now = nanoTime();
while (null != (delayed = out.peek()) && delayed.deadline <= now)
{
delayed.sink.accept(delayed.message, delayed.to, delayed.type);
out.poll();
}
}
}
}
private void drainIn()
{
for (Delayed delayed = in.poll(); delayed != null ; delayed = in.poll())
out.add(delayed);
}
}
public static String getArtificialLatencies()
{
return artificialLatencies;
}
private static long parseNanos(String latency)
{
if (!latency.endsWith("ms"))
throw new IllegalArgumentException("Latency must be specified in terms of milliseconds (with 'ms' suffix)");
return TimeUnit.MILLISECONDS.toNanos(Long.parseLong(latency.substring(0, latency.length() - 2)));
}
public static void setArtificialLatencies(String latencies)
{
setArtificialLatencies(latencies, parseNanos(ARTIFICIAL_LATENCY_LIMIT.getString()));
}
public static void unsafeSetArtificialLatencies(String latencies)
{
setArtificialLatencies(latencies, Long.MAX_VALUE);
}
private static synchronized void setArtificialLatencies(String latencies, long nanoLimit)
{
if (latencies.indexOf(',') < 0)
{
long nanos = parseNanos(latencies);
if (nanos >= nanoLimit)
throw new IllegalArgumentException("Artificial latency limit is " + nanoLimit + "ns; tried to set " + nanos + "ns");
artificialLatencyNanos = ignore -> nanos;
}
else
{
String[] parse = latencies.split(",");
Object2LongHashMap<String> dcLatencies = new Object2LongHashMap<>(-1L);
for (int i = 0 ; i < parse.length ; ++i)
{
String[] subparse = parse[i].split(":");
String dc = subparse[0];
long nanos = parseNanos(subparse[1]);
if (nanos >= nanoLimit)
throw new IllegalArgumentException("Artificial latency limit is " + nanoLimit + "ns; tried to set " + nanos + "ns");
dcLatencies.put(dc, nanos);
}
artificialLatencyNanos = addr -> {
Directory directory = ClusterMetadata.current().directory;
NodeId nodeId = directory.peerId(addr);
if (nodeId == null)
return 0;
Location location = directory.location(nodeId);
if (location == null)
return 0;
return dcLatencies.getOrDefault(location.datacenter, 0L);
};
}
artificialLatencies = latencies;
}
public static String getArtificialLatencyVerbs()
{
return artificialLatencyVerbs.stream()
.map(Verb::toString)
.collect(Collectors.joining(","));
}
public static boolean getArtificialLatencyOnlyPermittedConsistencyLevels()
{
return artificialLatencyOnlyPermittedConsistencyLevels;
}
public static void setArtificialLatencyVerbs(String commaDelimitedVerbs)
{
if (commaDelimitedVerbs.isEmpty())
artificialLatencyVerbs = Collections.emptySet();
else
artificialLatencyVerbs = Arrays.stream(commaDelimitedVerbs.split(","))
.filter(s -> !s.isEmpty())
.map(s -> {
try
{
return EnumSet.of(Verb.valueOf(s));
}
catch (IllegalArgumentException iae)
{
try
{
return EnumSet.of(Verb.valueOf(s + "_REQ"), Verb.valueOf(s + "_RSP"));
}
catch (IllegalArgumentException ignore) {}
throw iae;
}
})
.collect(Collector.of(() -> EnumSet.noneOf(Verb.class), Set::addAll, (left, right) -> { left.addAll(right); return left; }));
}
public static void setArtificialLatencyOnlyPermittedConsistencyLevels(boolean onlyPermitted)
{
artificialLatencyOnlyPermittedConsistencyLevels = onlyPermitted;
}
public static String recommendedVerbs()
{
EnumSet<Verb> verbs = EnumSet.noneOf(Verb.class);
verbs.add(Verb.MUTATION_REQ);
verbs.add(Verb.MUTATION_RSP);
verbs.add(Verb.READ_REQ);
verbs.add(Verb.READ_RSP);
verbs.add(Verb.RANGE_REQ);
verbs.add(Verb.RANGE_RSP);
verbs.add(Verb.READ_REPAIR_REQ);
verbs.add(Verb.READ_REPAIR_RSP);
verbs.add(Verb.HINT_REQ);
verbs.add(Verb.HINT_RSP);
for (Verb verb : Verb.values())
{
if (verb.name().startsWith("ACCORD") || verb.name().startsWith("PAXOS"))
verbs.add(verb);
}
return verbs.stream().map(Verb::toString).collect(Collectors.joining(","));
}
}

View File

@ -22,7 +22,6 @@ import java.nio.ByteBuffer;
import java.util.Collections; import java.util.Collections;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@ -58,9 +57,11 @@ 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.db.TypeSizes.sizeof; import static org.apache.cassandra.db.TypeSizes.sizeof;
import static org.apache.cassandra.db.TypeSizes.sizeofUnsignedVInt; import static org.apache.cassandra.db.TypeSizes.sizeofUnsignedVInt;
import static org.apache.cassandra.net.MessageFlag.ARTIFICIAL_LATENCY;
import static org.apache.cassandra.net.MessagingService.VERSION_40; import static org.apache.cassandra.net.MessagingService.VERSION_40;
import static org.apache.cassandra.net.MessagingService.VERSION_50; import static org.apache.cassandra.net.MessagingService.VERSION_50;
import static org.apache.cassandra.net.MessagingService.VERSION_60; import static org.apache.cassandra.net.MessagingService.VERSION_60;
import static org.apache.cassandra.utils.Clock.Global.nanoTime;
import static org.apache.cassandra.utils.FBUtilities.getBroadcastAddressAndPort; import static org.apache.cassandra.utils.FBUtilities.getBroadcastAddressAndPort;
import static org.apache.cassandra.utils.MonotonicClock.Global.approxTime; import static org.apache.cassandra.utils.MonotonicClock.Global.approxTime;
import static org.apache.cassandra.utils.vint.VIntCoding.computeUnsignedVIntSize; import static org.apache.cassandra.utils.vint.VIntCoding.computeUnsignedVIntSize;
@ -147,6 +148,12 @@ public class Message<T> implements ResponseContext
return header.expiresAtNanos; return header.expiresAtNanos;
} }
@Override
public boolean hasFlag(MessageFlag flag)
{
return header.hasFlag(flag);
}
/** For how long the message has lived. */ /** For how long the message has lived. */
public long elapsedSinceCreated(TimeUnit units) public long elapsedSinceCreated(TimeUnit units)
{ {
@ -246,28 +253,31 @@ public class Message<T> implements ResponseContext
return outWithParam(nextId(), verb, 0, payload, flag.addTo(0), null, null); return outWithParam(nextId(), verb, 0, payload, flag.addTo(0), null, null);
} }
public static <T> Message<T> outWithFlags(Verb verb, T payload, MessageFlag flag1, MessageFlag flag2) public static <T> Message<T> outWithFlag(Verb verb, T payload, Dispatcher.RequestTime requestTime, MessageFlag flag)
{ {
assert !verb.isResponse(); return outWithFlags(verb, payload, requestTime, flag.addTo(0));
return outWithParam(nextId(), verb, 0, payload, flag2.addTo(flag1.addTo(0)), null, null);
} }
public static <T> Message<T> outWithFlags(Verb verb, T payload, Dispatcher.RequestTime requestTime, List<MessageFlag> flags) public static <T> Message<T> outWithFlags(Verb verb, T payload, Dispatcher.RequestTime requestTime, int encodedFlags)
{ {
assert !verb.isResponse(); assert !verb.isResponse();
int encodedFlags = 0; if (ArtificialLatency.isEligibleForArtificialLatency())
for (MessageFlag flag : flags) encodedFlags = ARTIFICIAL_LATENCY.addTo(encodedFlags);
encodedFlags = flag.addTo(encodedFlags);
return new Message<T>(new Header(nextId(), return newWithFlags(nextId(), verb, payload, requestTime.startedAtNanos(), requestTime.computeDeadline(verb.expiresAfterNanos()), encodedFlags);
epochSupplier.get(), }
verb,
getBroadcastAddressAndPort(), private static <T> Message<T> newWithFlags(long id, Verb verb, T payload, long startedAtNanos, long expiresAtNanos, int encodedFlags)
requestTime.startedAtNanos(), {
requestTime.computeDeadline(verb.expiresAfterNanos()), return new Message<>(new Header(id,
encodedFlags, epochSupplier.get(),
buildParams(null, null)), verb,
payload); getBroadcastAddressAndPort(),
startedAtNanos,
expiresAtNanos,
encodedFlags,
buildParams(null, null)),
payload);
} }
@VisibleForTesting @VisibleForTesting
@ -294,6 +304,8 @@ public class Message<T> implements ResponseContext
long createdAtNanos = approxTime.now(); long createdAtNanos = approxTime.now();
if (expiresAtNanos == 0) if (expiresAtNanos == 0)
expiresAtNanos = verb.expiresAtNanos(createdAtNanos); expiresAtNanos = verb.expiresAtNanos(createdAtNanos);
if (ArtificialLatency.isEligibleForArtificialLatency())
flags = ARTIFICIAL_LATENCY.addTo(flags);
return new Message<>(new Header(id, epochSupplier.get(), verb, from, createdAtNanos, expiresAtNanos, flags, buildParams(paramType, paramValue)), payload); return new Message<>(new Header(id, epochSupplier.get(), verb, from, createdAtNanos, expiresAtNanos, flags, buildParams(paramType, paramValue)), payload);
} }
@ -355,7 +367,8 @@ public class Message<T> implements ResponseContext
public static <T> Message<T> responseWith(T payload, ResponseContext respondTo) public static <T> Message<T> responseWith(T payload, ResponseContext respondTo)
{ {
return outWithParam(respondTo.id(), respondTo.verb().responseVerb, respondTo.expiresAtNanos(), payload, null, null); int encodedFlags = respondTo.hasFlag(ARTIFICIAL_LATENCY) ? ARTIFICIAL_LATENCY.addTo(0) : 0;
return newWithFlags(respondTo.id(), respondTo.verb().responseVerb, payload, nanoTime(), respondTo.expiresAtNanos(), encodedFlags);
} }
/** Builds a response Message with no payload, and all the right fields inferred from request Message */ /** Builds a response Message with no payload, and all the right fields inferred from request Message */
@ -431,7 +444,7 @@ public class Message<T> implements ResponseContext
private static Map<ParamType, Object> buildParams(ParamType type, Object value) private static Map<ParamType, Object> buildParams(ParamType type, Object value)
{ {
Map<ParamType, Object> params = NO_PARAMS; EnumMap<ParamType, Object> params = NO_PARAMS;
if (Tracing.isTracing()) if (Tracing.isTracing())
params = Tracing.instance.addTraceHeaders(new EnumMap<>(ParamType.class)); params = Tracing.instance.addTraceHeaders(new EnumMap<>(ParamType.class));
@ -595,6 +608,11 @@ public class Message<T> implements ResponseContext
return !MessageFlag.NOT_FINAL.isIn(flags); return !MessageFlag.NOT_FINAL.isIn(flags);
} }
boolean permitsArtificialLatency()
{
return ARTIFICIAL_LATENCY.isIn(flags);
}
@Nullable @Nullable
ForwardingInfo forwardTo() ForwardingInfo forwardTo()
{ {
@ -665,7 +683,7 @@ public class Message<T> implements ResponseContext
private InetAddressAndPort from; private InetAddressAndPort from;
private T payload; private T payload;
private int flags = 0; private int flags = 0;
private final Map<ParamType, Object> params = new EnumMap<>(ParamType.class); private final EnumMap<ParamType, Object> params = new EnumMap<>(ParamType.class);
private long createdAtNanos; private long createdAtNanos;
private long expiresAtNanos; private long expiresAtNanos;
private long id; private long id;

View File

@ -25,15 +25,16 @@ import static java.lang.Math.max;
public enum MessageFlag public enum MessageFlag
{ {
/** a failure response should be sent back in case of failure */ /** a failure response should be sent back in case of failure */
CALL_BACK_ON_FAILURE (0), CALL_BACK_ON_FAILURE(0),
/** track repaired data - see CASSANDRA-14145 */ /** track repaired data - see CASSANDRA-14145 */
TRACK_REPAIRED_DATA (1), TRACK_REPAIRED_DATA (1),
/** allow creating warnings or aborting queries based off query - see CASSANDRA-16850 */ /** allow creating warnings or aborting queries based off query - see CASSANDRA-16850 */
TRACK_WARNINGS(2), TRACK_WARNINGS (2),
/** whether this message should be sent on an URGENT channel despite its Verb default priority */ /** whether this message should be sent on an URGENT channel despite its Verb default priority */
URGENT(3), URGENT (3),
/** Allow a single callback to receive multiple responses until a final response is received **/ /** Allow a single callback to receive multiple responses until a final response is received **/
NOT_FINAL(4) NOT_FINAL (4),
ARTIFICIAL_LATENCY (5)
; ;
private final int id; private final int id;
@ -54,7 +55,7 @@ public enum MessageFlag
/** /**
* @return new flags value with this flag added * @return new flags value with this flag added
*/ */
int addTo(int flags) public int addTo(int flags)
{ {
return flags | (1 << id); return flags | (1 << id);
} }

View File

@ -18,7 +18,7 @@
package org.apache.cassandra.net; package org.apache.cassandra.net;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.function.BiPredicate; import java.util.function.Predicate;
import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.locator.InetAddressAndPort;
@ -38,22 +38,71 @@ public class OutboundSink
void accept(Message<?> message, InetAddressAndPort to, ConnectionType connectionType); void accept(Message<?> message, InetAddressAndPort to, ConnectionType connectionType);
} }
private static class Filtered implements Sink public interface Filter
{
public boolean test(Message<?> message, InetAddressAndPort to, ConnectionType type);
}
public interface AsyncFilter
{
void filter(Message<?> message, InetAddressAndPort to, ConnectionType type, Sink next);
}
private static abstract class AbstractFiltered implements Sink
{ {
final BiPredicate<Message<?>, InetAddressAndPort> condition;
final Sink next; final Sink next;
private Filtered(BiPredicate<Message<?>, InetAddressAndPort> condition, Sink next) private AbstractFiltered(Sink next)
{ {
this.condition = condition;
this.next = next; this.next = next;
} }
abstract AbstractFiltered withNext(Sink next);
}
private static class Filtered extends AbstractFiltered
{
final Filter condition;
private Filtered(Filter condition, Sink next)
{
super(next);
this.condition = condition;
}
public void accept(Message<?> message, InetAddressAndPort to, ConnectionType connectionType) public void accept(Message<?> message, InetAddressAndPort to, ConnectionType connectionType)
{ {
if (condition.test(message, to)) if (condition.test(message, to, connectionType))
next.accept(message, to, connectionType); next.accept(message, to, connectionType);
} }
@Override
AbstractFiltered withNext(Sink next)
{
return new Filtered(condition, next);
}
}
private static class AsyncFiltered extends AbstractFiltered
{
final AsyncFilter filter;
private AsyncFiltered(AsyncFilter filter, Sink next)
{
super(next);
this.filter = filter;
}
public void accept(Message<?> message, InetAddressAndPort to, ConnectionType connectionType)
{
filter.filter(message, to, connectionType, next);
}
@Override
AbstractFiltered withNext(Sink next)
{
return new AsyncFiltered(filter, next);
}
} }
private volatile Sink sink; private volatile Sink sink;
@ -70,16 +119,26 @@ public class OutboundSink
sink.accept(message, to, connectionType); sink.accept(message, to, connectionType);
} }
public void add(BiPredicate<Message<?>, InetAddressAndPort> allow) public void add(Filter allow)
{ {
sinkUpdater.updateAndGet(this, sink -> new Filtered(allow, sink)); sinkUpdater.updateAndGet(this, sink -> new Filtered(allow, sink));
} }
public void remove(BiPredicate<Message<?>, InetAddressAndPort> allow) public void remove(Filter allow)
{ {
sinkUpdater.updateAndGet(this, sink -> without(sink, allow)); sinkUpdater.updateAndGet(this, sink -> without(sink, allow));
} }
public void add(AsyncFilter filter)
{
sinkUpdater.updateAndGet(this, sink -> new AsyncFiltered(filter, sink));
}
public void remove(AsyncFilter filter)
{
sinkUpdater.updateAndGet(this, sink -> without(sink, filter));
}
public void clear() public void clear()
{ {
sinkUpdater.updateAndGet(this, OutboundSink::clear); sinkUpdater.updateAndGet(this, OutboundSink::clear);
@ -92,17 +151,28 @@ public class OutboundSink
return sink; return sink;
} }
private static Sink without(Sink sink, BiPredicate<Message<?>, InetAddressAndPort> condition) private static Sink without(Sink sink, Filter condition)
{ {
if (!(sink instanceof Filtered)) return without(sink, f -> f instanceof Filtered && condition.equals(((Filtered) f).condition));
return sink;
Filtered filtered = (Filtered) sink;
Sink next = without(filtered.next, condition);
return condition.equals(filtered.condition) ? next
: next == filtered.next
? sink
: new Filtered(filtered.condition, next);
} }
private static Sink without(Sink sink, AsyncFilter filter)
{
return without(sink, f -> f instanceof AsyncFiltered && filter.equals(((AsyncFiltered) f).filter));
}
private static Sink without(Sink sink, Predicate<AbstractFiltered> remove)
{
if (!(sink instanceof AbstractFiltered))
return sink;
AbstractFiltered filtered = (AbstractFiltered) sink;
if (remove.test(filtered))
return filtered.next;
Sink next = without(filtered.next, remove);
if (next == filtered.next)
return filtered;
return filtered.withNext(next);
}
} }

View File

@ -27,4 +27,5 @@ public interface ResponseContext extends ReplyContext
InetAddressAndPort from(); InetAddressAndPort from();
Verb verb(); Verb verb();
long expiresAtNanos(); long expiresAtNanos();
boolean hasFlag(MessageFlag flag);
} }

View File

@ -40,7 +40,7 @@ public class ClientWarn extends ExecutorLocals.Impl
public void set(State value) public void set(State value)
{ {
ExecutorLocals current = ExecutorLocals.current(); ExecutorLocals current = ExecutorLocals.current();
ExecutorLocals.Impl.set(current.traceState, value); ExecutorLocals.Impl.set(current.traceState, value, current.eligibleForArtificialLatency);
} }
public void warn(String text) public void warn(String text)

View File

@ -21,7 +21,6 @@ import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@ -129,6 +128,7 @@ import org.apache.cassandra.metrics.ClientRequestSizeMetrics;
import org.apache.cassandra.metrics.DenylistMetrics; import org.apache.cassandra.metrics.DenylistMetrics;
import org.apache.cassandra.metrics.ReadRepairMetrics; import org.apache.cassandra.metrics.ReadRepairMetrics;
import org.apache.cassandra.metrics.StorageMetrics; import org.apache.cassandra.metrics.StorageMetrics;
import org.apache.cassandra.net.ArtificialLatency;
import org.apache.cassandra.net.ForwardingInfo; import org.apache.cassandra.net.ForwardingInfo;
import org.apache.cassandra.net.Message; import org.apache.cassandra.net.Message;
import org.apache.cassandra.net.MessageFlag; import org.apache.cassandra.net.MessageFlag;
@ -759,7 +759,7 @@ public class StorageProxy implements StorageProxyMBean
if (Iterables.size(missingMRC) > 0) if (Iterables.size(missingMRC) > 0)
{ {
Tracing.trace("Repairing replicas that missed the most recent commit"); Tracing.trace("Repairing replicas that missed the most recent commit");
sendCommit(mostRecent, missingMRC); sendCommit(mostRecent, consistencyForPaxos, missingMRC);
// TODO: provided commits don't invalid the prepare we just did above (which they don't), we could just wait // TODO: provided commits don't invalid the prepare we just did above (which they don't), we could just wait
// for all the missingMRC to acknowledge this commit and then move on with proposing our value. But that means // for all the missingMRC to acknowledge this commit and then move on with proposing our value. But that means
// adding the ability to have commitPaxos block, which is exactly CASSANDRA-5442 will do. So once we have that // adding the ability to have commitPaxos block, which is exactly CASSANDRA-5442 will do. So once we have that
@ -782,7 +782,7 @@ public class StorageProxy implements StorageProxyMBean
/** /**
* Unlike commitPaxos, this does not wait for replies * Unlike commitPaxos, this does not wait for replies
*/ */
private static void sendCommit(Commit commit, Iterable<InetAddressAndPort> replicas) private static void sendCommit(Commit commit, ConsistencyLevel consistencyForPaxos, Iterable<InetAddressAndPort> replicas)
{ {
Message<Commit> message = Message.out(PAXOS_COMMIT_REQ, commit); Message<Commit> message = Message.out(PAXOS_COMMIT_REQ, commit);
for (InetAddressAndPort target : replicas) for (InetAddressAndPort target : replicas)
@ -796,7 +796,6 @@ public class StorageProxy implements StorageProxyMBean
Message<Commit> message = Message.out(PAXOS_PREPARE_REQ, toPrepare); Message<Commit> message = Message.out(PAXOS_PREPARE_REQ, toPrepare);
boolean hasLocalRequest = false; boolean hasLocalRequest = false;
for (Replica replica: replicaPlan.contacts()) for (Replica replica: replicaPlan.contacts())
{ {
if (replica.isSelf()) if (replica.isSelf())
@ -838,6 +837,7 @@ public class StorageProxy implements StorageProxyMBean
{ {
ProposeCallback callback = new ProposeCallback(replicaPlan.contacts().size(), replicaPlan.requiredParticipants(), !backoffIfPartial, replicaPlan.consistencyLevel(), requestTime); ProposeCallback callback = new ProposeCallback(replicaPlan.contacts().size(), replicaPlan.requiredParticipants(), !backoffIfPartial, replicaPlan.consistencyLevel(), requestTime);
Message<Commit> message = Message.out(PAXOS_PROPOSE_REQ, proposal); Message<Commit> message = Message.out(PAXOS_PROPOSE_REQ, proposal);
for (Replica replica : replicaPlan.contacts()) for (Replica replica : replicaPlan.contacts())
{ {
if (replica.isSelf()) if (replica.isSelf())
@ -1862,10 +1862,10 @@ public class StorageProxy implements StorageProxyMBean
// belongs on a different server // belongs on a different server
if (message == null) if (message == null)
{ {
message = Message.outWithFlags(MUTATION_REQ, message = Message.outWithFlag(MUTATION_REQ,
mutation, mutation,
requestTime, requestTime,
Collections.singletonList(MessageFlag.CALL_BACK_ON_FAILURE)); MessageFlag.CALL_BACK_ON_FAILURE);
} }
String dc = DatabaseDescriptor.getLocator().location(destination.endpoint()).datacenter; String dc = DatabaseDescriptor.getLocator().location(destination.endpoint()).datacenter;
@ -2356,7 +2356,7 @@ public class StorageProxy implements StorageProxyMBean
try try
{ {
final ConsistencyLevel consistencyForReplayCommitsOrFetch = consistencyLevel == ConsistencyLevel.LOCAL_SERIAL final ConsistencyLevel consistencyForReplayCommitsOrFetch = consistencyLevel.isDatacenterLocal()
? ConsistencyLevel.LOCAL_QUORUM ? ConsistencyLevel.LOCAL_QUORUM
: ConsistencyLevel.QUORUM; : ConsistencyLevel.QUORUM;
@ -3408,6 +3408,18 @@ public class StorageProxy implements StorageProxyMBean
public Long getTruncateRpcTimeout() { return DatabaseDescriptor.getTruncateRpcTimeout(MILLISECONDS); } public Long getTruncateRpcTimeout() { return DatabaseDescriptor.getTruncateRpcTimeout(MILLISECONDS); }
public void setTruncateRpcTimeout(Long timeoutInMillis) { DatabaseDescriptor.setTruncateRpcTimeout(timeoutInMillis); } public void setTruncateRpcTimeout(Long timeoutInMillis) { DatabaseDescriptor.setTruncateRpcTimeout(timeoutInMillis); }
public boolean getArtificialLatencyEnabled() { return ArtificialLatency.isEnabled(); }
public void setArtificialLatencyEnabled(boolean enabled) { ArtificialLatency.setEnabled(enabled); }
public String getArtificialLatencyVerbs() { return ArtificialLatency.getArtificialLatencyVerbs(); }
public void setArtificialLatencyVerbs(String commaDelimitedVerbs) { ArtificialLatency.setArtificialLatencyVerbs(commaDelimitedVerbs); }
public String getArtificialLatencies() { return ArtificialLatency.getArtificialLatencies(); }
public void setArtificialLatencies(String latencies) { ArtificialLatency.setArtificialLatencies(latencies); }
public boolean getAllowArtificialLatencyForAllConsistencyLevels() { return ArtificialLatency.getArtificialLatencyOnlyPermittedConsistencyLevels(); }
public void setAllowArtificialLatencyForAllConsistencyLevels(boolean onlyPermitted) { ArtificialLatency.setArtificialLatencyOnlyPermittedConsistencyLevels(onlyPermitted); }
public Long getNativeTransportMaxConcurrentConnections() { return DatabaseDescriptor.getNativeTransportMaxConcurrentConnections(); } public Long getNativeTransportMaxConcurrentConnections() { return DatabaseDescriptor.getNativeTransportMaxConcurrentConnections(); }
public void setNativeTransportMaxConcurrentConnections(Long nativeTransportMaxConcurrentConnections) { DatabaseDescriptor.setNativeTransportMaxConcurrentConnections(nativeTransportMaxConcurrentConnections); } public void setNativeTransportMaxConcurrentConnections(Long nativeTransportMaxConcurrentConnections) { DatabaseDescriptor.setNativeTransportMaxConcurrentConnections(nativeTransportMaxConcurrentConnections); }

View File

@ -52,6 +52,15 @@ public interface StorageProxyMBean
public Long getTruncateRpcTimeout(); public Long getTruncateRpcTimeout();
public void setTruncateRpcTimeout(Long timeoutInMillis); public void setTruncateRpcTimeout(Long timeoutInMillis);
public boolean getArtificialLatencyEnabled();
public void setArtificialLatencyEnabled(boolean enabled);
public String getArtificialLatencyVerbs();
public void setArtificialLatencyVerbs(String commaDelimitedVerbs);
public String getArtificialLatencies();
public void setArtificialLatencies(String latencies);
public boolean getAllowArtificialLatencyForAllConsistencyLevels();
public void setAllowArtificialLatencyForAllConsistencyLevels(boolean onlyPermitted);
public void setNativeTransportMaxConcurrentConnections(Long nativeTransportMaxConcurrentConnections); public void setNativeTransportMaxConcurrentConnections(Long nativeTransportMaxConcurrentConnections);
public Long getNativeTransportMaxConcurrentConnections(); public Long getNativeTransportMaxConcurrentConnections();

View File

@ -361,7 +361,6 @@ public abstract class AbstractReadExecutor
if (traceState != null) if (traceState != null)
traceState.trace("speculating read retry on {}", extraReplica); traceState.trace("speculating read retry on {}", extraReplica);
logger.trace("speculating read retry on {}", extraReplica); logger.trace("speculating read retry on {}", extraReplica);
MessagingService.instance().sendWithCallback(retryCommand.createMessage(false, requestTime), extraReplica.endpoint(), handler); MessagingService.instance().sendWithCallback(retryCommand.createMessage(false, requestTime), extraReplica.endpoint(), handler);
} }
} }

View File

@ -23,6 +23,7 @@ import java.io.IOException;
import java.net.InetAddress; import java.net.InetAddress;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Collections; import java.util.Collections;
import java.util.EnumMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
@ -230,7 +231,7 @@ public abstract class Tracing extends ExecutorLocals.Impl
public void set(TraceState tls) public void set(TraceState tls)
{ {
ExecutorLocals current = ExecutorLocals.current(); ExecutorLocals current = ExecutorLocals.current();
ExecutorLocals.Impl.set(tls, current.clientWarnState); ExecutorLocals.Impl.set(tls, current.clientWarnState, current.eligibleForArtificialLatency);
} }
public TraceState begin(final String request, final Map<String, String> parameters) public TraceState begin(final String request, final Map<String, String> parameters)
@ -303,7 +304,7 @@ public abstract class Tracing extends ExecutorLocals.Impl
} }
} }
public Map<ParamType, Object> addTraceHeaders(Map<ParamType, Object> addToMutable) public EnumMap<ParamType, Object> addTraceHeaders(EnumMap<ParamType, Object> addToMutable)
{ {
assert isTracing(); assert isTracing();

View File

@ -30,6 +30,7 @@ import org.apache.cassandra.cql3.QueryHandler;
import org.apache.cassandra.cql3.QueryOptions; import org.apache.cassandra.cql3.QueryOptions;
import org.apache.cassandra.cql3.ResultSet; import org.apache.cassandra.cql3.ResultSet;
import org.apache.cassandra.exceptions.PreparedQueryNotFoundException; import org.apache.cassandra.exceptions.PreparedQueryNotFoundException;
import org.apache.cassandra.net.ArtificialLatency;
import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.QueryState; import org.apache.cassandra.service.QueryState;
import org.apache.cassandra.tracing.Tracing; import org.apache.cassandra.tracing.Tracing;
@ -157,6 +158,9 @@ public class ExecuteMessage extends Message.Request
if (traceRequest) if (traceRequest)
traceQuery(state, prepared); traceQuery(state, prepared);
if (options.isEligibleForArtificialLatency())
ArtificialLatency.setEligibleForArtificialLatency(true);
// Some custom QueryHandlers are interested by the bound names. We provide them this information // Some custom QueryHandlers are interested by the bound names. We provide them this information
// by wrapping the QueryOptions. // by wrapping the QueryOptions.
QueryOptions queryOptions = QueryOptions.addColumnSpecifications(options, prepared.statement.getBindVariables()); QueryOptions queryOptions = QueryOptions.addColumnSpecifications(options, prepared.statement.getBindVariables());

View File

@ -121,6 +121,7 @@ import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.metrics.CassandraMetricsRegistry; import org.apache.cassandra.metrics.CassandraMetricsRegistry;
import org.apache.cassandra.metrics.Sampler; import org.apache.cassandra.metrics.Sampler;
import org.apache.cassandra.metrics.ThreadLocalMetrics; import org.apache.cassandra.metrics.ThreadLocalMetrics;
import org.apache.cassandra.net.ArtificialLatency;
import org.apache.cassandra.net.Message; import org.apache.cassandra.net.Message;
import org.apache.cassandra.net.MessagingService; import org.apache.cassandra.net.MessagingService;
import org.apache.cassandra.net.NoPayload; import org.apache.cassandra.net.NoPayload;
@ -374,7 +375,7 @@ public class Instance extends IsolatedExecutor implements IInvokableInstance
protected void registerMockMessaging(ICluster<?> cluster) protected void registerMockMessaging(ICluster<?> cluster)
{ {
MessagingService.instance().outboundSink.add((message, to) -> { MessagingService.instance().outboundSink.add((message, to, type) -> {
if (!internodeMessagingStarted) if (!internodeMessagingStarted)
{ {
inInstancelogger.debug("Dropping outbound message {} to {} as internode messaging has not been started yet", inInstancelogger.debug("Dropping outbound message {} to {} as internode messaging has not been started yet",
@ -405,7 +406,7 @@ public class Instance extends IsolatedExecutor implements IInvokableInstance
protected void registerOutboundFilter(ICluster cluster) protected void registerOutboundFilter(ICluster cluster)
{ {
MessagingService.instance().outboundSink.add((message, to) -> { MessagingService.instance().outboundSink.add((message, to, type) -> {
if (isShutdown()) if (isShutdown())
return false; // TODO: Simulator needs this to trigger a failure return false; // TODO: Simulator needs this to trigger a failure
int fromNum = config.num(); // since this instance is sending the message, from will always be this instance int fromNum = config.num(); // since this instance is sending the message, from will always be this instance
@ -678,6 +679,7 @@ public class Instance extends IsolatedExecutor implements IInvokableInstance
{ {
partialStartup(cluster); partialStartup(cluster);
} }
ArtificialLatency.touch();
} }
catch (Throwable t) catch (Throwable t)
{ {

View File

@ -32,14 +32,17 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicIntegerArray;
import java.util.function.IntSupplier;
import com.google.common.util.concurrent.RateLimiter; import com.google.common.util.concurrent.RateLimiter;
import org.agrona.collections.IntArrayList;
import org.apache.commons.math3.distribution.ZipfDistribution;
import org.apache.commons.math3.random.JDKRandomGenerator;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -54,14 +57,15 @@ import org.apache.cassandra.distributed.api.IMessageFilters;
import org.apache.cassandra.distributed.shared.DistributedTestBase; import org.apache.cassandra.distributed.shared.DistributedTestBase;
import org.apache.cassandra.net.Verb; import org.apache.cassandra.net.Verb;
import org.apache.cassandra.schema.Schema; import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.service.accord.AccordExecutor;
import org.apache.cassandra.service.accord.AccordKeyspace; import org.apache.cassandra.service.accord.AccordKeyspace;
import org.apache.cassandra.service.accord.AccordService; import org.apache.cassandra.service.accord.AccordService;
import org.apache.cassandra.utils.EstimatedHistogram; import org.apache.cassandra.utils.EstimatedHistogram;
import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException;
import static java.lang.System.currentTimeMillis; import static java.lang.System.currentTimeMillis;
import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.TimeUnit.NANOSECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.apache.cassandra.db.ColumnFamilyStore.FlushReason.UNIT_TESTS; import static org.apache.cassandra.db.ColumnFamilyStore.FlushReason.UNIT_TESTS;
public class AccordLoadTest extends AccordTestBase public class AccordLoadTest extends AccordTestBase
@ -72,27 +76,236 @@ public class AccordLoadTest extends AccordTestBase
public static void setUp() throws IOException public static void setUp() throws IOException
{ {
CassandraRelevantProperties.SIMULATOR_STARTED.setString(Long.toString(MILLISECONDS.toSeconds(currentTimeMillis()))); CassandraRelevantProperties.SIMULATOR_STARTED.setString(Long.toString(MILLISECONDS.toSeconds(currentTimeMillis())));
// AccordTestBase.setupCluster(builder -> builder, 3); int nodeCount = 3;
AccordTestBase.setupCluster(builder -> builder.withConfig(config -> config AccordTestBase.setupCluster(builder -> builder.withDCs(nodeCount).withConfig(config -> {
.with(Feature.NETWORK, Feature.GOSSIP) config.with(Feature.NETWORK, Feature.GOSSIP)
.set("accord.shard_durability_target_splits", "8") .set("accord.shard_durability_target_splits", "8")
.set("accord.shard_durability_max_splits", "16") .set("accord.shard_durability_max_splits", "16")
.set("accord.shard_durability_cycle", "1m") .set("accord.shard_durability_cycle", "1m")
.set("accord.catchup_on_start_fail_latency", "2m") .set("accord.catchup_on_start_fail_latency", "2m");
// .set("accord.ephemeral_read_enabled", "true") }), nodeCount);
), 3);
} }
@Ignore public static class Settings
@Test {
public void testLoad() throws Exception final int repairInterval;
final int compactionInterval;
final int journalFlushInterval;
final int cfkFlushInterval;
final int cfkCompactionPeriodSeconds;
final int dataFlushInterval;
final int restartInterval;
final int restartDecay;
final int batchSize;
final long batchPeriodNanos;
final int clientConcurrency;
final int clients;
final int clientRatePerSecond;
final int keysPerOperation;
final float readRatio;
final IntSupplier keySelector;
final boolean readBeforeWrite;
Settings(SettingsBuilder builder)
{
this.repairInterval = builder.repairInterval;
this.compactionInterval = builder.compactionInterval;
this.journalFlushInterval = builder.journalFlushInterval;
this.cfkFlushInterval = builder.cfkFlushInterval;
this.cfkCompactionPeriodSeconds = builder.cfkCompactionPeriodSeconds;
this.dataFlushInterval = builder.dataFlushInterval;
this.restartInterval = builder.restartInterval;
this.restartDecay = builder.restartDecay;
this.batchSize = builder.batchSize;
this.batchPeriodNanos = builder.batchPeriodNanos;
this.clientConcurrency = builder.clientConcurrency;
this.clients = builder.clients;
this.clientRatePerSecond = builder.ratePerSecond;
this.keysPerOperation = builder.keysPerOperation;
this.readRatio = builder.readChance;
this.keySelector = builder.keySelector;
this.readBeforeWrite = builder.readBeforeWrite;
}
}
// interval is measured in terms of *operations* unless otherwise specified
public static class SettingsBuilder
{
int repairInterval = Integer.MAX_VALUE;
int compactionInterval = Integer.MAX_VALUE;
int journalFlushInterval = Integer.MAX_VALUE;
int cfkFlushInterval = Integer.MAX_VALUE;
int cfkCompactionPeriodSeconds = Integer.MAX_VALUE;
int dataFlushInterval = Integer.MAX_VALUE;
int restartInterval = Integer.MAX_VALUE;
int restartDecay = 2;
int batchSize = 1000;
long batchPeriodNanos = SECONDS.toNanos(10);
int clientConcurrency = 50;
int clients = -1;
int ratePerSecond = 1000;
int keysPerOperation = 1;
float readChance = 0.5f;
IntSupplier keySelector;
boolean readBeforeWrite;
public SettingsBuilder setRepairInterval(int repairInterval)
{
this.repairInterval = repairInterval;
return this;
}
public SettingsBuilder setCompactionInterval(int compactionInterval)
{
this.compactionInterval = compactionInterval;
return this;
}
public SettingsBuilder setJournalFlushInterval(int journalFlushInterval)
{
this.journalFlushInterval = journalFlushInterval;
return this;
}
public SettingsBuilder setCfkFlushInterval(int cfkFlushInterval)
{
this.cfkFlushInterval = cfkFlushInterval;
return this;
}
public SettingsBuilder setCfkCompactionPeriodSeconds(int cfkCompactionPeriodSeconds)
{
this.cfkCompactionPeriodSeconds = cfkCompactionPeriodSeconds;
return this;
}
public SettingsBuilder setDataFlushInterval(int dataFlushInterval)
{
this.dataFlushInterval = dataFlushInterval;
return this;
}
public SettingsBuilder setRestartInterval(int restartInterval)
{
this.restartInterval = restartInterval;
return this;
}
public SettingsBuilder setRestartDecay(int restartDecay)
{
this.restartDecay = restartDecay;
return this;
}
public SettingsBuilder setBatchSize(int batchSize)
{
this.batchSize = batchSize;
return this;
}
public SettingsBuilder setBatchPeriodNanos(long batchPeriodNanos)
{
this.batchPeriodNanos = batchPeriodNanos;
return this;
}
public SettingsBuilder setClientConcurrency(int clientConcurrency)
{
this.clientConcurrency = clientConcurrency;
return this;
}
public SettingsBuilder setClients(int clients)
{
this.clients = clients;
return this;
}
public SettingsBuilder setRatePerSecond(int ratePerSecond)
{
this.ratePerSecond = ratePerSecond;
return this;
}
public SettingsBuilder setKeysPerOperation(int keysPerOperation)
{
this.keysPerOperation = keysPerOperation;
return this;
}
public SettingsBuilder setReadChance(float readChance)
{
this.readChance = readChance;
return this;
}
public SettingsBuilder setReadBeforeWrite(boolean readBeforeWrite)
{
this.readBeforeWrite = readBeforeWrite;
return this;
}
public SettingsBuilder setKeySelector(IntSupplier keySelector)
{
this.keySelector = keySelector;
return this;
}
public Settings build()
{
return new Settings(this);
}
}
private static SettingsBuilder ycsbA(SettingsBuilder builder, int keyCount)
{
return builder.setKeySelector(ycsbZipfian(keyCount))
.setReadChance(0.5f);
}
private static SettingsBuilder ycsbB(SettingsBuilder builder, int keyCount)
{
return builder.setKeySelector(ycsbZipfian(keyCount))
.setReadChance(0.95f);
}
private static SettingsBuilder ycsbC(SettingsBuilder builder, int keyCount)
{
return builder.setKeySelector(ycsbZipfian(keyCount))
.setReadChance(1.0f);
}
private static IntSupplier ycsbZipfian(int keyCount)
{
ZipfDistribution distribution = new ZipfDistribution(new JDKRandomGenerator(), keyCount, 0.99);
return distribution::sample;
}
private static IntSupplier roundrobin(int keyCount)
{
AtomicInteger next = new AtomicInteger();
return () -> {
int v = next.incrementAndGet();
if (v < keyCount)
return v;
return next.updateAndGet(i -> i > keyCount ? 0 : i + 1);
};
}
private static IntSupplier uniform(int keyCount)
{
Random random = new Random();
return () -> random.nextInt(keyCount);
}
public void testLoad(final Settings settings) throws Exception
{ {
Cluster cluster = SHARED_CLUSTER; Cluster cluster = SHARED_CLUSTER;
cluster.schemaChange("CREATE TABLE " + qualifiedAccordTableName + " (k int, v int, PRIMARY KEY(k)) WITH transactional_mode = 'full'"); cluster.schemaChange("CREATE TABLE " + qualifiedAccordTableName + " (k int, v int, PRIMARY KEY(k)) WITH transactional_mode = 'full'");
try try
{ {
final ConcurrentHashMap<Verb, AtomicInteger> verbs = new ConcurrentHashMap<>(); final ConcurrentHashMap<Verb, AtomicInteger> verbs = new ConcurrentHashMap<>();
cluster.filters().outbound().messagesMatching(new IMessageFilters.Matcher() cluster.filters().outbound().messagesMatching(new IMessageFilters.Matcher()
{ {
@ -104,129 +317,138 @@ public class AccordLoadTest extends AccordTestBase
} }
}).drop(); }).drop();
ICoordinator coordinator = cluster.coordinator(1); int clientCount = settings.clients < 0 ? cluster.size() : settings.clients;
final int repairInterval = Integer.MAX_VALUE; long nextRepairAt = settings.repairInterval;
final int compactionInterval = Integer.MAX_VALUE; long nextCompactionAt = settings.compactionInterval;
// final int flushInterval = 50_000; long nextJournalFlushAt = settings.journalFlushInterval;
final int journalFlushInterval = Integer.MAX_VALUE; long nextDataFlushAt = settings.dataFlushInterval;
final int cfkFlushInterval = Integer.MAX_VALUE; long nextCfkFlushAt = settings.cfkFlushInterval;
final int dataFlushInterval = Integer.MAX_VALUE; long nextRestartAt = settings.restartInterval;
// final int compactionPeriodSeconds = 0;
// int restartInterval = 30_000;
int restartInterval = Integer.MAX_VALUE;
final int restartDecay = 2;
// final int restartInterval = Integer.MAX_VALUE;
final int batchSizeLimit = 200;
final long batchTime = TimeUnit.SECONDS.toNanos(10);
final int concurrency = 200;
final int ratePerSecond = 500;
// final int keyCount = 10_000;
final int keyCount = 10_000;
final float readChance = 0.33f;
long nextRepairAt = repairInterval;
long nextCompactionAt = compactionInterval;
long nextJournalFlushAt = journalFlushInterval;
long nextDataFlushAt = dataFlushInterval;
long nextCfkFlushAt = cfkFlushInterval;
long nextRestartAt = restartInterval;
final ExecutorService restartExecutor = Executors.newSingleThreadExecutor(); final ExecutorService restartExecutor = Executors.newSingleThreadExecutor();
final ExecutorService clientExecutor = Executors.newFixedThreadPool(settings.clients);
final BitSet initialised = new BitSet(); final BitSet initialised = new BitSet();
java.util.concurrent.Future<?> restarting = null; java.util.concurrent.Future<?> restarting = null;
cluster.get(1).nodetoolResult("cms", "reconfigure", "3").asserts().success(); cluster.get(1).nodetoolResult("cms", "reconfigure", "3").asserts().success();
// cluster.forEach(i -> i.runOnInstance(() -> { if (settings.cfkCompactionPeriodSeconds > 0)
// if (compactionPeriodSeconds > 0) {
// ((AccordService) AccordService.instance()).journal().compactor().updateCompactionPeriod(1, SECONDS); cluster.forEach(i -> i.runOnInstance(() -> {
// ((AccordSpec.JournalSpec)((AccordService) AccordService.instance()).journal().configuration()).segmentSize = 128 << 10; ((AccordService) AccordService.instance()).journal().compactor().updateCompactionPeriod(settings.cfkCompactionPeriodSeconds, SECONDS);
// })); }));
}
final AtomicBoolean stop = new AtomicBoolean();
Random random = new Random(); Random random = new Random();
final Semaphore inFlight = new Semaphore(concurrency); Semaphore completed = new Semaphore(0);
final RateLimiter rateLimiter = RateLimiter.create(ratePerSecond); AtomicIntegerArray coordinatorIndexes = new AtomicIntegerArray(clientCount);
final List<java.util.concurrent.Future<?>> clients = new ArrayList<>();
final EstimatedHistogram histogram = new EstimatedHistogram(200);
if (settings.clients >= cluster.size())
throw new IllegalArgumentException("Cannot have more clients than nodes");
if (settings.restartInterval < Integer.MAX_VALUE && settings.clients + 1 >= cluster.size())
throw new IllegalArgumentException("If restarting, cannot have as many clients as nodes, as must reroute client requests during restart");
for (int client = 0 ; client < clientCount ; ++client)
{
final int clientIndex = client;
coordinatorIndexes.set(client, client + 1);
final RateLimiter rateLimiter = RateLimiter.create(settings.clientRatePerSecond);
final Semaphore inFlight = new Semaphore(0);
clients.add(clientExecutor.submit(() -> {
while (!stop.get())
{
int coordinatorIdx = coordinatorIndexes.get(clientIndex);
ICoordinator coordinator = cluster.coordinator(coordinatorIdx);
try
{
rateLimiter.acquire();
inFlight.acquire();
long commandStart = System.nanoTime();
IntArrayList keys = new IntArrayList(settings.keysPerOperation, -1);
for (int i = 0 ; i < settings.keysPerOperation ; ++i)
{
int k = settings.keySelector.getAsInt();
if (!keys.containsInt(k))
keys.add(k);
}
if (!keys.intStream().allMatch(initialised::get))
{
coordinator.executeWithResult((success, fail) -> {
inFlight.release();
completed.release();
if (fail == null)
{
histogram.add(NANOSECONDS.toMicros(System.nanoTime() - commandStart));
synchronized (initialised)
{
keys.forEachInt(initialised::set);
}
}
else
{
logger.error("{}", fail.getMessage());
}
}, "UPDATE " + qualifiedAccordTableName + " SET v = 0 WHERE k IN ?", ConsistencyLevel.SERIAL, ConsistencyLevel.QUORUM, keys);
}
else if (random.nextFloat() < settings.readRatio)
{
coordinator.executeWithResult((success, fail) -> {
inFlight.release();
completed.release();
if (fail == null)
histogram.add(NANOSECONDS.toMicros(System.nanoTime() - commandStart));
}, "BEGIN TRANSACTION\n" +
"SELECT * FROM " + qualifiedAccordTableName + " WHERE k IN ?;\n" +
"COMMIT TRANSACTION;", ConsistencyLevel.SERIAL, keys
);
}
else
{
coordinator.executeWithResult((success, fail) -> {
inFlight.release();
completed.release();
if (fail == null)
histogram.add(NANOSECONDS.toMicros(System.nanoTime() - commandStart));
else
logger.error("{}", fail.getMessage());
}, "BEGIN TRANSACTION\n" +
// "UPDATE " + qualifiedAccordTableName + " SET v = ? WHERE k = ?;\n" +
"UPDATE " + qualifiedAccordTableName + " SET v += ? WHERE k IN ?;\n" +
"COMMIT TRANSACTION;", ConsistencyLevel.SERIAL, ConsistencyLevel.QUORUM, random.nextInt(100), keys);
}
}
catch (RejectedExecutionException e)
{
inFlight.release();
}
catch (InterruptedException e)
{
throw new UncheckedInterruptedException(e);
}
}
}));
}
while (true) while (true)
{ {
final EstimatedHistogram histogram = new EstimatedHistogram(200);
long batchStart = System.nanoTime(); long batchStart = System.nanoTime();
long batchEnd = batchStart + batchTime;
int batchSize = 0; int batchSize = 0;
while (batchSize < batchSizeLimit)
{ if (completed.tryAcquire(settings.batchSize, settings.batchPeriodNanos, NANOSECONDS))
inFlight.acquire(); batchSize = settings.batchSize;
rateLimiter.acquire(); batchSize += completed.drainPermits();
try
{
long commandStart = System.nanoTime();
int k1 = random.nextInt(keyCount);
int k2 = random.nextInt(keyCount);
if (random.nextFloat() < readChance)
{
coordinator.executeWithResult((success, fail) -> {
inFlight.release();
if (fail == null) histogram.add(NANOSECONDS.toMicros(System.nanoTime() - commandStart));
}, "BEGIN TRANSACTION\n" +
"SELECT * FROM " + qualifiedAccordTableName + " WHERE k IN ?;\n" +
"COMMIT TRANSACTION;", ConsistencyLevel.SERIAL,
List.of(k1, k2)
// List.of(k1)
);
}
else if (initialised.get(k1) && initialised.get(k2))
{
coordinator.executeWithResult((success, fail) -> {
inFlight.release();
if (fail == null) histogram.add(NANOSECONDS.toMicros(System.nanoTime() - commandStart));
}, "BEGIN TRANSACTION\n" +
"UPDATE " + qualifiedAccordTableName + " SET v += 1 WHERE k = ?;\n" +
"UPDATE " + qualifiedAccordTableName + " SET v += 1 WHERE k = ?;\n" +
"COMMIT TRANSACTION;", ConsistencyLevel.SERIAL, ConsistencyLevel.QUORUM, k1, k2);
}
else
{
initialised.set(k1);
initialised.set(k2);
coordinator.executeWithResult((success, fail) -> {
inFlight.release();
if (fail == null) histogram.add(NANOSECONDS.toMicros(System.nanoTime() - commandStart));
// else exceptions.add(fail);
}, "UPDATE " + qualifiedAccordTableName + " SET v = 0 WHERE k = ? IF NOT EXISTS;", ConsistencyLevel.SERIAL, ConsistencyLevel.QUORUM, k1);
coordinator.executeWithResult((success, fail) -> {
inFlight.release();
if (fail == null) histogram.add(NANOSECONDS.toMicros(System.nanoTime() - commandStart));
// else exceptions.add(fail);
}, "UPDATE " + qualifiedAccordTableName + " SET v = 0 WHERE k = ? IF NOT EXISTS;", ConsistencyLevel.SERIAL, ConsistencyLevel.QUORUM, k2);
}
}
catch (RejectedExecutionException e)
{
inFlight.release();
while (true)
{
try
{
int index = 1 + random.nextInt(cluster.size());
logger.info("Picking new coordinator ... {}", index);
coordinator = cluster.coordinator(index);
if (cluster.get(index).callOnInstance(() -> AccordService.started()))
break;
}
catch (Throwable t) { logger.info("Failed to select coordinator", t); }
}
}
batchSize++;
if (System.nanoTime() >= batchEnd)
break;
}
if ((nextRepairAt -= batchSize) <= 0) if ((nextRepairAt -= batchSize) <= 0)
{ {
nextRepairAt += repairInterval; nextRepairAt += settings.repairInterval;
System.out.println("repairing..."); System.out.println("repairing...");
cluster.coordinator(1).instance().nodetool("repair", qualifiedAccordTableName); cluster.coordinator(1).instance().nodetool("repair", qualifiedAccordTableName);
} }
if ((nextCompactionAt -= batchSize) <= 0) if ((nextCompactionAt -= batchSize) <= 0)
{ {
nextCompactionAt += compactionInterval; nextCompactionAt += settings.compactionInterval;
System.out.println("compacting accord..."); System.out.println("compacting accord...");
cluster.forEach(i -> { cluster.forEach(i -> {
try { i.nodetool("compact", "system_accord.journal"); } try { i.nodetool("compact", "system_accord.journal"); }
@ -236,7 +458,7 @@ public class AccordLoadTest extends AccordTestBase
if ((nextJournalFlushAt -= batchSize) <= 0) if ((nextJournalFlushAt -= batchSize) <= 0)
{ {
nextJournalFlushAt += journalFlushInterval; nextJournalFlushAt += settings.journalFlushInterval;
System.out.println("flushing journal..."); System.out.println("flushing journal...");
cluster.forEach(i -> { cluster.forEach(i -> {
try try
@ -258,7 +480,7 @@ public class AccordLoadTest extends AccordTestBase
if ((nextDataFlushAt -= batchSize) <= 0) if ((nextDataFlushAt -= batchSize) <= 0)
{ {
nextDataFlushAt += dataFlushInterval; nextDataFlushAt += settings.dataFlushInterval;
System.out.println("flushing data..."); System.out.println("flushing data...");
cluster.forEach(i -> { cluster.forEach(i -> {
try try
@ -276,7 +498,7 @@ public class AccordLoadTest extends AccordTestBase
if ((nextCfkFlushAt -= batchSize) <= 0) if ((nextCfkFlushAt -= batchSize) <= 0)
{ {
nextCfkFlushAt += cfkFlushInterval; nextCfkFlushAt += settings.cfkFlushInterval;
System.out.println("flushing data..."); System.out.println("flushing data...");
cluster.forEach(i -> { cluster.forEach(i -> {
try try
@ -300,8 +522,25 @@ public class AccordLoadTest extends AccordTestBase
if (restarting != null) if (restarting != null)
restarting.get(); restarting.get();
nextRestartAt += restartInterval; nextRestartAt += settings.restartInterval;
int nodeIdx = 1 + random.nextInt(cluster.size()); int nodeIdx = 1 + random.nextInt(cluster.size());
out: for (int i = 0 ; i < coordinatorIndexes.length() ; ++i)
{
if (nodeIdx == coordinatorIndexes.get(i))
{
cont: while (true)
{
int replaceIdx = 1 + random.nextInt(cluster.size());
for (int j = 0 ; j < coordinatorIndexes.length() ; ++j)
{
if (coordinatorIndexes.get(j) == replaceIdx)
continue cont;
}
coordinatorIndexes.set(i, replaceIdx);
break out;
}
}
}
restarting = restartExecutor.submit(() -> { restarting = restartExecutor.submit(() -> {
System.out.printf("restarting node %d...\n", nodeIdx); System.out.printf("restarting node %d...\n", nodeIdx);
try try
@ -315,20 +554,12 @@ public class AccordLoadTest extends AccordTestBase
throw new RuntimeException(e); throw new RuntimeException(e);
} }
}); });
if (nodeIdx == coordinator.instance().config().num())
coordinator = cluster.coordinator((nodeIdx % cluster.size()) + 1);
} }
} }
final Date date = new Date(); final Date date = new Date();
System.out.printf("%tT rate: %.2f/s (%d total)\n", date, (((float)batchSizeLimit * 1000) / NANOSECONDS.toMillis(System.nanoTime() - batchStart)), batchSize); System.out.printf("%tT rate: %.2f/s (%d total)\n", date, (((float)settings.batchSize * 1000) / NANOSECONDS.toMillis(System.nanoTime() - batchStart)), batchSize);
System.out.printf("%tT percentiles: %d %d %d %d\n", date, histogram.percentile(.25)/1000, histogram.percentile(.5)/1000, histogram.percentile(.75)/1000, histogram.percentile(1)/1000); System.out.printf("%tT percentiles: %d %d %d %d\n", date, histogram.percentile(.25)/1000, histogram.percentile(.5)/1000, histogram.percentile(.95)/1000, histogram.percentile(.99)/1000, histogram.percentile(1)/1000);
cluster.forEach(() -> {
String waiting = "";
for (AccordExecutor executor : AccordService.instance().executors())
waiting += executor.unsafeWaitingToRunCount() + " ";
System.out.println(waiting);
});
class VerbCount class VerbCount
{ {
@ -382,6 +613,6 @@ public class AccordLoadTest extends AccordTestBase
AccordLoadTest.setUp(); AccordLoadTest.setUp();
AccordLoadTest test = new AccordLoadTest(); AccordLoadTest test = new AccordLoadTest();
test.setup(); test.setup();
test.testLoad(); test.testLoad(ycsbA(new SettingsBuilder(), 1000).build());
} }
} }

View File

@ -46,6 +46,7 @@ import org.apache.cassandra.dht.IPartitioner;
import org.apache.cassandra.dht.Murmur3Partitioner; import org.apache.cassandra.dht.Murmur3Partitioner;
import org.apache.cassandra.dht.Token; import org.apache.cassandra.dht.Token;
import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.net.ConnectionType;
import org.apache.cassandra.net.Message; import org.apache.cassandra.net.Message;
import org.apache.cassandra.net.MessagingService; import org.apache.cassandra.net.MessagingService;
import org.apache.cassandra.net.Verb; import org.apache.cassandra.net.Verb;
@ -1155,7 +1156,7 @@ public class ClusterMetadataTestHelper
final SettableFuture<MessageDelivery> future = SettableFuture.create(); final SettableFuture<MessageDelivery> future = SettableFuture.create();
Set<Verb> ignore = Sets.newHashSet(ignored); Set<Verb> ignore = Sets.newHashSet(ignored);
MessagingService.instance().outboundSink.clear(); MessagingService.instance().outboundSink.clear();
MessagingService.instance().outboundSink.add((Message<?> message, InetAddressAndPort to) -> MessagingService.instance().outboundSink.add((Message<?> message, InetAddressAndPort to, ConnectionType type) ->
{ {
if (!ignore.contains(message.verb())) if (!ignore.contains(message.verb()))
future.set(new MessageDelivery(message, to)); future.set(new MessageDelivery(message, to));

View File

@ -25,7 +25,7 @@ import static org.apache.cassandra.concurrent.ExecutorFactory.Global.executorFac
public class LocalAwareExecutorPlusTest extends AbstractExecutorPlusTest public class LocalAwareExecutorPlusTest extends AbstractExecutorPlusTest
{ {
final ExecutorLocals locals = new ExecutorLocals(null, null); final ExecutorLocals locals = new ExecutorLocals(null, null, false);
@Test @Test
public void testPooled() throws Throwable public void testPooled() throws Throwable

View File

@ -91,7 +91,7 @@ public class ReadCommandVerbHandlerOutOfRangeTest
MessagingService.instance().inboundSink.clear(); MessagingService.instance().inboundSink.clear();
MessagingService.instance().outboundSink.clear(); MessagingService.instance().outboundSink.clear();
MessagingService.instance().outboundSink.add((message, to) -> false); MessagingService.instance().outboundSink.add((message, to, type) -> false);
MessagingService.instance().inboundSink.add((message) -> false); MessagingService.instance().inboundSink.add((message) -> false);
cfs = Keyspace.open(KEYSPACE_NONREPLICATED).getColumnFamilyStore(TABLE); cfs = Keyspace.open(KEYSPACE_NONREPLICATED).getColumnFamilyStore(TABLE);

View File

@ -92,7 +92,7 @@ public class ReadCommandVerbHandlerTest
{ {
MessagingService.instance().inboundSink.clear(); MessagingService.instance().inboundSink.clear();
MessagingService.instance().outboundSink.clear(); MessagingService.instance().outboundSink.clear();
MessagingService.instance().outboundSink.add((message, to) -> false); MessagingService.instance().outboundSink.add((message, to, type) -> false);
MessagingService.instance().inboundSink.add((message) -> false); MessagingService.instance().inboundSink.add((message) -> false);
handler = new ReadCommandVerbHandler(); handler = new ReadCommandVerbHandler();

View File

@ -62,7 +62,7 @@ public class AbstractPendingRepairTest extends AbstractRepairTest
LocalSessionAccessor.startup(); LocalSessionAccessor.startup();
// cutoff messaging service // cutoff messaging service
MessagingService.instance().outboundSink.add((message, to) -> false); MessagingService.instance().outboundSink.add((message, to, type) -> false);
MessagingService.instance().inboundSink.add((message) -> false); MessagingService.instance().inboundSink.add((message) -> false);
} }

View File

@ -505,7 +505,7 @@ public class CompactionIteratorTest extends CQLTester
TableMetadata metadata = getCurrentColumnFamilyStore().metadata(); TableMetadata metadata = getCurrentColumnFamilyStore().metadata();
final HashMap<InetAddressAndPort, Message<?>> sentMessages = new HashMap<>(); final HashMap<InetAddressAndPort, Message<?>> sentMessages = new HashMap<>();
MessagingService.instance().outboundSink.add((message, to) -> { sentMessages.put(to, message); return false;}); MessagingService.instance().outboundSink.add((message, to, type) -> { sentMessages.put(to, message); return false;});
// no duplicates // no duplicates
sentMessages.clear(); sentMessages.clear();

View File

@ -135,7 +135,7 @@ public class CompressionDictionaryEventHandlerTest
AtomicReference<CompressionDictionaryUpdateMessage> capturedMessage = new AtomicReference<>(); AtomicReference<CompressionDictionaryUpdateMessage> capturedMessage = new AtomicReference<>();
// Capture outbound messages // Capture outbound messages
MessagingService.instance().outboundSink.add((message, to) -> { MessagingService.instance().outboundSink.add((message, to, type) -> {
if (message.verb() == Verb.DICTIONARY_UPDATE_REQ) if (message.verb() == Verb.DICTIONARY_UPDATE_REQ)
{ {
capturedMessage.set((CompressionDictionaryUpdateMessage) message.payload); capturedMessage.set((CompressionDictionaryUpdateMessage) message.payload);
@ -213,7 +213,7 @@ public class CompressionDictionaryEventHandlerTest
public void testSendNotificationRobustness() public void testSendNotificationRobustness()
{ {
// Test that sending notifications doesn't throw even if messaging fails // Test that sending notifications doesn't throw even if messaging fails
MessagingService.instance().outboundSink.add((message, to) -> { MessagingService.instance().outboundSink.add((message, to, type) -> {
if (message.verb() == Verb.DICTIONARY_UPDATE_REQ) if (message.verb() == Verb.DICTIONARY_UPDATE_REQ)
{ {
throw new RuntimeException("Simulated messaging failure"); throw new RuntimeException("Simulated messaging failure");

View File

@ -71,7 +71,7 @@ public class DuplicateRowCheckerTest extends CQLTester
public static void setupMessaging() public static void setupMessaging()
{ {
sentMessages = new HashMap<>(); sentMessages = new HashMap<>();
MessagingService.instance().outboundSink.add((message, to) -> { sentMessages.put(to, message); return false;}); MessagingService.instance().outboundSink.add((message, to, type) -> { sentMessages.put(to, message); return false;});
} }
@Before @Before

View File

@ -29,7 +29,6 @@ import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.BiPredicate;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -78,8 +77,10 @@ import org.apache.cassandra.dht.Murmur3Partitioner.LongToken;
import org.apache.cassandra.exceptions.ExceptionSerializer; import org.apache.cassandra.exceptions.ExceptionSerializer;
import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.locator.InetAddressAndPort;
import org.apache.cassandra.net.ConnectionType;
import org.apache.cassandra.net.Message; import org.apache.cassandra.net.Message;
import org.apache.cassandra.net.MessagingService; import org.apache.cassandra.net.MessagingService;
import org.apache.cassandra.net.OutboundSink;
import org.apache.cassandra.net.Verb; import org.apache.cassandra.net.Verb;
import org.apache.cassandra.schema.Schema; import org.apache.cassandra.schema.Schema;
import org.apache.cassandra.schema.SchemaConstants; import org.apache.cassandra.schema.SchemaConstants;
@ -1098,7 +1099,7 @@ public class AccordDebugKeyspaceTest extends CQLTester
return (AccordService) AccordService.instance(); return (AccordService) AccordService.instance();
} }
private static class AccordMsgFilter implements BiPredicate<Message<?>, InetAddressAndPort> private static class AccordMsgFilter implements OutboundSink.Filter
{ {
volatile Condition preAccept = Condition.newOneTimeCondition(); volatile Condition preAccept = Condition.newOneTimeCondition();
volatile Condition commit = Condition.newOneTimeCondition(); volatile Condition commit = Condition.newOneTimeCondition();
@ -1123,7 +1124,7 @@ public class AccordDebugKeyspaceTest extends CQLTester
} }
@Override @Override
public boolean test(Message<?> msg, InetAddressAndPort to) public boolean test(Message<?> msg, InetAddressAndPort to, ConnectionType type)
{ {
if (!msg.verb().name().startsWith("ACCORD_")) if (!msg.verb().name().startsWith("ACCORD_"))
return true; return true;

View File

@ -25,7 +25,6 @@ import java.util.Queue;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Function; import java.util.function.Function;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
@ -45,7 +44,7 @@ public class MatcherResponse implements Closeable
Multimaps.newListMultimap(new HashMap<>(), ArrayList::new); Multimaps.newListMultimap(new HashMap<>(), ArrayList::new);
private final MockMessagingSpy spy = new MockMessagingSpy(); private final MockMessagingSpy spy = new MockMessagingSpy();
private final AtomicInteger limitCounter = new AtomicInteger(Integer.MAX_VALUE); private final AtomicInteger limitCounter = new AtomicInteger(Integer.MAX_VALUE);
private BiPredicate<Message<?>, InetAddressAndPort> sink; private OutboundSink.Filter sink;
MatcherResponse(Matcher<?> matcher) MatcherResponse(Matcher<?> matcher)
{ {
@ -160,9 +159,10 @@ public class MatcherResponse implements Closeable
assert sink == null: "destroy() must be called first to register new response"; assert sink == null: "destroy() must be called first to register new response";
sink = new BiPredicate<Message<?>, InetAddressAndPort>() sink = new OutboundSink.Filter()
{ {
public boolean test(Message message, InetAddressAndPort to) @Override
public boolean test(Message message, InetAddressAndPort to, ConnectionType type)
{ {
// prevent outgoing message from being send in case matcher indicates a match // prevent outgoing message from being send in case matcher indicates a match
// and instead send the mocked response // and instead send the mocked response

View File

@ -42,6 +42,7 @@ import org.apache.cassandra.net.MessagingService.Version;
import org.apache.cassandra.tcm.Epoch; import org.apache.cassandra.tcm.Epoch;
import org.apache.cassandra.tracing.Tracing; import org.apache.cassandra.tracing.Tracing;
import org.apache.cassandra.tracing.Tracing.TraceType; import org.apache.cassandra.tracing.Tracing.TraceType;
import org.apache.cassandra.transport.Dispatcher;
import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.FreeRunningClock; import org.apache.cassandra.utils.FreeRunningClock;
import org.apache.cassandra.utils.TimeUUID; import org.apache.cassandra.utils.TimeUUID;
@ -55,6 +56,7 @@ import static org.apache.cassandra.net.NoPayload.noPayload;
import static org.apache.cassandra.net.ParamType.RESPOND_TO; import static org.apache.cassandra.net.ParamType.RESPOND_TO;
import static org.apache.cassandra.net.ParamType.TRACE_SESSION; import static org.apache.cassandra.net.ParamType.TRACE_SESSION;
import static org.apache.cassandra.net.ParamType.TRACE_TYPE; import static org.apache.cassandra.net.ParamType.TRACE_TYPE;
import static org.apache.cassandra.utils.Clock.Global.nanoTime;
import static org.apache.cassandra.utils.MonotonicClock.Global.approxTime; import static org.apache.cassandra.utils.MonotonicClock.Global.approxTime;
import static org.apache.cassandra.utils.TimeUUID.Generator.nextTimeUUID; import static org.apache.cassandra.utils.TimeUUID.Generator.nextTimeUUID;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@ -193,7 +195,7 @@ public class MessageTest
{ {
testCycle(Message.out(Verb._TEST_2, 42)); testCycle(Message.out(Verb._TEST_2, 42));
testCycle(Message.outWithFlag(Verb._TEST_2, 42, MessageFlag.CALL_BACK_ON_FAILURE)); testCycle(Message.outWithFlag(Verb._TEST_2, 42, MessageFlag.CALL_BACK_ON_FAILURE));
testCycle(Message.outWithFlags(Verb._TEST_2, 42, MessageFlag.CALL_BACK_ON_FAILURE, MessageFlag.TRACK_REPAIRED_DATA)); testCycle(Message.outWithFlags(Verb._TEST_2, 42, new Dispatcher.RequestTime(nanoTime()), MessageFlag.CALL_BACK_ON_FAILURE.addTo(MessageFlag.TRACK_REPAIRED_DATA.addTo(0))));
testCycle(Message.outWithParam(1, Verb._TEST_2, 42, RESPOND_TO, FBUtilities.getBroadcastAddressAndPort())); testCycle(Message.outWithParam(1, Verb._TEST_2, 42, RESPOND_TO, FBUtilities.getBroadcastAddressAndPort()));
} }

View File

@ -23,7 +23,6 @@ import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.BiPredicate;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
@ -211,7 +210,7 @@ public class StartupClusterConnectivityCheckerTest
} }
} }
private static class Sink implements BiPredicate<Message<?>, InetAddressAndPort> private static class Sink implements OutboundSink.Filter
{ {
private final boolean markAliveInGossip; private final boolean markAliveInGossip;
private final boolean processConnectAck; private final boolean processConnectAck;
@ -227,7 +226,7 @@ public class StartupClusterConnectivityCheckerTest
} }
@Override @Override
public boolean test(Message message, InetAddressAndPort to) public boolean test(Message message, InetAddressAndPort to, ConnectionType type)
{ {
ConnectionTypeRecorder recorder = seenConnectionRequests.computeIfAbsent(to, inetAddress -> new ConnectionTypeRecorder()); ConnectionTypeRecorder recorder = seenConnectionRequests.computeIfAbsent(to, inetAddress -> new ConnectionTypeRecorder());

View File

@ -880,7 +880,7 @@ public class RepairJobTest
List<Message<?>> messageCapture) List<Message<?>> messageCapture)
{ {
MessagingService.instance().inboundSink.add(message -> message.verb().isResponse()); MessagingService.instance().inboundSink.add(message -> message.verb().isResponse());
MessagingService.instance().outboundSink.add((message, to) -> { MessagingService.instance().outboundSink.add((message, to, type) -> {
if (message == null || !(message.payload instanceof RepairMessage)) if (message == null || !(message.payload instanceof RepairMessage))
return false; return false;

View File

@ -375,7 +375,7 @@ public class ValidatorTest
private CompletableFuture<Message> registerOutgoingMessageSink() private CompletableFuture<Message> registerOutgoingMessageSink()
{ {
final CompletableFuture<Message> future = new CompletableFuture<>(); final CompletableFuture<Message> future = new CompletableFuture<>();
MessagingService.instance().outboundSink.add((message, to) -> future.complete(message)); MessagingService.instance().outboundSink.add((message, to, type) -> future.complete(message));
return future; return future;
} }
} }