diff --git a/CHANGES.txt b/CHANGES.txt index d320b77349..26e0297db5 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -5686,7 +5686,7 @@ Merged from 1.0: * avoid recursion in leveled compaction (CASSANDRA-4587) * increase stack size under Java7 to 180K * Log(info) schema changes (CASSANDRA-4547) - * Change nodetool setcachecapcity to manipulate global caches (CASSANDRA-4563) + * Change nodetool setcachecapacity to manipulate global caches (CASSANDRA-4563) * (cql3) fix setting compaction strategy (CASSANDRA-4597) * fix broken system.schema_* timestamps on system startup (CASSANDRA-4561) * fix wrong skip of cache saving (CASSANDRA-4533) diff --git a/src/java/org/apache/cassandra/auth/CIDRGroupsMappingManager.java b/src/java/org/apache/cassandra/auth/CIDRGroupsMappingManager.java index 6659bdcc15..e258b50a3a 100644 --- a/src/java/org/apache/cassandra/auth/CIDRGroupsMappingManager.java +++ b/src/java/org/apache/cassandra/auth/CIDRGroupsMappingManager.java @@ -47,14 +47,13 @@ import org.apache.cassandra.db.marshal.TupleType; import org.apache.cassandra.exceptions.RequestExecutionException; import org.apache.cassandra.schema.SchemaConstants; import org.apache.cassandra.service.ClientState; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.transport.messages.ResultMessage; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.MBeanWrapper; import org.apache.cassandra.utils.Pair; import static org.apache.cassandra.service.QueryState.forInternalCalls; -import static org.apache.cassandra.utils.Clock.Global.nanoTime; - public class CIDRGroupsMappingManager implements CIDRGroupsMappingManagerMBean { @@ -89,7 +88,7 @@ public class CIDRGroupsMappingManager implements CIDRGroupsMappingManagerMBean @VisibleForTesting ResultMessage.Rows select(SelectStatement statement, QueryOptions options) { - return statement.execute(forInternalCalls(), options, nanoTime()); + return statement.execute(forInternalCalls(), options, Dispatcher.RequestTime.forImmediateExecution()); } UntypedResultSet process(String query, ConsistencyLevel cl) throws RequestExecutionException diff --git a/src/java/org/apache/cassandra/auth/CIDRPermissionsManager.java b/src/java/org/apache/cassandra/auth/CIDRPermissionsManager.java index 459d845bee..8476d7b865 100644 --- a/src/java/org/apache/cassandra/auth/CIDRPermissionsManager.java +++ b/src/java/org/apache/cassandra/auth/CIDRPermissionsManager.java @@ -41,12 +41,12 @@ import org.apache.cassandra.exceptions.RequestExecutionException; import org.apache.cassandra.schema.SchemaConstants; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.reads.range.RangeCommands; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.transport.messages.ResultMessage; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.MBeanWrapper; import static org.apache.cassandra.service.QueryState.forInternalCalls; -import static org.apache.cassandra.utils.Clock.Global.nanoTime; /** * Provides functionality to list/update/drop CIDR permissions of a role @@ -76,7 +76,7 @@ public class CIDRPermissionsManager implements CIDRPermissionsManagerMBean, Auth @VisibleForTesting ResultMessage.Rows select(SelectStatement statement, QueryOptions options) { - return statement.execute(forInternalCalls(), options, nanoTime()); + return statement.execute(forInternalCalls(), options, Dispatcher.RequestTime.forImmediateExecution()); } @VisibleForTesting diff --git a/src/java/org/apache/cassandra/auth/CassandraAuthorizer.java b/src/java/org/apache/cassandra/auth/CassandraAuthorizer.java index b3f85e8621..153ec0e5fe 100644 --- a/src/java/org/apache/cassandra/auth/CassandraAuthorizer.java +++ b/src/java/org/apache/cassandra/auth/CassandraAuthorizer.java @@ -46,12 +46,11 @@ import org.apache.cassandra.schema.SchemaConstants; import org.apache.cassandra.cql3.statements.SelectStatement; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.QueryState; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.transport.messages.ResultMessage; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.Pair; -import static org.apache.cassandra.utils.Clock.Global.nanoTime; - /** * CassandraAuthorizer is an IAuthorizer implementation that keeps * user permissions internally in C* using the system_auth.role_permissions @@ -421,7 +420,7 @@ public class CassandraAuthorizer implements IAuthorizer ResultMessage.Rows select(SelectStatement statement, QueryOptions options) { - return statement.execute(QueryState.forInternalCalls(), options, nanoTime()); + return statement.execute(QueryState.forInternalCalls(), options, Dispatcher.RequestTime.forImmediateExecution()); } /** @@ -439,7 +438,7 @@ public class CassandraAuthorizer implements IAuthorizer QueryProcessor.instance.processBatch(statement, QueryState.forInternalCalls(), BatchQueryOptions.withoutPerStatementVariables(options), - nanoTime()); + Dispatcher.RequestTime.forImmediateExecution()); } public static ConsistencyLevel authWriteConsistencyLevel() diff --git a/src/java/org/apache/cassandra/auth/CassandraNetworkAuthorizer.java b/src/java/org/apache/cassandra/auth/CassandraNetworkAuthorizer.java index cc08e93c22..62e6b775af 100644 --- a/src/java/org/apache/cassandra/auth/CassandraNetworkAuthorizer.java +++ b/src/java/org/apache/cassandra/auth/CassandraNetworkAuthorizer.java @@ -39,11 +39,11 @@ import org.apache.cassandra.db.marshal.UTF8Type; import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.schema.SchemaConstants; import org.apache.cassandra.service.ClientState; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.transport.messages.ResultMessage; import org.apache.cassandra.utils.ByteBufferUtil; import static org.apache.cassandra.service.QueryState.forInternalCalls; -import static org.apache.cassandra.utils.Clock.Global.nanoTime; public class CassandraNetworkAuthorizer implements INetworkAuthorizer { @@ -61,7 +61,7 @@ public class CassandraNetworkAuthorizer implements INetworkAuthorizer @VisibleForTesting ResultMessage.Rows select(SelectStatement statement, QueryOptions options) { - return statement.execute(forInternalCalls(), options, nanoTime()); + return statement.execute(forInternalCalls(), options, Dispatcher.RequestTime.forImmediateExecution()); } /** diff --git a/src/java/org/apache/cassandra/auth/CassandraRoleManager.java b/src/java/org/apache/cassandra/auth/CassandraRoleManager.java index 3221c85184..0e9c0b5640 100644 --- a/src/java/org/apache/cassandra/auth/CassandraRoleManager.java +++ b/src/java/org/apache/cassandra/auth/CassandraRoleManager.java @@ -45,6 +45,7 @@ import org.apache.cassandra.exceptions.*; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.StorageProxy; import org.apache.cassandra.service.StorageService; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.transport.messages.ResultMessage; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.NoSpamLogger; @@ -52,7 +53,6 @@ import org.mindrot.jbcrypt.BCrypt; import static org.apache.cassandra.config.CassandraRelevantProperties.AUTH_BCRYPT_GENSALT_LOG2_ROUNDS; import static org.apache.cassandra.service.QueryState.forInternalCalls; -import static org.apache.cassandra.utils.Clock.Global.nanoTime; /** * Responsible for the creation, maintenance and deletion of roles @@ -678,7 +678,7 @@ public class CassandraRoleManager implements IRoleManager @VisibleForTesting ResultMessage.Rows select(SelectStatement statement, QueryOptions options) { - return statement.execute(forInternalCalls(), options, nanoTime()); + return statement.execute(forInternalCalls(), options, Dispatcher.RequestTime.forImmediateExecution()); } @Override diff --git a/src/java/org/apache/cassandra/auth/PasswordAuthenticator.java b/src/java/org/apache/cassandra/auth/PasswordAuthenticator.java index b96aabc76a..a54282a7a0 100644 --- a/src/java/org/apache/cassandra/auth/PasswordAuthenticator.java +++ b/src/java/org/apache/cassandra/auth/PasswordAuthenticator.java @@ -43,12 +43,12 @@ import org.apache.cassandra.exceptions.AuthenticationException; import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.QueryState; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.transport.messages.ResultMessage; import org.apache.cassandra.utils.ByteBufferUtil; import org.mindrot.jbcrypt.BCrypt; import static org.apache.cassandra.auth.CassandraRoleManager.consistencyForRoleRead; -import static org.apache.cassandra.utils.Clock.Global.nanoTime; /** * PasswordAuthenticator is an IAuthenticator implementation @@ -198,7 +198,7 @@ public class PasswordAuthenticator implements IAuthenticator, AuthCache.BulkLoad @VisibleForTesting ResultMessage.Rows select(SelectStatement statement, QueryOptions options) { - return statement.execute(QueryState.forInternalCalls(), options, nanoTime()); + return statement.execute(QueryState.forInternalCalls(), options, Dispatcher.RequestTime.forImmediateExecution()); } public Set protectedResources() diff --git a/src/java/org/apache/cassandra/batchlog/BatchlogManager.java b/src/java/org/apache/cassandra/batchlog/BatchlogManager.java index b7b25cb7b2..fd98a0fec8 100644 --- a/src/java/org/apache/cassandra/batchlog/BatchlogManager.java +++ b/src/java/org/apache/cassandra/batchlog/BatchlogManager.java @@ -36,6 +36,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Iterables; import com.google.common.util.concurrent.RateLimiter; import org.apache.cassandra.concurrent.ScheduledExecutorPlus; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.utils.TimeUUID; import org.apache.cassandra.utils.concurrent.Future; import org.slf4j.Logger; @@ -82,7 +83,6 @@ import static org.apache.cassandra.cql3.QueryProcessor.executeInternal; import static org.apache.cassandra.cql3.QueryProcessor.executeInternalWithPaging; import static org.apache.cassandra.net.Verb.MUTATION_REQ; import static org.apache.cassandra.utils.Clock.Global.currentTimeMillis; -import static org.apache.cassandra.utils.Clock.Global.nanoTime; public class BatchlogManager implements BatchlogManagerMBean { @@ -503,7 +503,7 @@ public class BatchlogManager implements BatchlogManagerMBean ReplicaPlan.ForWrite replicaPlan = new ReplicaPlan.ForWrite(keyspace, liveAndDown.replicationStrategy(), ConsistencyLevel.ONE, liveRemoteOnly.pending(), liveRemoteOnly.all(), liveRemoteOnly.all(), liveRemoteOnly.all()); - ReplayWriteResponseHandler handler = new ReplayWriteResponseHandler<>(replicaPlan, mutation, nanoTime()); + ReplayWriteResponseHandler handler = new ReplayWriteResponseHandler<>(replicaPlan, mutation, Dispatcher.RequestTime.forImmediateExecution()); Message message = Message.outWithFlag(MUTATION_REQ, mutation, MessageFlag.CALL_BACK_ON_FAILURE); for (Replica replica : liveRemoteOnly.all()) MessagingService.instance().sendWriteWithCallback(message, replica, handler); @@ -527,9 +527,9 @@ public class BatchlogManager implements BatchlogManagerMBean private final Set undelivered = Collections.newSetFromMap(new ConcurrentHashMap<>()); // TODO: should we be hinting here, since presumably batch log will retry? Maintaining historical behaviour for the moment. - ReplayWriteResponseHandler(ReplicaPlan.ForWrite replicaPlan, Supplier hintOnFailure, long queryStartNanoTime) + ReplayWriteResponseHandler(ReplicaPlan.ForWrite replicaPlan, Supplier hintOnFailure, Dispatcher.RequestTime requestTime) { - super(replicaPlan, null, WriteType.UNLOGGED_BATCH, hintOnFailure, queryStartNanoTime); + super(replicaPlan, null, WriteType.UNLOGGED_BATCH, hintOnFailure, requestTime); Iterables.addAll(undelivered, replicaPlan.contacts().endpoints()); } diff --git a/src/java/org/apache/cassandra/concurrent/DebuggableTask.java b/src/java/org/apache/cassandra/concurrent/DebuggableTask.java index ac04eb4c34..10aa8c2142 100644 --- a/src/java/org/apache/cassandra/concurrent/DebuggableTask.java +++ b/src/java/org/apache/cassandra/concurrent/DebuggableTask.java @@ -17,6 +17,9 @@ */ package org.apache.cassandra.concurrent; +import java.util.concurrent.Callable; + +import org.apache.cassandra.utils.MonotonicClock; import org.apache.cassandra.utils.Shared; import static org.apache.cassandra.utils.Shared.Recursive.INTERFACES; @@ -29,6 +32,11 @@ import static org.apache.cassandra.utils.Shared.Scope.SIMULATION; @Shared(scope = SIMULATION, inner = INTERFACES) public interface DebuggableTask { + public default long elapsedSinceCreation() + { + return MonotonicClock.Global.approxTime.now() - creationTimeNanos(); + } + public long creationTimeNanos(); public long startTimeNanos(); @@ -36,6 +44,7 @@ public interface DebuggableTask public String description(); interface RunnableDebuggableTask extends Runnable, DebuggableTask {} + interface CallableDebuggableTask extends Callable, DebuggableTask {} /** * Wraps a {@link DebuggableTask} to include the name of the thread running it. diff --git a/src/java/org/apache/cassandra/concurrent/FutureTask.java b/src/java/org/apache/cassandra/concurrent/FutureTask.java index 763884a2da..877e6f5cb2 100644 --- a/src/java/org/apache/cassandra/concurrent/FutureTask.java +++ b/src/java/org/apache/cassandra/concurrent/FutureTask.java @@ -89,6 +89,34 @@ public class FutureTask extends AsyncFuture implements RunnableFuture public static Callable callable(Runnable run) { + if (run instanceof DebuggableTask.RunnableDebuggableTask) + { + return new DebuggableTask.CallableDebuggableTask() + { + final RunnableDebuggableTask task = (RunnableDebuggableTask) run; + public T call() + { + run.run(); + return null; + } + + public long creationTimeNanos() + { + return task.creationTimeNanos(); + } + + public long startTimeNanos() + { + return task.startTimeNanos(); + } + + public String description() + { + return task.description(); + } + }; + } + return new Callable() { public T call() diff --git a/src/java/org/apache/cassandra/concurrent/ResizableThreadPool.java b/src/java/org/apache/cassandra/concurrent/ResizableThreadPool.java index 9c1dba670d..389246b422 100644 --- a/src/java/org/apache/cassandra/concurrent/ResizableThreadPool.java +++ b/src/java/org/apache/cassandra/concurrent/ResizableThreadPool.java @@ -79,4 +79,9 @@ public interface ResizableThreadPool { return -1; } + + default long oldestTaskQueueTime() + { + return 0; + } } diff --git a/src/java/org/apache/cassandra/concurrent/SEPExecutor.java b/src/java/org/apache/cassandra/concurrent/SEPExecutor.java index f4b74c01c0..a3bb216aee 100644 --- a/src/java/org/apache/cassandra/concurrent/SEPExecutor.java +++ b/src/java/org/apache/cassandra/concurrent/SEPExecutor.java @@ -84,6 +84,21 @@ public class SEPExecutor implements LocalAwareExecutorPlus, SEPExecutorMBean completedTasks.incrementAndGet(); } + @Override + public long oldestTaskQueueTime() + { + Runnable task = tasks.peek(); + if (!(task instanceof FutureTask)) + return 0L; + + FutureTask futureTask = (FutureTask) task; + DebuggableTask debuggableTask = futureTask.debuggableTask(); + if (debuggableTask == null) + return 0L; + + return debuggableTask.elapsedSinceCreation(); + } + @Override public int getMaxTasksQueued() { diff --git a/src/java/org/apache/cassandra/config/Config.java b/src/java/org/apache/cassandra/config/Config.java index 30da524777..3e8c4fae77 100644 --- a/src/java/org/apache/cassandra/config/Config.java +++ b/src/java/org/apache/cassandra/config/Config.java @@ -1283,4 +1283,24 @@ public class Config public double severity_during_decommission = 0; public StorageCompatibilityMode storage_compatibility_mode; + + /** + * Which timestamp should be used to represent a base for replica-side timeouts. + * Client-side timeout is always based on the QUEUE, and is controlled by native_transport_timeout. + */ + public enum CQLStartTime + { + REQUEST, // uses a timestamp that represent the start of processing of the request + QUEUE // uses a timestamp that represents when the request was enqueued + } + public volatile CQLStartTime cql_start_time = CQLStartTime.REQUEST; + + public boolean native_transport_throw_on_overload = false; + public double native_transport_queue_max_item_age_threshold = Double.MAX_VALUE; + public DurationSpec.LongMillisecondsBound native_transport_min_backoff_on_queue_overload = new DurationSpec.LongMillisecondsBound("10ms"); + public DurationSpec.LongMillisecondsBound native_transport_max_backoff_on_queue_overload = new DurationSpec.LongMillisecondsBound("200ms"); + + // 3.x Cassandra Driver has its "read" timeout set to 12 seconds, default matches this. + public DurationSpec.LongMillisecondsBound native_transport_timeout = new DurationSpec.LongMillisecondsBound("12s"); + public boolean enforce_native_deadline_for_hints = false; } diff --git a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java index fcfc09042d..95eb7aa3c7 100644 --- a/src/java/org/apache/cassandra/config/DatabaseDescriptor.java +++ b/src/java/org/apache/cassandra/config/DatabaseDescriptor.java @@ -990,6 +990,15 @@ public class DatabaseDescriptor throw new ConfigurationException(String.format("Invalid configuration. Heap dump is enabled but cannot create heap dump output path: %s.", conf.heap_dump_path != null ? conf.heap_dump_path : "null")); conf.sai_options.validate(); + + if (conf.native_transport_min_backoff_on_queue_overload.toMilliseconds() <= 0) + throw new ConfigurationException("native_transport_min_backoff_on_queue_overload should be strictly positive"); + + if (conf.native_transport_min_backoff_on_queue_overload.toMilliseconds() >= conf.native_transport_max_backoff_on_queue_overload.toMilliseconds()) + throw new ConfigurationException(String.format("native_transport_min_backoff_on_queue_overload should be strictly less than native_transport_max_backoff_on_queue_overload, but %s >= %s", + conf.native_transport_min_backoff_on_queue_overload, + conf.native_transport_max_backoff_on_queue_overload)); + } @VisibleForTesting @@ -2238,6 +2247,100 @@ public class DatabaseDescriptor getTruncateRpcTimeout(unit)); } + public static Config.CQLStartTime getCQLStartTime() + { + return conf.cql_start_time; + } + + public static void setCQLStartTime(Config.CQLStartTime value) + { + conf.cql_start_time = value; + } + + /** + * How much time the item is allowed to spend in (currently only Native) queue, compared to {@link #nativeTransportIdleTimeout()}, + * before backpressure starts being applied. + * + * For example, setting this value to 0.5 means and having the largest of read/range/write/counter timeouts to 10 seconds + * means that if any item spends more than 5 seconds in the queue, backpressure will be applied to the socket associated + * with this queue. + * + * Set to 0 or any negative value to fully disable. + */ + public static double getNativeTransportQueueMaxItemAgeThreshold() + { + return conf.native_transport_queue_max_item_age_threshold; + } + + public static void setNativeTransportMaxQueueItemAgeThreshold(double threshold) + { + conf.native_transport_queue_max_item_age_threshold = threshold; + } + + public static long getNativeTransportMinBackoffOnQueueOverload(TimeUnit timeUnit) + { + return conf.native_transport_min_backoff_on_queue_overload.to(timeUnit); + } + + public static long getNativeTransportMaxBackoffOnQueueOverload(TimeUnit timeUnit) + { + return conf.native_transport_max_backoff_on_queue_overload.to(timeUnit); + } + + public static void setNativeTransportBackoffOnQueueOverload(long minBackoffMillis, + long maxBackoffMillis, + TimeUnit timeUnit) + { + if (minBackoffMillis <= 0) + throw new IllegalArgumentException("native_transport_min_backoff_on_queue_overload should be positive"); + + if (minBackoffMillis >= maxBackoffMillis) + throw new IllegalArgumentException(String.format("native_transport_max_backoff_on_queue_overload should be greater than native_transport_min_backoff_on_queue_overload, but %s >= %s", minBackoffMillis, maxBackoffMillis)); + + + conf.native_transport_min_backoff_on_queue_overload = new DurationSpec.LongMillisecondsBound(minBackoffMillis, timeUnit); + conf.native_transport_max_backoff_on_queue_overload = new DurationSpec.LongMillisecondsBound(maxBackoffMillis, timeUnit); + } + + private static long native_transport_timeout_nanos_cached = -1; + + public static long getNativeTransportTimeout(TimeUnit timeUnit) + { + if (timeUnit == TimeUnit.NANOSECONDS) + { + if (native_transport_timeout_nanos_cached == -1) + native_transport_timeout_nanos_cached = conf.native_transport_timeout.to(TimeUnit.NANOSECONDS); + + return native_transport_timeout_nanos_cached; + } + return conf.native_transport_timeout.to(timeUnit); + } + + public static void setNativeTransportTimeout(long dealine, TimeUnit timeUnit) + { + conf.native_transport_timeout = new DurationSpec.LongMillisecondsBound(dealine, timeUnit); + } + + public static boolean getEnforceNativeDeadlineForHints() + { + return conf.enforce_native_deadline_for_hints; + } + + public static void setEnforceNativeDeadlineForHints(boolean value) + { + conf.enforce_native_deadline_for_hints = value; + } + + public static boolean getNativeTransportThrowOnOverload() + { + return conf.native_transport_throw_on_overload; + } + + public static void setNativeTransportThrowOnOverload(boolean throwOnOverload) + { + conf.native_transport_throw_on_overload = throwOnOverload; + } + public static long getPingTimeout(TimeUnit unit) { return unit.convert(getBlockForPeersTimeoutInSeconds(), TimeUnit.SECONDS); diff --git a/src/java/org/apache/cassandra/cql3/CQLStatement.java b/src/java/org/apache/cassandra/cql3/CQLStatement.java index e78f7332c3..1ac36befcd 100644 --- a/src/java/org/apache/cassandra/cql3/CQLStatement.java +++ b/src/java/org/apache/cassandra/cql3/CQLStatement.java @@ -24,6 +24,7 @@ import org.apache.cassandra.audit.AuditLogContext; import org.apache.cassandra.cql3.functions.Function; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.QueryState; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.transport.messages.ResultMessage; public interface CQLStatement @@ -75,9 +76,9 @@ public interface CQLStatement * * @param state the current query state * @param options options for this query (consistency, variables, pageSize, ...) - * @param queryStartNanoTime the timestamp returned by System.nanoTime() when this statement was received + * @param requestTime request enqueue / and start times; */ - public ResultMessage execute(QueryState state, QueryOptions options, long queryStartNanoTime); + public ResultMessage execute(QueryState state, QueryOptions options, Dispatcher.RequestTime requestTime); /** * Variant of execute used for internal query against the system tables, and thus only query the local node. diff --git a/src/java/org/apache/cassandra/cql3/CustomPayloadMirroringQueryHandler.java b/src/java/org/apache/cassandra/cql3/CustomPayloadMirroringQueryHandler.java index af765d02c5..3c7f58d6f3 100644 --- a/src/java/org/apache/cassandra/cql3/CustomPayloadMirroringQueryHandler.java +++ b/src/java/org/apache/cassandra/cql3/CustomPayloadMirroringQueryHandler.java @@ -24,6 +24,7 @@ import org.apache.cassandra.config.CassandraRelevantProperties; import org.apache.cassandra.cql3.statements.BatchStatement; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.QueryState; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.transport.messages.ResultMessage; import org.apache.cassandra.utils.MD5Digest; @@ -41,13 +42,14 @@ public class CustomPayloadMirroringQueryHandler implements QueryHandler return queryProcessor.parse(query, state, options); } + @Override public ResultMessage process(CQLStatement statement, QueryState state, QueryOptions options, Map customPayload, - long queryStartNanoTime) + Dispatcher.RequestTime requestTime) { - ResultMessage result = queryProcessor.process(statement, state, options, customPayload, queryStartNanoTime); + ResultMessage result = queryProcessor.process(statement, state, options, customPayload, requestTime); result.setCustomPayload(customPayload); return result; } @@ -64,24 +66,26 @@ public class CustomPayloadMirroringQueryHandler implements QueryHandler return queryProcessor.getPrepared(id); } + @Override public ResultMessage processPrepared(CQLStatement statement, QueryState state, QueryOptions options, Map customPayload, - long queryStartNanoTime) + Dispatcher.RequestTime requestTime) { - ResultMessage result = queryProcessor.processPrepared(statement, state, options, customPayload, queryStartNanoTime); + ResultMessage result = queryProcessor.processPrepared(statement, state, options, customPayload, requestTime); result.setCustomPayload(customPayload); return result; } + @Override public ResultMessage processBatch(BatchStatement statement, QueryState state, BatchQueryOptions options, Map customPayload, - long queryStartNanoTime) + Dispatcher.RequestTime requestTime) { - ResultMessage result = queryProcessor.processBatch(statement, state, options, customPayload, queryStartNanoTime); + ResultMessage result = queryProcessor.processBatch(statement, state, options, customPayload, requestTime); result.setCustomPayload(customPayload); return result; } diff --git a/src/java/org/apache/cassandra/cql3/QueryHandler.java b/src/java/org/apache/cassandra/cql3/QueryHandler.java index 8be7184d44..9637f6e41b 100644 --- a/src/java/org/apache/cassandra/cql3/QueryHandler.java +++ b/src/java/org/apache/cassandra/cql3/QueryHandler.java @@ -25,6 +25,7 @@ import org.apache.cassandra.exceptions.RequestExecutionException; import org.apache.cassandra.exceptions.RequestValidationException; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.QueryState; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.transport.messages.ResultMessage; import org.apache.cassandra.utils.MD5Digest; @@ -36,7 +37,7 @@ public interface QueryHandler QueryState state, QueryOptions options, Map customPayload, - long queryStartNanoTime) throws RequestExecutionException, RequestValidationException; + Dispatcher.RequestTime requestTime) throws RequestExecutionException, RequestValidationException; ResultMessage.Prepared prepare(String query, ClientState clientState, @@ -48,13 +49,13 @@ public interface QueryHandler QueryState state, QueryOptions options, Map customPayload, - long queryStartNanoTime) throws RequestExecutionException, RequestValidationException; + Dispatcher.RequestTime requestTime) throws RequestExecutionException, RequestValidationException; ResultMessage processBatch(BatchStatement statement, QueryState state, BatchQueryOptions options, Map customPayload, - long queryStartNanoTime) throws RequestExecutionException, RequestValidationException; + Dispatcher.RequestTime requestTime) throws RequestExecutionException, RequestValidationException; public static class Prepared { diff --git a/src/java/org/apache/cassandra/cql3/QueryProcessor.java b/src/java/org/apache/cassandra/cql3/QueryProcessor.java index 15cfaa6c0f..fe1bc53d41 100644 --- a/src/java/org/apache/cassandra/cql3/QueryProcessor.java +++ b/src/java/org/apache/cassandra/cql3/QueryProcessor.java @@ -67,6 +67,7 @@ import org.apache.cassandra.metrics.CQLMetrics; import org.apache.cassandra.service.*; import org.apache.cassandra.service.pager.QueryPager; import org.apache.cassandra.tracing.Tracing; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.transport.messages.ResultMessage; import org.apache.cassandra.utils.*; @@ -243,7 +244,7 @@ public class QueryProcessor implements QueryHandler } } - public ResultMessage processStatement(CQLStatement statement, QueryState queryState, QueryOptions options, long queryStartNanoTime) + public ResultMessage processStatement(CQLStatement statement, QueryState queryState, QueryOptions options, Dispatcher.RequestTime requestTime) throws RequestExecutionException, RequestValidationException { logger.trace("Process {} @CL.{}", statement, options.getConsistency()); @@ -253,7 +254,7 @@ public class QueryProcessor implements QueryHandler ResultMessage result = options.getConsistency() == ConsistencyLevel.NODE_LOCAL ? processNodeLocalStatement(statement, queryState, options) - : statement.execute(queryState, options, queryStartNanoTime); + : statement.execute(queryState, options, requestTime); return result == null ? new ResultMessage.Void() : result; } @@ -314,12 +315,12 @@ public class QueryProcessor implements QueryHandler } } - public static ResultMessage process(String queryString, ConsistencyLevel cl, QueryState queryState, long queryStartNanoTime) + public static ResultMessage process(String queryString, ConsistencyLevel cl, QueryState queryState, Dispatcher.RequestTime requestTime) throws RequestExecutionException, RequestValidationException { QueryOptions options = QueryOptions.forInternalCalls(cl, Collections.emptyList()); CQLStatement statement = instance.parse(queryString, queryState, options); - return instance.process(statement, queryState, options, queryStartNanoTime); + return instance.process(statement, queryState, options, requestTime); } public CQLStatement parse(String queryString, QueryState queryState, QueryOptions options) @@ -331,12 +332,12 @@ public class QueryProcessor implements QueryHandler QueryState state, QueryOptions options, Map customPayload, - long queryStartNanoTime) throws RequestExecutionException, RequestValidationException + Dispatcher.RequestTime requestTime) throws RequestExecutionException, RequestValidationException { - return process(statement, state, options, queryStartNanoTime); + return process(statement, state, options, requestTime); } - public ResultMessage process(CQLStatement prepared, QueryState queryState, QueryOptions options, long queryStartNanoTime) + public ResultMessage process(CQLStatement prepared, QueryState queryState, QueryOptions options, Dispatcher.RequestTime requestTime) throws RequestExecutionException, RequestValidationException { options.prepare(prepared.getBindVariables()); @@ -346,7 +347,7 @@ public class QueryProcessor implements QueryHandler if (!queryState.getClientState().isInternal) metrics.regularStatementsExecuted.inc(); - return processStatement(prepared, queryState, options, queryStartNanoTime); + return processStatement(prepared, queryState, options, requestTime); } public static CQLStatement parseStatement(String queryStr, ClientState clientState) throws RequestValidationException @@ -364,7 +365,7 @@ public class QueryProcessor implements QueryHandler QueryState queryState = QueryState.forInternalCalls(); QueryOptions options = QueryOptions.forInternalCalls(cl, values); CQLStatement statement = instance.parse(query, queryState, options); - ResultMessage result = instance.process(statement, queryState, options, nanoTime()); + ResultMessage result = instance.process(statement, queryState, options, Dispatcher.RequestTime.forImmediateExecution()); if (result instanceof ResultMessage.Rows) return UntypedResultSet.create(((ResultMessage.Rows)result).result); else @@ -526,7 +527,7 @@ public class QueryProcessor implements QueryHandler try { Prepared prepared = prepareInternal(query); - ResultMessage result = prepared.statement.execute(state, makeInternalOptionsWithNowInSec(prepared.statement, state.getNowInSeconds(), values, cl), nanoTime()); + ResultMessage result = prepared.statement.execute(state, makeInternalOptionsWithNowInSec(prepared.statement, state.getNowInSeconds(), values, cl), Dispatcher.RequestTime.forImmediateExecution()); if (result instanceof ResultMessage.Rows) return UntypedResultSet.create(((ResultMessage.Rows)result).result); else @@ -586,12 +587,12 @@ public class QueryProcessor implements QueryHandler * Note that this only make sense for Selects so this only accept SELECT statements and is only useful in rare * cases. */ - public static UntypedResultSet executeInternalWithNow(long nowInSec, long queryStartNanoTime, String query, Object... values) + public static UntypedResultSet executeInternalWithNow(long nowInSec, Dispatcher.RequestTime requestTime, String query, Object... values) { Prepared prepared = prepareInternal(query); assert prepared.statement instanceof SelectStatement; SelectStatement select = (SelectStatement)prepared.statement; - ResultMessage result = select.executeInternal(internalQueryState(), makeInternalOptionsWithNowInSec(prepared.statement, nowInSec, values), nowInSec, queryStartNanoTime); + ResultMessage result = select.executeInternal(internalQueryState(), makeInternalOptionsWithNowInSec(prepared.statement, nowInSec, values), nowInSec, requestTime); assert result instanceof ResultMessage.Rows; return UntypedResultSet.create(((ResultMessage.Rows)result).result); } @@ -791,17 +792,18 @@ public class QueryProcessor implements QueryHandler return new ResultMessage.Prepared(statementId, resultMetadata.getResultMetadataId(), preparedMetadata, resultMetadata); } + @Override public ResultMessage processPrepared(CQLStatement statement, QueryState state, QueryOptions options, Map customPayload, - long queryStartNanoTime) + Dispatcher.RequestTime requestTime) throws RequestExecutionException, RequestValidationException { - return processPrepared(statement, state, options, queryStartNanoTime); + return processPrepared(statement, state, options, requestTime); } - public ResultMessage processPrepared(CQLStatement statement, QueryState queryState, QueryOptions options, long queryStartNanoTime) + public ResultMessage processPrepared(CQLStatement statement, QueryState queryState, QueryOptions options, Dispatcher.RequestTime requestTime) throws RequestExecutionException, RequestValidationException { List variables = options.getValues(); @@ -820,27 +822,27 @@ public class QueryProcessor implements QueryHandler } metrics.preparedStatementsExecuted.inc(); - return processStatement(statement, queryState, options, queryStartNanoTime); + return processStatement(statement, queryState, options, requestTime); } public ResultMessage processBatch(BatchStatement statement, QueryState state, BatchQueryOptions options, Map customPayload, - long queryStartNanoTime) + Dispatcher.RequestTime requestTime) throws RequestExecutionException, RequestValidationException { - return processBatch(statement, state, options, queryStartNanoTime); + return processBatch(statement, state, options, requestTime); } - public ResultMessage processBatch(BatchStatement batch, QueryState queryState, BatchQueryOptions options, long queryStartNanoTime) + public ResultMessage processBatch(BatchStatement batch, QueryState queryState, BatchQueryOptions options, Dispatcher.RequestTime requestTime) throws RequestExecutionException, RequestValidationException { ClientState clientState = queryState.getClientState().cloneWithKeyspaceIfSet(options.getKeyspace()); batch.authorize(clientState); batch.validate(); batch.validate(clientState); - return batch.execute(queryState, options, queryStartNanoTime); + return batch.execute(queryState, options, requestTime); } public static CQLStatement getStatement(String queryStr, ClientState clientState) diff --git a/src/java/org/apache/cassandra/cql3/UntypedResultSet.java b/src/java/org/apache/cassandra/cql3/UntypedResultSet.java index 92a33c9e02..a0201c500a 100644 --- a/src/java/org/apache/cassandra/cql3/UntypedResultSet.java +++ b/src/java/org/apache/cassandra/cql3/UntypedResultSet.java @@ -48,12 +48,11 @@ import org.apache.cassandra.schema.ColumnMetadata; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.pager.QueryPager; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.utils.AbstractIterator; import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.TimeUUID; -import static org.apache.cassandra.utils.Clock.Global.nanoTime; - /** a utility for doing internal cql-based queries */ public abstract class UntypedResultSet implements Iterable { @@ -291,7 +290,7 @@ public abstract class UntypedResultSet implements Iterable if (pager.isExhausted()) return endOfData(); - try (PartitionIterator iter = pager.fetchPage(pageSize, cl, clientState, nanoTime())) + try (PartitionIterator iter = pager.fetchPage(pageSize, cl, clientState, Dispatcher.RequestTime.forImmediateExecution())) { currentPage = select.process(iter, nowInSec, true, clientState).rows.iterator(); } diff --git a/src/java/org/apache/cassandra/cql3/statements/AuthenticationStatement.java b/src/java/org/apache/cassandra/cql3/statements/AuthenticationStatement.java index db3aa99273..cd6455c21d 100644 --- a/src/java/org/apache/cassandra/cql3/statements/AuthenticationStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/AuthenticationStatement.java @@ -26,6 +26,7 @@ import org.apache.cassandra.exceptions.RequestValidationException; import org.apache.cassandra.exceptions.UnauthorizedException; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.QueryState; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.transport.messages.ResultMessage; public abstract class AuthenticationStatement extends CQLStatement.Raw implements CQLStatement @@ -35,7 +36,8 @@ public abstract class AuthenticationStatement extends CQLStatement.Raw implement return this; } - public ResultMessage execute(QueryState state, QueryOptions options, long queryStartNanoTime) + @Override + public ResultMessage execute(QueryState state, QueryOptions options, Dispatcher.RequestTime requestTime) throws RequestExecutionException, RequestValidationException { return execute(state.getClientState()); @@ -43,6 +45,7 @@ public abstract class AuthenticationStatement extends CQLStatement.Raw implement public abstract ResultMessage execute(ClientState state) throws RequestExecutionException, RequestValidationException; + @Override public ResultMessage executeLocally(QueryState state, QueryOptions options) { // executeLocally is for local query only, thus altering users doesn't make sense and is not supported diff --git a/src/java/org/apache/cassandra/cql3/statements/AuthorizationStatement.java b/src/java/org/apache/cassandra/cql3/statements/AuthorizationStatement.java index 46285c6730..59c1739cd4 100644 --- a/src/java/org/apache/cassandra/cql3/statements/AuthorizationStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/AuthorizationStatement.java @@ -26,6 +26,7 @@ import org.apache.cassandra.exceptions.RequestExecutionException; import org.apache.cassandra.exceptions.RequestValidationException; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.QueryState; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.transport.messages.ResultMessage; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; @@ -37,7 +38,8 @@ public abstract class AuthorizationStatement extends CQLStatement.Raw implements return this; } - public ResultMessage execute(QueryState state, QueryOptions options, long queryStartNanoTime) + @Override + public ResultMessage execute(QueryState state, QueryOptions options, Dispatcher.RequestTime requestTime) throws RequestValidationException, RequestExecutionException { return execute(state.getClientState()); diff --git a/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java b/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java index eea09100b0..a70a8891a6 100644 --- a/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/BatchStatement.java @@ -45,6 +45,7 @@ import org.apache.cassandra.metrics.BatchMetrics; import org.apache.cassandra.metrics.ClientRequestSizeMetrics; import org.apache.cassandra.service.*; import org.apache.cassandra.tracing.Tracing; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.transport.messages.ResultMessage; import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.NoSpamLogger; @@ -53,7 +54,6 @@ import org.apache.cassandra.utils.Pair; import static java.util.function.Predicate.isEqual; import static org.apache.cassandra.cql3.statements.RequestValidations.checkFalse; -import static org.apache.cassandra.utils.Clock.Global.nanoTime; /** * A BATCH statement parsed from a CQL query. @@ -272,7 +272,7 @@ public class BatchStatement implements CQLStatement boolean local, long batchTimestamp, long nowInSeconds, - long queryStartNanoTime) + Dispatcher.RequestTime requestTime) { if (statements.isEmpty()) return Collections.emptyList(); @@ -309,7 +309,7 @@ public class BatchStatement implements CQLStatement } QueryOptions statementOptions = options.forStatement(i); long timestamp = attrs.getTimestamp(batchTimestamp, statementOptions); - statement.addUpdates(collector, partitionKeys.get(i), state, statementOptions, local, timestamp, nowInSeconds, queryStartNanoTime); + statement.addUpdates(collector, partitionKeys.get(i), state, statementOptions, local, timestamp, nowInSeconds, requestTime); } if (tablesWithZeroGcGs != null) @@ -397,12 +397,12 @@ public class BatchStatement implements CQLStatement } - public ResultMessage execute(QueryState queryState, QueryOptions options, long queryStartNanoTime) + public ResultMessage execute(QueryState queryState, QueryOptions options, Dispatcher.RequestTime requestTime) { - return execute(queryState, BatchQueryOptions.withoutPerStatementVariables(options), queryStartNanoTime); + return execute(queryState, BatchQueryOptions.withoutPerStatementVariables(options), requestTime); } - public ResultMessage execute(QueryState queryState, BatchQueryOptions options, long queryStartNanoTime) + public ResultMessage execute(QueryState queryState, BatchQueryOptions options, Dispatcher.RequestTime requestTime) { long timestamp = options.getTimestamp(queryState); long nowInSeconds = options.getNowInSeconds(queryState); @@ -420,18 +420,18 @@ public class BatchStatement implements CQLStatement statements.get(i).validateDiskUsage(options.forStatement(i), clientState); if (hasConditions) - return executeWithConditions(options, queryState, queryStartNanoTime); + return executeWithConditions(options, queryState, requestTime); if (updatesVirtualTables) - executeInternalWithoutCondition(queryState, options, queryStartNanoTime); + executeInternalWithoutCondition(queryState, options, requestTime); else - executeWithoutConditions(getMutations(clientState, options, false, timestamp, nowInSeconds, queryStartNanoTime), - options.getConsistency(), queryStartNanoTime); + executeWithoutConditions(getMutations(clientState, options, false, timestamp, nowInSeconds, requestTime), + options.getConsistency(), requestTime); return new ResultMessage.Void(); } - private void executeWithoutConditions(List mutations, ConsistencyLevel cl, long queryStartNanoTime) throws RequestExecutionException, RequestValidationException + private void executeWithoutConditions(List mutations, ConsistencyLevel cl, Dispatcher.RequestTime requestTime) throws RequestExecutionException, RequestValidationException { if (mutations.isEmpty()) return; @@ -442,7 +442,7 @@ public class BatchStatement implements CQLStatement updatePartitionsPerBatchMetrics(mutations.size()); boolean mutateAtomic = (isLogged() && mutations.size() > 1); - StorageProxy.mutateWithTriggers(mutations, cl, mutateAtomic, queryStartNanoTime); + StorageProxy.mutateWithTriggers(mutations, cl, mutateAtomic, requestTime); ClientRequestSizeMetrics.recordRowAndColumnCountMetrics(mutations); } @@ -457,7 +457,7 @@ public class BatchStatement implements CQLStatement } } - private ResultMessage executeWithConditions(BatchQueryOptions options, QueryState state, long queryStartNanoTime) + private ResultMessage executeWithConditions(BatchQueryOptions options, QueryState state, Dispatcher.RequestTime requestTime) { Pair> p = makeCasRequest(options, state); CQL3CasRequest casRequest = p.left; @@ -474,7 +474,7 @@ public class BatchStatement implements CQLStatement options.getConsistency(), state.getClientState(), options.getNowInSeconds(state), - queryStartNanoTime)) + requestTime)) { return new ResultMessage.Rows(ModificationStatement.buildCasResultSet(ksName, tableName, @@ -563,16 +563,16 @@ public class BatchStatement implements CQLStatement if (hasConditions) return executeInternalWithConditions(batchOptions, queryState); - executeInternalWithoutCondition(queryState, batchOptions, nanoTime()); + executeInternalWithoutCondition(queryState, batchOptions, Dispatcher.RequestTime.forImmediateExecution()); return new ResultMessage.Void(); } - private ResultMessage executeInternalWithoutCondition(QueryState queryState, BatchQueryOptions batchOptions, long queryStartNanoTime) + private ResultMessage executeInternalWithoutCondition(QueryState queryState, BatchQueryOptions batchOptions, Dispatcher.RequestTime requestTime) { long timestamp = batchOptions.getTimestamp(queryState); long nowInSeconds = batchOptions.getNowInSeconds(queryState); - for (IMutation mutation : getMutations(queryState.getClientState(), batchOptions, true, timestamp, nowInSeconds, queryStartNanoTime)) + for (IMutation mutation : getMutations(queryState.getClientState(), batchOptions, true, timestamp, nowInSeconds, requestTime)) mutation.apply(); return null; } diff --git a/src/java/org/apache/cassandra/cql3/statements/DescribeStatement.java b/src/java/org/apache/cassandra/cql3/statements/DescribeStatement.java index 90382dfe57..9d886455e5 100644 --- a/src/java/org/apache/cassandra/cql3/statements/DescribeStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/DescribeStatement.java @@ -46,6 +46,7 @@ import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.QueryState; import org.apache.cassandra.service.StorageService; import org.apache.cassandra.service.pager.PagingState; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.transport.messages.ResultMessage; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.FBUtilities; @@ -124,7 +125,7 @@ public abstract class DescribeStatement extends CQLStatement.Raw implements C } @Override - public final ResultMessage execute(QueryState state, QueryOptions options, long queryStartNanoTime) throws RequestValidationException, RequestExecutionException + public final ResultMessage execute(QueryState state, QueryOptions options, Dispatcher.RequestTime requestTime) throws RequestValidationException, RequestExecutionException { return executeLocally(state, options); } diff --git a/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java b/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java index 61fbd6f391..a538348cea 100644 --- a/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/ModificationStatement.java @@ -59,6 +59,7 @@ import org.apache.cassandra.service.disk.usage.DiskUsageBroadcaster; import org.apache.cassandra.service.paxos.Ballot; import org.apache.cassandra.service.paxos.BallotGenerator; import org.apache.cassandra.service.paxos.Commit.Proposal; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.transport.messages.ResultMessage; import org.apache.cassandra.triggers.TriggerExecutor; import org.apache.cassandra.utils.FBUtilities; @@ -68,7 +69,6 @@ import org.apache.cassandra.utils.Pair; import static org.apache.cassandra.cql3.statements.RequestValidations.checkFalse; import static org.apache.cassandra.cql3.statements.RequestValidations.checkNull; import static org.apache.cassandra.service.paxos.Ballot.Flag.NONE; -import static org.apache.cassandra.utils.Clock.Global.nanoTime; /* * Abstract parent class of individual modifications, i.e. INSERT, UPDATE and DELETE. @@ -422,7 +422,7 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa boolean local, ConsistencyLevel cl, long nowInSeconds, - long queryStartNanoTime) + Dispatcher.RequestTime requestTime) { if (!requiresRead()) return null; @@ -457,7 +457,7 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa } } - try (PartitionIterator iter = group.execute(cl, null, queryStartNanoTime)) + try (PartitionIterator iter = group.execute(cl, null, requestTime)) { return asMaterializedMap(iter); } @@ -488,7 +488,7 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa && getRestrictions().isColumnRange(); } - public ResultMessage execute(QueryState queryState, QueryOptions options, long queryStartNanoTime) + public ResultMessage execute(QueryState queryState, QueryOptions options, Dispatcher.RequestTime requestTime) throws RequestExecutionException, RequestValidationException { if (options.getConsistency() == null) @@ -498,15 +498,15 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa queryState.getClientState()); return hasConditions() - ? executeWithCondition(queryState, options, queryStartNanoTime) - : executeWithoutCondition(queryState, options, queryStartNanoTime); + ? executeWithCondition(queryState, options, requestTime) + : executeWithoutCondition(queryState, options, requestTime); } - private ResultMessage executeWithoutCondition(QueryState queryState, QueryOptions options, long queryStartNanoTime) + private ResultMessage executeWithoutCondition(QueryState queryState, QueryOptions options, Dispatcher.RequestTime requestTime) throws RequestExecutionException, RequestValidationException { if (isVirtual()) - return executeInternalWithoutCondition(queryState, options, queryStartNanoTime); + return executeInternalWithoutCondition(queryState, options, requestTime); ConsistencyLevel cl = options.getConsistency(); if (isCounter()) @@ -523,10 +523,10 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa false, options.getTimestamp(queryState), options.getNowInSeconds(queryState), - queryStartNanoTime); + requestTime); if (!mutations.isEmpty()) { - StorageProxy.mutateWithTriggers(mutations, cl, false, queryStartNanoTime); + StorageProxy.mutateWithTriggers(mutations, cl, false, requestTime); if (!SchemaConstants.isSystemKeyspace(metadata.keyspace)) ClientRequestSizeMetrics.recordRowAndColumnCountMetrics(mutations); @@ -535,7 +535,7 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa return null; } - private ResultMessage executeWithCondition(QueryState queryState, QueryOptions options, long queryStartNanoTime) + private ResultMessage executeWithCondition(QueryState queryState, QueryOptions options, Dispatcher.RequestTime requestTime) { CQL3CasRequest request = makeCasRequest(queryState, options); @@ -547,7 +547,7 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa options.getConsistency(), queryState.getClientState(), options.getNowInSeconds(queryState), - queryStartNanoTime)) + requestTime)) { return new ResultMessage.Rows(buildCasResultSet(result, queryState, options)); } @@ -680,15 +680,15 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa { return hasConditions() ? executeInternalWithCondition(queryState, options) - : executeInternalWithoutCondition(queryState, options, nanoTime()); + : executeInternalWithoutCondition(queryState, options, Dispatcher.RequestTime.forImmediateExecution()); } - public ResultMessage executeInternalWithoutCondition(QueryState queryState, QueryOptions options, long queryStartNanoTime) + public ResultMessage executeInternalWithoutCondition(QueryState queryState, QueryOptions options, Dispatcher.RequestTime requestTime) throws RequestValidationException, RequestExecutionException { long timestamp = options.getTimestamp(queryState); long nowInSeconds = options.getNowInSeconds(queryState); - for (IMutation mutation : getMutations(queryState.getClientState(), options, true, timestamp, nowInSeconds, queryStartNanoTime)) + for (IMutation mutation : getMutations(queryState.getClientState(), options, true, timestamp, nowInSeconds, requestTime)) mutation.apply(); return null; } @@ -741,12 +741,12 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa boolean local, long timestamp, long nowInSeconds, - long queryStartNanoTime) + Dispatcher.RequestTime requestTime) { List keys = buildPartitionKeyNames(options, state); HashMultiset perPartitionKeyCounts = HashMultiset.create(keys); SingleTableUpdatesCollector collector = new SingleTableUpdatesCollector(metadata, updatedColumns, perPartitionKeyCounts); - addUpdates(collector, keys, state, options, local, timestamp, nowInSeconds, queryStartNanoTime); + addUpdates(collector, keys, state, options, local, timestamp, nowInSeconds, requestTime); return collector.toMutations(state); } @@ -757,7 +757,7 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa boolean local, long timestamp, long nowInSeconds, - long queryStartNanoTime) + Dispatcher.RequestTime requestTime) { if (hasSlices()) { @@ -775,7 +775,7 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa local, timestamp, nowInSeconds, - queryStartNanoTime); + requestTime); for (ByteBuffer key : keys) { Validation.validateKey(metadata(), key); @@ -795,7 +795,7 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa if (restrictions.hasClusteringColumnsRestrictions() && clusterings.isEmpty()) return; - UpdateParameters params = makeUpdateParameters(keys, clusterings, state, options, local, timestamp, nowInSeconds, queryStartNanoTime); + UpdateParameters params = makeUpdateParameters(keys, clusterings, state, options, local, timestamp, nowInSeconds, requestTime); for (ByteBuffer key : keys) { @@ -835,7 +835,7 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa boolean local, long timestamp, long nowInSeconds, - long queryStartNanoTime) + Dispatcher.RequestTime requestTime) { if (clusterings.contains(Clustering.STATIC_CLUSTERING)) return makeUpdateParameters(keys, @@ -846,7 +846,7 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa local, timestamp, nowInSeconds, - queryStartNanoTime); + requestTime); return makeUpdateParameters(keys, new ClusteringIndexNamesFilter(clusterings, false), @@ -856,7 +856,7 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa local, timestamp, nowInSeconds, - queryStartNanoTime); + requestTime); } private UpdateParameters makeUpdateParameters(Collection keys, @@ -867,7 +867,7 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa boolean local, long timestamp, long nowInSeconds, - long queryStartNanoTime) + Dispatcher.RequestTime requestTime) { // Some lists operation requires reading Map lists = @@ -877,7 +877,7 @@ public abstract class ModificationStatement implements CQLStatement.SingleKeyspa local, options.getConsistency(), nowInSeconds, - queryStartNanoTime); + requestTime); return new UpdateParameters(metadata(), updatedColumns(), diff --git a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java index d8fa830b13..c75a551d20 100644 --- a/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/SelectStatement.java @@ -76,6 +76,7 @@ import org.apache.cassandra.service.StorageProxy; import org.apache.cassandra.service.pager.AggregationQueryPager; import org.apache.cassandra.service.pager.PagingState; import org.apache.cassandra.service.pager.QueryPager; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.transport.messages.ResultMessage; import org.apache.cassandra.utils.ByteBufferUtil; @@ -92,7 +93,6 @@ import static org.apache.cassandra.cql3.statements.RequestValidations.checkNull; import static org.apache.cassandra.cql3.statements.RequestValidations.checkTrue; import static org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest; import static org.apache.cassandra.utils.ByteBufferUtil.UNSET_BYTE_BUFFER; -import static org.apache.cassandra.utils.Clock.Global.nanoTime; /** * Encapsulates a completely parsed SELECT query, including the target @@ -277,7 +277,7 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement Guardrails.allowFilteringEnabled.ensureEnabled(state); } - public ResultMessage.Rows execute(QueryState state, QueryOptions options, long queryStartNanoTime) + public ResultMessage.Rows execute(QueryState state, QueryOptions options, Dispatcher.RequestTime requestTime) { ConsistencyLevel cl = options.getConsistency(); checkNotNull(cl, "Invalid empty consistency level"); @@ -337,7 +337,7 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement if (aggregationSpec == null && (pageSize <= 0 || (query.limits().count() <= pageSize) || query.isTopK())) { - rows = execute(query, options, state.getClientState(), selectors, nowInSec, userLimit, null, queryStartNanoTime, unmask); + rows = execute(query, options, state.getClientState(), selectors, nowInSec, userLimit, null, requestTime, unmask); } else { @@ -351,7 +351,7 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement nowInSec, userLimit, aggregationSpec, - queryStartNanoTime, + requestTime, unmask); } if (!SchemaConstants.isSystemKeyspace(table.keyspace)) @@ -418,10 +418,10 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement long nowInSec, int userLimit, AggregationSpecification aggregationSpec, - long queryStartNanoTime, + Dispatcher.RequestTime requestTime, boolean unmask) { - try (PartitionIterator data = query.execute(options.getConsistency(), state, queryStartNanoTime)) + try (PartitionIterator data = query.execute(options.getConsistency(), state, requestTime)) { return processResults(data, options, selectors, nowInSec, userLimit, aggregationSpec, unmask, state); } @@ -463,7 +463,7 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement return pager.state(); } - public abstract PartitionIterator fetchPage(int pageSize, long queryStartNanoTime); + public abstract PartitionIterator fetchPage(int pageSize, Dispatcher.RequestTime requestTime); public static class NormalPager extends Pager { @@ -477,9 +477,9 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement this.clientState = clientState; } - public PartitionIterator fetchPage(int pageSize, long queryStartNanoTime) + public PartitionIterator fetchPage(int pageSize, Dispatcher.RequestTime requestTime) { - return pager.fetchPage(pageSize, consistency, clientState, queryStartNanoTime); + return pager.fetchPage(pageSize, consistency, clientState, requestTime); } } @@ -493,7 +493,7 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement this.executionController = executionController; } - public PartitionIterator fetchPage(int pageSize, long queryStartNanoTime) + public PartitionIterator fetchPage(int pageSize, Dispatcher.RequestTime requestTime) { return pager.fetchPageInternal(pageSize, executionController); } @@ -508,7 +508,7 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement long nowInSec, int userLimit, AggregationSpecification aggregationSpec, - long queryStartNanoTime, + Dispatcher.RequestTime requestTime, boolean unmask) { Guardrails.pageSize.guard(pageSize, table(), false, state.getClientState()); @@ -536,7 +536,7 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement + " you must either remove the ORDER BY or the IN and sort client side, or disable paging for this query"); ResultMessage.Rows msg; - try (PartitionIterator page = pager.fetchPage(pageSize, queryStartNanoTime)) + try (PartitionIterator page = pager.fetchPage(pageSize, requestTime)) { msg = processResults(page, options, selectors, nowInSec, userLimit, aggregationSpec, unmask, state.getClientState()); } @@ -570,13 +570,13 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement public ResultMessage.Rows executeLocally(QueryState state, QueryOptions options) throws RequestExecutionException, RequestValidationException { - return executeInternal(state, options, options.getNowInSeconds(state), nanoTime()); + return executeInternal(state, options, options.getNowInSeconds(state), Dispatcher.RequestTime.forImmediateExecution()); } public ResultMessage.Rows executeInternal(QueryState state, QueryOptions options, long nowInSec, - long queryStartNanoTime) + Dispatcher.RequestTime requestTime) { int userLimit = getLimit(options); int userPerPartitionLimit = getPerPartitionLimit(options); @@ -614,7 +614,7 @@ public class SelectStatement implements CQLStatement.SingleKeyspaceCqlStatement nowInSec, userLimit, aggregationSpec, - queryStartNanoTime, + requestTime, unmask); } } diff --git a/src/java/org/apache/cassandra/cql3/statements/TruncateStatement.java b/src/java/org/apache/cassandra/cql3/statements/TruncateStatement.java index 1d01eaa017..2d1d58c7aa 100644 --- a/src/java/org/apache/cassandra/cql3/statements/TruncateStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/TruncateStatement.java @@ -34,6 +34,7 @@ import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.QueryState; import org.apache.cassandra.service.StorageProxy; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.transport.messages.ResultMessage; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; @@ -61,7 +62,8 @@ public class TruncateStatement extends QualifiedStatement implements CQLStatemen Guardrails.dropTruncateTableEnabled.ensureEnabled(state); } - public ResultMessage execute(QueryState state, QueryOptions options, long queryStartNanoTime) throws InvalidRequestException, TruncateException + @Override + public ResultMessage execute(QueryState state, QueryOptions options, Dispatcher.RequestTime requestTime) throws InvalidRequestException, TruncateException { try { diff --git a/src/java/org/apache/cassandra/cql3/statements/UseStatement.java b/src/java/org/apache/cassandra/cql3/statements/UseStatement.java index c7f0c24852..b3819b5cd2 100644 --- a/src/java/org/apache/cassandra/cql3/statements/UseStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/UseStatement.java @@ -25,6 +25,7 @@ import org.apache.cassandra.cql3.QueryOptions; import org.apache.cassandra.cql3.QueryProcessor; import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.exceptions.UnauthorizedException; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.transport.messages.ResultMessage; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.QueryState; @@ -32,7 +33,6 @@ import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import static org.apache.cassandra.cql3.statements.RequestValidations.checkTrue; -import static org.apache.cassandra.utils.Clock.Global.nanoTime; public class UseStatement extends CQLStatement.Raw implements CQLStatement { @@ -59,7 +59,8 @@ public class UseStatement extends CQLStatement.Raw implements CQLStatement checkTrue(DatabaseDescriptor.getUseStatementsEnabled(), "USE statements prohibited. (see use_statements_enabled in cassandra.yaml)"); } - public ResultMessage execute(QueryState state, QueryOptions options, long queryStartNanoTime) throws InvalidRequestException + @Override + public ResultMessage execute(QueryState state, QueryOptions options, Dispatcher.RequestTime requestTime) throws InvalidRequestException { QueryProcessor.metrics.useStatementsExecuted.inc(); state.getClientState().setKeyspace(keyspace); @@ -70,7 +71,7 @@ public class UseStatement extends CQLStatement.Raw implements CQLStatement { // In production, internal queries are exclusively on the system keyspace and 'use' is thus useless // but for some unit tests we need to set the keyspace (e.g. for tests with DROP INDEX) - return execute(state, options, nanoTime()); + return execute(state, options, Dispatcher.RequestTime.forImmediateExecution()); } @Override diff --git a/src/java/org/apache/cassandra/cql3/statements/schema/AlterSchemaStatement.java b/src/java/org/apache/cassandra/cql3/statements/schema/AlterSchemaStatement.java index a539ea74e8..a75c0ac4f1 100644 --- a/src/java/org/apache/cassandra/cql3/statements/schema/AlterSchemaStatement.java +++ b/src/java/org/apache/cassandra/cql3/statements/schema/AlterSchemaStatement.java @@ -34,6 +34,7 @@ import org.apache.cassandra.schema.Keyspaces.KeyspacesDiff; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.ClientWarn; import org.apache.cassandra.service.QueryState; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.transport.Event.SchemaChange; import org.apache.cassandra.transport.messages.ResultMessage; @@ -55,7 +56,8 @@ abstract public class AlterSchemaStatement implements CQLStatement.SingleKeyspac this.state = state; } - public ResultMessage execute(QueryState state, QueryOptions options, long queryStartNanoTime) + @Override + public ResultMessage execute(QueryState state, QueryOptions options, Dispatcher.RequestTime requestTime) { return execute(state, false); } diff --git a/src/java/org/apache/cassandra/db/CounterMutationVerbHandler.java b/src/java/org/apache/cassandra/db/CounterMutationVerbHandler.java index e4c7669806..aab1cfda01 100644 --- a/src/java/org/apache/cassandra/db/CounterMutationVerbHandler.java +++ b/src/java/org/apache/cassandra/db/CounterMutationVerbHandler.java @@ -25,8 +25,7 @@ import org.apache.cassandra.net.IVerbHandler; import org.apache.cassandra.net.Message; import org.apache.cassandra.net.MessagingService; import org.apache.cassandra.service.StorageProxy; - -import static org.apache.cassandra.utils.Clock.Global.nanoTime; +import org.apache.cassandra.transport.Dispatcher; public class CounterMutationVerbHandler implements IVerbHandler { @@ -36,7 +35,6 @@ public class CounterMutationVerbHandler implements IVerbHandler public void doVerb(final Message message) { - long queryStartNanoTime = nanoTime(); final CounterMutation cm = message.payload; logger.trace("Applying forwarded {}", cm); @@ -51,6 +49,6 @@ public class CounterMutationVerbHandler implements IVerbHandler StorageProxy.applyCounterMutationOnLeader(cm, localDataCenter, () -> MessagingService.instance().send(message.emptyResponse(), message.from()), - queryStartNanoTime); + Dispatcher.RequestTime.forImmediateExecution()); } } diff --git a/src/java/org/apache/cassandra/db/MutationVerbHandler.java b/src/java/org/apache/cassandra/db/MutationVerbHandler.java index 1ab6711fdb..0ab9d0777b 100644 --- a/src/java/org/apache/cassandra/db/MutationVerbHandler.java +++ b/src/java/org/apache/cassandra/db/MutationVerbHandler.java @@ -22,7 +22,9 @@ import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.net.*; import org.apache.cassandra.tracing.Tracing; +import static java.util.concurrent.TimeUnit.NANOSECONDS; import static org.apache.cassandra.db.commitlog.CommitLogSegment.ENTRY_OVERHEAD_SIZE; +import static org.apache.cassandra.utils.MonotonicClock.Global.approxTime; public class MutationVerbHandler implements IVerbHandler { @@ -41,6 +43,13 @@ public class MutationVerbHandler implements IVerbHandler public void doVerb(Message message) { + if (approxTime.now() > message.expiresAtNanos()) + { + Tracing.trace("Discarding mutation from {} (timed out)", message.from()); + MessagingService.instance().metrics.recordDroppedMessage(message, message.elapsedSinceCreated(NANOSECONDS), NANOSECONDS); + return; + } + message.payload.validateSize(MessagingService.current_version, ENTRY_OVERHEAD_SIZE); // Check if there were any forwarding headers in this message diff --git a/src/java/org/apache/cassandra/db/PartitionRangeReadCommand.java b/src/java/org/apache/cassandra/db/PartitionRangeReadCommand.java index 7aa1e01b6a..5f6d0ba21d 100644 --- a/src/java/org/apache/cassandra/db/PartitionRangeReadCommand.java +++ b/src/java/org/apache/cassandra/db/PartitionRangeReadCommand.java @@ -54,6 +54,7 @@ import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.StorageProxy; import org.apache.cassandra.tracing.Tracing; +import org.apache.cassandra.transport.Dispatcher; /** * A read command that selects a (part of a) range of partitions. @@ -304,9 +305,9 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR return dataRange.isReversed(); } - public PartitionIterator execute(ConsistencyLevel consistency, ClientState state, long queryStartNanoTime) throws RequestExecutionException + public PartitionIterator execute(ConsistencyLevel consistency, ClientState state, Dispatcher.RequestTime requestTime) throws RequestExecutionException { - return StorageProxy.getRangeSlice(this, consistency, queryStartNanoTime); + return StorageProxy.getRangeSlice(this, consistency, requestTime); } protected void recordLatency(TableMetrics metric, long latencyNanos) @@ -549,7 +550,7 @@ public class PartitionRangeReadCommand extends ReadCommand implements PartitionR } @Override - public PartitionIterator execute(ConsistencyLevel consistency, ClientState state, long queryStartNanoTime) throws RequestExecutionException + public PartitionIterator execute(ConsistencyLevel consistency, ClientState state, Dispatcher.RequestTime requestTime) throws RequestExecutionException { return executeInternal(executionController()); } diff --git a/src/java/org/apache/cassandra/db/ReadCommand.java b/src/java/org/apache/cassandra/db/ReadCommand.java index 73582c659c..2c354aa777 100644 --- a/src/java/org/apache/cassandra/db/ReadCommand.java +++ b/src/java/org/apache/cassandra/db/ReadCommand.java @@ -69,6 +69,7 @@ import org.apache.cassandra.service.ActiveRepairService; import org.apache.cassandra.service.ClientWarn; import org.apache.cassandra.tracing.Tracing; import org.apache.cassandra.utils.CassandraUInt; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.ObjectSizes; import org.apache.cassandra.utils.TimeUUID; @@ -76,8 +77,8 @@ import org.apache.cassandra.utils.TimeUUID; import static com.google.common.collect.Iterables.any; import static com.google.common.collect.Iterables.filter; import static org.apache.cassandra.utils.Clock.Global.nanoTime; -import static org.apache.cassandra.utils.MonotonicClock.Global.approxTime; import static org.apache.cassandra.db.partitions.UnfilteredPartitionIterators.MergeListener.NOOP; +import static org.apache.cassandra.utils.MonotonicClock.Global.approxTime; /** * General interface for storage-engine read commands (common to both range and @@ -789,14 +790,19 @@ public abstract class ReadCommand extends AbstractReadQuery /** * Creates a message for this command. */ - public Message createMessage(boolean trackRepairedData) + public Message createMessage(boolean trackRepairedData, Dispatcher.RequestTime requestTime) { - Message msg = trackRepairedData - ? Message.outWithFlags(verb(), this, MessageFlag.CALL_BACK_ON_FAILURE, MessageFlag.TRACK_REPAIRED_DATA) - : Message.outWithFlag(verb(), this, MessageFlag.CALL_BACK_ON_FAILURE); + List flags = new ArrayList<>(3); + flags.add(MessageFlag.CALL_BACK_ON_FAILURE); if (trackWarnings) - msg = msg.withFlag(MessageFlag.TRACK_WARNINGS); - return msg; + flags.add(MessageFlag.TRACK_WARNINGS); + if (trackRepairedData) + flags.add(MessageFlag.TRACK_REPAIRED_DATA); + + return Message.outWithFlags(verb(), + this, + requestTime, + flags); } protected abstract boolean intersects(SSTableReader sstable); diff --git a/src/java/org/apache/cassandra/db/ReadQuery.java b/src/java/org/apache/cassandra/db/ReadQuery.java index cf3097575c..798e0bc7d4 100644 --- a/src/java/org/apache/cassandra/db/ReadQuery.java +++ b/src/java/org/apache/cassandra/db/ReadQuery.java @@ -26,6 +26,7 @@ import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.pager.QueryPager; import org.apache.cassandra.service.pager.PagingState; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.utils.FBUtilities; @@ -48,7 +49,7 @@ public interface ReadQuery return ReadExecutionController.empty(); } - public PartitionIterator execute(ConsistencyLevel consistency, ClientState state, long queryStartNanoTime) throws RequestExecutionException + public PartitionIterator execute(ConsistencyLevel consistency, ClientState state, Dispatcher.RequestTime requestTime) throws RequestExecutionException { return EmptyIterators.partition(); } @@ -141,9 +142,10 @@ public interface ReadQuery * * @param consistency the consistency level to achieve for the query. * @param state client state + * @param state request enqueue / and start times * @return the result of the query. */ - public PartitionIterator execute(ConsistencyLevel consistency, ClientState state, long queryStartNanoTime) throws RequestExecutionException; + public PartitionIterator execute(ConsistencyLevel consistency, ClientState state, Dispatcher.RequestTime requestTime) throws RequestExecutionException; /** * Execute the query for internal queries (that is, it basically executes the query locally). diff --git a/src/java/org/apache/cassandra/db/SinglePartitionReadCommand.java b/src/java/org/apache/cassandra/db/SinglePartitionReadCommand.java index ae692fc5b8..c838aa43b0 100644 --- a/src/java/org/apache/cassandra/db/SinglePartitionReadCommand.java +++ b/src/java/org/apache/cassandra/db/SinglePartitionReadCommand.java @@ -77,6 +77,7 @@ import org.apache.cassandra.service.CacheService; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.StorageProxy; import org.apache.cassandra.tracing.Tracing; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.btree.BTreeSet; @@ -462,12 +463,12 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar } @Override - public PartitionIterator execute(ConsistencyLevel consistency, ClientState state, long queryStartNanoTime) throws RequestExecutionException + public PartitionIterator execute(ConsistencyLevel consistency, ClientState state, Dispatcher.RequestTime requestTime) throws RequestExecutionException { if (clusteringIndexFilter.isEmpty(metadata().comparator)) return EmptyIterators.partition(); - return StorageProxy.read(Group.one(this), consistency, queryStartNanoTime); + return StorageProxy.read(Group.one(this), consistency, requestTime); } protected void recordLatency(TableMetrics metric, long latencyNanos) @@ -1273,9 +1274,9 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar new Group(commands, limits); } - public PartitionIterator execute(ConsistencyLevel consistency, ClientState state, long queryStartNanoTime) throws RequestExecutionException + public PartitionIterator execute(ConsistencyLevel consistency, ClientState state, Dispatcher.RequestTime requestTime) throws RequestExecutionException { - return StorageProxy.read(this, consistency, queryStartNanoTime); + return StorageProxy.read(this, consistency, requestTime); } } @@ -1287,13 +1288,13 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar } @Override - public PartitionIterator execute(ConsistencyLevel consistency, ClientState state, long queryStartNanoTime) throws RequestExecutionException + public PartitionIterator execute(ConsistencyLevel consistency, ClientState state, Dispatcher.RequestTime requestTime) throws RequestExecutionException { if (queries.size() == 1) - return queries.get(0).execute(consistency, state, queryStartNanoTime); + return queries.get(0).execute(consistency, state, requestTime); return PartitionIterators.concat(queries.stream() - .map(q -> q.execute(consistency, state, queryStartNanoTime)) + .map(q -> q.execute(consistency, state, requestTime)) .collect(Collectors.toList())); } } @@ -1366,7 +1367,7 @@ public class SinglePartitionReadCommand extends ReadCommand implements SinglePar } @Override - public PartitionIterator execute(ConsistencyLevel consistency, ClientState state, long queryStartNanoTime) throws RequestExecutionException + public PartitionIterator execute(ConsistencyLevel consistency, ClientState state, Dispatcher.RequestTime requestTime) throws RequestExecutionException { return executeInternal(executionController()); } diff --git a/src/java/org/apache/cassandra/db/view/TableViews.java b/src/java/org/apache/cassandra/db/view/TableViews.java index 366d4278e0..a8ca7b7f6e 100644 --- a/src/java/org/apache/cassandra/db/view/TableViews.java +++ b/src/java/org/apache/cassandra/db/view/TableViews.java @@ -38,6 +38,7 @@ import org.apache.cassandra.schema.TableId; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.schema.TableMetadataRef; import org.apache.cassandra.service.StorageProxy; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.btree.BTree; import org.apache.cassandra.utils.btree.BTreeSet; @@ -149,7 +150,7 @@ public class TableViews extends AbstractCollection // Read modified rows long nowInSec = FBUtilities.nowInSeconds(); - long queryStartNanoTime = nanoTime(); + Dispatcher.RequestTime requestTime = Dispatcher.RequestTime.forImmediateExecution(); SinglePartitionReadCommand command = readExistingRowsCommand(update, views, nowInSec); if (command == null) return; @@ -166,7 +167,7 @@ public class TableViews extends AbstractCollection Keyspace.openAndGetStore(update.metadata()).metric.viewReadTime.update(nanoTime() - start, TimeUnit.NANOSECONDS); if (!mutations.isEmpty()) - StorageProxy.mutateMV(update.partitionKey().getKey(), mutations, writeCommitLog, baseComplete, queryStartNanoTime); + StorageProxy.mutateMV(update.partitionKey().getKey(), mutations, writeCommitLog, baseComplete, requestTime); } diff --git a/src/java/org/apache/cassandra/db/view/ViewBuilderTask.java b/src/java/org/apache/cassandra/db/view/ViewBuilderTask.java index 7bda2fddac..9a72c1e270 100644 --- a/src/java/org/apache/cassandra/db/view/ViewBuilderTask.java +++ b/src/java/org/apache/cassandra/db/view/ViewBuilderTask.java @@ -56,11 +56,11 @@ import org.apache.cassandra.gms.Gossiper; import org.apache.cassandra.io.sstable.ReducingKeyIterator; import org.apache.cassandra.io.sstable.format.SSTableReader; import org.apache.cassandra.service.StorageProxy; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.TimeUUID; import org.apache.cassandra.utils.concurrent.Refs; -import static org.apache.cassandra.utils.Clock.Global.nanoTime; import static org.apache.cassandra.utils.TimeUUID.Generator.nextTimeUUID; public class ViewBuilderTask extends CompactionInfo.Holder implements Callable @@ -114,7 +114,7 @@ public class ViewBuilderTask extends CompactionInfo.Holder implements Callable StorageProxy.mutateMV(key.getKey(), m, true, noBase, nanoTime())); + mutations.forEachRemaining(m -> StorageProxy.mutateMV(key.getKey(), m, true, noBase, Dispatcher.RequestTime.forImmediateExecution())); } } diff --git a/src/java/org/apache/cassandra/db/virtual/CIDRFilteringMetricsTable.java b/src/java/org/apache/cassandra/db/virtual/CIDRFilteringMetricsTable.java index 580dcdd1fd..6bbf82c402 100644 --- a/src/java/org/apache/cassandra/db/virtual/CIDRFilteringMetricsTable.java +++ b/src/java/org/apache/cassandra/db/virtual/CIDRFilteringMetricsTable.java @@ -43,11 +43,11 @@ import org.apache.cassandra.metrics.CIDRAuthorizerMetrics; import org.apache.cassandra.schema.SchemaConstants; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.service.ClientState; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.transport.messages.ResultMessage; import org.apache.cassandra.utils.MBeanWrapper; import static org.apache.cassandra.service.QueryState.forInternalCalls; -import static org.apache.cassandra.utils.Clock.Global.nanoTime; /** * Virtual tables capturing metrics related to CIDR filtering @@ -196,7 +196,7 @@ public class CIDRFilteringMetricsTable implements CIDRFilteringMetricsTableMBean QueryOptions options = QueryOptions.forInternalCalls(CassandraAuthorizer.authReadConsistencyLevel(), Collections.emptyList()); - ResultMessage.Rows rows = statement.execute(forInternalCalls(), options, nanoTime()); + ResultMessage.Rows rows = statement.execute(forInternalCalls(), options, Dispatcher.RequestTime.forImmediateExecution()); return UntypedResultSet.create(rows.result); } diff --git a/src/java/org/apache/cassandra/locator/AbstractReplicationStrategy.java b/src/java/org/apache/cassandra/locator/AbstractReplicationStrategy.java index c32b7be2b0..e32d940ec8 100644 --- a/src/java/org/apache/cassandra/locator/AbstractReplicationStrategy.java +++ b/src/java/org/apache/cassandra/locator/AbstractReplicationStrategy.java @@ -46,6 +46,7 @@ import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.DatacenterSyncWriteResponseHandler; import org.apache.cassandra.service.DatacenterWriteResponseHandler; import org.apache.cassandra.service.WriteResponseHandler; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.utils.FBUtilities; import org.cliffc.high_scale_lib.NonBlockingHashMap; @@ -136,32 +137,32 @@ public abstract class AbstractReplicationStrategy Runnable callback, WriteType writeType, Supplier hintOnFailure, - long queryStartNanoTime) + Dispatcher.RequestTime requestTime) { return getWriteResponseHandler(replicaPlan, callback, writeType, hintOnFailure, - queryStartNanoTime, DatabaseDescriptor.getIdealConsistencyLevel()); + requestTime, DatabaseDescriptor.getIdealConsistencyLevel()); } public AbstractWriteResponseHandler getWriteResponseHandler(ReplicaPlan.ForWrite replicaPlan, Runnable callback, WriteType writeType, Supplier hintOnFailure, - long queryStartNanoTime, + Dispatcher.RequestTime requestTime, ConsistencyLevel idealConsistencyLevel) { AbstractWriteResponseHandler resultResponseHandler; if (replicaPlan.consistencyLevel().isDatacenterLocal()) { // block for in this context will be localnodes block. - resultResponseHandler = new DatacenterWriteResponseHandler(replicaPlan, callback, writeType, hintOnFailure, queryStartNanoTime); + resultResponseHandler = new DatacenterWriteResponseHandler(replicaPlan, callback, writeType, hintOnFailure, requestTime); } else if (replicaPlan.consistencyLevel() == ConsistencyLevel.EACH_QUORUM && (this instanceof NetworkTopologyStrategy)) { - resultResponseHandler = new DatacenterSyncWriteResponseHandler(replicaPlan, callback, writeType, hintOnFailure, queryStartNanoTime); + resultResponseHandler = new DatacenterSyncWriteResponseHandler(replicaPlan, callback, writeType, hintOnFailure, requestTime); } else { - resultResponseHandler = new WriteResponseHandler(replicaPlan, callback, writeType, hintOnFailure, queryStartNanoTime); + resultResponseHandler = new WriteResponseHandler(replicaPlan, callback, writeType, hintOnFailure, requestTime); } //Check if tracking the ideal consistency level is configured @@ -181,7 +182,7 @@ public abstract class AbstractReplicationStrategy callback, writeType, hintOnFailure, - queryStartNanoTime, + requestTime, idealConsistencyLevel); resultResponseHandler.setIdealCLResponseHandler(idealHandler); } diff --git a/src/java/org/apache/cassandra/metrics/ClientMetrics.java b/src/java/org/apache/cassandra/metrics/ClientMetrics.java index 4e3eb4f8ec..5616571b9a 100644 --- a/src/java/org/apache/cassandra/metrics/ClientMetrics.java +++ b/src/java/org/apache/cassandra/metrics/ClientMetrics.java @@ -18,13 +18,23 @@ */ package org.apache.cassandra.metrics; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import com.google.common.annotations.VisibleForTesting; + import com.codahale.metrics.Gauge; import com.codahale.metrics.Histogram; import com.codahale.metrics.Meter; import com.codahale.metrics.Reservoir; +import com.codahale.metrics.Timer; import org.apache.cassandra.transport.*; import static org.apache.cassandra.metrics.CassandraMetricsRegistry.Metrics; @@ -41,15 +51,17 @@ public final class ClientMetrics private Meter authSuccess; private Meter authFailure; private AtomicInteger pausedConnections; - + @SuppressWarnings({ "unused", "FieldCanBeLocal" }) private Gauge pausedConnectionsGauge; - + private Meter connectionPaused; private Meter requestDiscarded; private Meter requestDispatched; + private Meter timedOutBeforeProcessing; private Meter protocolException; private Meter unknownException; + private Timer queueTime; private ClientMetrics() { @@ -65,11 +77,22 @@ public final class ClientMetrics authFailure.mark(); } - public void pauseConnection() { pausedConnections.incrementAndGet(); } + @VisibleForTesting + public int getNumberOfPausedConnections() + { + return (int) connectionPaused.getCount(); + } + + public void pauseConnection() + { + connectionPaused.mark(); + pausedConnections.incrementAndGet(); + } public void unpauseConnection() { pausedConnections.decrementAndGet(); } public void markRequestDiscarded() { requestDiscarded.mark(); } public void markRequestDispatched() { requestDispatched.mark(); } + public void markTimedOutBeforeProcessing() { timedOutBeforeProcessing.mark(); } public List allConnectedClients() { @@ -120,13 +143,16 @@ public final class ClientMetrics pausedConnections = new AtomicInteger(); pausedConnectionsGauge = registerGauge("PausedConnections", pausedConnections::get); + connectionPaused = registerMeter("ConnectionPaused"); requestDiscarded = registerMeter("RequestDiscarded"); requestDispatched = registerMeter("RequestDispatched"); + timedOutBeforeProcessing = registerMeter("TimedOutBeforeProcessing"); protocolException = registerMeter("ProtocolException"); unknownException = registerMeter("UnknownException"); initialized = true; + queueTime = registerTimer("Queued"); } private int countConnectedClients() @@ -191,4 +217,14 @@ public final class ClientMetrics { return Metrics.meter(factory.createMetricName(name)); } + + public Timer registerTimer(String name) + { + return Metrics.timer(factory.createMetricName(name)); + } + + public void queueTime(long value, TimeUnit unit) + { + queueTime.update(value, unit); + } } diff --git a/src/java/org/apache/cassandra/metrics/ThreadPoolMetrics.java b/src/java/org/apache/cassandra/metrics/ThreadPoolMetrics.java index 62ef1775fb..c29254011c 100644 --- a/src/java/org/apache/cassandra/metrics/ThreadPoolMetrics.java +++ b/src/java/org/apache/cassandra/metrics/ThreadPoolMetrics.java @@ -40,6 +40,7 @@ public class ThreadPoolMetrics public static final String TOTAL_BLOCKED_TASKS = "TotalBlockedTasks"; public static final String MAX_POOL_SIZE = "MaxPoolSize"; public static final String MAX_TASKS_QUEUED = "MaxTasksQueued"; + public static final String OLDEST_TASK_QUEUE_TIME = "OldestTaskQueueTime"; /** Number of active tasks. */ public final Gauge activeTasks; @@ -62,6 +63,9 @@ public class ThreadPoolMetrics /** Maximum number of threads before it will start queuing tasks */ public final Gauge maxPoolSize; + /** For how long the oldest task in the queue was queued */ + public final Gauge oldestTaskQueueTime; + /** Maximum number of tasks queued before a task get blocked */ public final Gauge maxTasksQueued; @@ -87,6 +91,7 @@ public class ThreadPoolMetrics completedTasks = executor::getCompletedTaskCount; maxPoolSize = executor::getMaximumPoolSize; maxTasksQueued = executor::getMaxTasksQueued; + oldestTaskQueueTime = executor::oldestTaskQueueTime; } public ThreadPoolMetrics register() @@ -98,6 +103,7 @@ public class ThreadPoolMetrics Metrics.register(makeMetricName(path, poolName, TOTAL_BLOCKED_TASKS), totalBlocked); Metrics.register(makeMetricName(path, poolName, MAX_POOL_SIZE), maxPoolSize); Metrics.register(makeMetricName(path, poolName, MAX_TASKS_QUEUED), maxTasksQueued); + Metrics.register(makeMetricName(path, poolName, OLDEST_TASK_QUEUE_TIME), oldestTaskQueueTime); return Metrics.register(this); } @@ -110,6 +116,7 @@ public class ThreadPoolMetrics Metrics.remove(makeMetricName(path, poolName, TOTAL_BLOCKED_TASKS)); Metrics.remove(makeMetricName(path, poolName, MAX_POOL_SIZE)); Metrics.remove(makeMetricName(path, poolName, MAX_TASKS_QUEUED)); + Metrics.remove(makeMetricName(path, poolName, OLDEST_TASK_QUEUE_TIME)); Metrics.remove(this); } diff --git a/src/java/org/apache/cassandra/net/InboundMessageHandler.java b/src/java/org/apache/cassandra/net/InboundMessageHandler.java index 5fb7fdcb93..50a42e7b71 100644 --- a/src/java/org/apache/cassandra/net/InboundMessageHandler.java +++ b/src/java/org/apache/cassandra/net/InboundMessageHandler.java @@ -403,24 +403,24 @@ public class InboundMessageHandler extends AbstractMessageHandler { /** * Actually handle the message. Runs on the appropriate {@link Stage} for the {@link Verb}. - * + *

* Small messages will come pre-deserialized. Large messages will be deserialized on the stage, * just in time, and only then processed. */ public void run() { Header header = header(); - long currentTimeNanos = approxTime.now(); - boolean expired = approxTime.isAfter(currentTimeNanos, header.expiresAtNanos); + long approxStartTimeNanos = approxTime.now(); + boolean expired = approxTime.isAfter(approxStartTimeNanos, header.expiresAtNanos); boolean processed = false; try { - callbacks.onExecuting(size(), header, currentTimeNanos - header.createdAtNanos, NANOSECONDS); + callbacks.onExecuting(size(), header, approxStartTimeNanos - header.createdAtNanos, NANOSECONDS); if (expired) { - callbacks.onExpired(size(), header, currentTimeNanos - header.createdAtNanos, NANOSECONDS); + callbacks.onExpired(size(), header, approxStartTimeNanos - header.createdAtNanos, NANOSECONDS); return; } @@ -441,7 +441,7 @@ public class InboundMessageHandler extends AbstractMessageHandler releaseResources(); - callbacks.onExecuted(size(), header, approxTime.now() - currentTimeNanos, NANOSECONDS); + callbacks.onExecuted(size(), header, approxTime.now() - approxStartTimeNanos, NANOSECONDS); } } diff --git a/src/java/org/apache/cassandra/net/Message.java b/src/java/org/apache/cassandra/net/Message.java index c60ebee972..705061562a 100644 --- a/src/java/org/apache/cassandra/net/Message.java +++ b/src/java/org/apache/cassandra/net/Message.java @@ -22,6 +22,7 @@ import java.nio.ByteBuffer; import java.util.Collections; import java.util.EnumMap; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -42,6 +43,7 @@ import org.apache.cassandra.io.util.DataOutputPlus; import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.tracing.Tracing; import org.apache.cassandra.tracing.Tracing.TraceType; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.utils.MonotonicClockTranslation; import org.apache.cassandra.utils.NoSpamLogger; import org.apache.cassandra.utils.TimeUUID; @@ -217,6 +219,23 @@ public class Message return outWithParam(nextId(), verb, 0, payload, flag2.addTo(flag1.addTo(0)), null, null); } + public static Message outWithFlags(Verb verb, T payload, Dispatcher.RequestTime requestTime, List flags) + { + assert !verb.isResponse(); + int encodedFlags = 0; + for (MessageFlag flag : flags) + encodedFlags = flag.addTo(encodedFlags); + + return new Message(new Header(nextId(), + verb, + getBroadcastAddressAndPort(), + requestTime.startedAtNanos(), + requestTime.computeDeadline(verb.expiresAfterNanos()), + encodedFlags, + buildParams(null, null)), + payload); + } + @VisibleForTesting static Message outWithParam(long id, Verb verb, T payload, ParamType paramType, Object paramValue) { @@ -832,15 +851,13 @@ public class Message Map params = extractParams(buf, index, version); long createdAtNanos = calculateCreationTimeNanos(createdAtMillis, timeSnapshot, currentTimeNanos); - long expiresAtNanos = getExpiresAtNanos(createdAtNanos, currentTimeNanos, TimeUnit.MILLISECONDS.toNanos(expiresInMillis)); + long expiresAtNanos = getExpiresAtNanos(createdAtNanos, TimeUnit.MILLISECONDS.toNanos(expiresInMillis)); return new Header(id, verb, from, createdAtNanos, expiresAtNanos, flags, params); } - private static long getExpiresAtNanos(long createdAtNanos, long currentTimeNanos, long expirationPeriodNanos) + private static long getExpiresAtNanos(long createdAtNanos, long expirationPeriodNanos) { - if (!DatabaseDescriptor.hasCrossNodeTimeout() || createdAtNanos > currentTimeNanos) - createdAtNanos = currentTimeNanos; return createdAtNanos + expirationPeriodNanos; } @@ -862,7 +879,7 @@ public class Message long currentTimeNanos = approxTime.now(); MonotonicClockTranslation timeSnapshot = approxTime.translate(); long creationTimeNanos = calculateCreationTimeNanos(in.readInt(), timeSnapshot, currentTimeNanos); - long expiresAtNanos = getExpiresAtNanos(creationTimeNanos, currentTimeNanos, TimeUnit.MILLISECONDS.toNanos(in.readUnsignedVInt())); + long expiresAtNanos = getExpiresAtNanos(creationTimeNanos, TimeUnit.MILLISECONDS.toNanos(in.readUnsignedVInt())); Verb verb = Verb.fromId(in.readUnsignedVInt32()); int flags = in.readUnsignedVInt32(); Map params = deserializeParams(in, version); @@ -901,6 +918,10 @@ public class Message @VisibleForTesting static long calculateCreationTimeNanos(int messageTimestampMillis, MonotonicClockTranslation timeSnapshot, long currentTimeNanos) { + // We do not trust external time source, so we override their value with current time + if (!DatabaseDescriptor.hasCrossNodeTimeout()) + return currentTimeNanos; + long currentTimeMillis = timeSnapshot.toMillisSinceEpoch(currentTimeNanos); // Reconstruct the message construction time sent by the remote host (we sent only the lower 4 bytes, assuming the // higher 4 bytes wouldn't change between the sender and receiver) diff --git a/src/java/org/apache/cassandra/repair/RepairCoordinator.java b/src/java/org/apache/cassandra/repair/RepairCoordinator.java index 82664f2d3c..091819c65d 100644 --- a/src/java/org/apache/cassandra/repair/RepairCoordinator.java +++ b/src/java/org/apache/cassandra/repair/RepairCoordinator.java @@ -45,6 +45,7 @@ import org.apache.cassandra.net.Verb; import org.apache.cassandra.repair.messages.FailSession; import org.apache.cassandra.repair.messages.RepairMessage; import org.apache.cassandra.repair.state.ParticipateState; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.utils.*; import org.apache.cassandra.utils.concurrent.Future; import org.apache.commons.lang3.time.DurationFormatUtils; @@ -551,7 +552,7 @@ public class RepairCoordinator implements Runnable, ProgressEventNotifier, Repai QueryOptions options = QueryOptions.forInternalCalls(ConsistencyLevel.ONE, Lists.newArrayList(sessionIdBytes, tminBytes, tmaxBytes)); - ResultMessage.Rows rows = statement.execute(forInternalCalls(), options, ctx.clock().nanoTime()); + ResultMessage.Rows rows = statement.execute(forInternalCalls(), options, new Dispatcher.RequestTime(ctx.clock().nanoTime())); UntypedResultSet result = UntypedResultSet.create(rows.result); for (UntypedResultSet.Row r : result) diff --git a/src/java/org/apache/cassandra/service/AbstractWriteResponseHandler.java b/src/java/org/apache/cassandra/service/AbstractWriteResponseHandler.java index d2c54f9f8f..ceda09450e 100644 --- a/src/java/org/apache/cassandra/service/AbstractWriteResponseHandler.java +++ b/src/java/org/apache/cassandra/service/AbstractWriteResponseHandler.java @@ -25,45 +25,42 @@ import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; - import javax.annotation.Nullable; -import org.apache.cassandra.db.ConsistencyLevel; - -import org.apache.cassandra.db.Mutation; -import org.apache.cassandra.locator.EndpointsForToken; -import org.apache.cassandra.locator.ReplicaPlan; -import org.apache.cassandra.locator.ReplicaPlan.ForWrite; -import org.apache.cassandra.utils.concurrent.Condition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.cassandra.db.ColumnFamilyStore; +import org.apache.cassandra.db.ConsistencyLevel; import org.apache.cassandra.db.IMutation; +import org.apache.cassandra.db.Mutation; import org.apache.cassandra.db.WriteType; import org.apache.cassandra.exceptions.RequestFailureReason; import org.apache.cassandra.exceptions.WriteFailureException; import org.apache.cassandra.exceptions.WriteTimeoutException; +import org.apache.cassandra.locator.EndpointsForToken; import org.apache.cassandra.locator.InetAddressAndPort; -import org.apache.cassandra.net.RequestCallback; +import org.apache.cassandra.locator.ReplicaPlan; +import org.apache.cassandra.locator.ReplicaPlan.ForWrite; import org.apache.cassandra.net.Message; +import org.apache.cassandra.net.RequestCallback; +import org.apache.cassandra.transport.Dispatcher; +import org.apache.cassandra.utils.concurrent.Condition; import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException; import static java.lang.Long.MAX_VALUE; import static java.lang.Math.min; import static java.util.concurrent.TimeUnit.MICROSECONDS; import static java.util.concurrent.TimeUnit.NANOSECONDS; - -import static org.apache.cassandra.utils.Clock.Global.nanoTime; import static java.util.stream.Collectors.toList; import static org.apache.cassandra.config.DatabaseDescriptor.getCounterWriteRpcTimeout; import static org.apache.cassandra.config.DatabaseDescriptor.getWriteRpcTimeout; import static org.apache.cassandra.db.WriteType.COUNTER; +import static org.apache.cassandra.locator.Replicas.countInOurDc; import static org.apache.cassandra.schema.Schema.instance; import static org.apache.cassandra.service.StorageProxy.WritePerformer; +import static org.apache.cassandra.utils.Clock.Global.nanoTime; import static org.apache.cassandra.utils.concurrent.Condition.newOneTimeCondition; -import static org.apache.cassandra.locator.Replicas.countInOurDc; - public abstract class AbstractWriteResponseHandler implements RequestCallback { @@ -80,7 +77,7 @@ public abstract class AbstractWriteResponseHandler implements RequestCallback AtomicIntegerFieldUpdater.newUpdater(AbstractWriteResponseHandler.class, "failures"); private volatile int failures = 0; private final Map failureReasonByEndpoint; - private final long queryStartNanoTime; + private final Dispatcher.RequestTime requestTime; private @Nullable final Supplier hintOnFailure; /** @@ -99,17 +96,17 @@ public abstract class AbstractWriteResponseHandler implements RequestCallback /** * @param callback A callback to be called when the write is successful. * @param hintOnFailure - * @param queryStartNanoTime + * @param requestTime */ protected AbstractWriteResponseHandler(ForWrite replicaPlan, Runnable callback, WriteType writeType, - Supplier hintOnFailure, long queryStartNanoTime) + Supplier hintOnFailure, Dispatcher.RequestTime requestTime) { this.replicaPlan = replicaPlan; this.callback = callback; this.writeType = writeType; this.hintOnFailure = hintOnFailure; this.failureReasonByEndpoint = new ConcurrentHashMap<>(); - this.queryStartNanoTime = queryStartNanoTime; + this.requestTime = requestTime; } public void get() throws WriteTimeoutException, WriteFailureException @@ -154,10 +151,11 @@ public abstract class AbstractWriteResponseHandler implements RequestCallback public final long currentTimeoutNanos() { + long now = nanoTime(); long requestTimeout = writeType == COUNTER ? getCounterWriteRpcTimeout(NANOSECONDS) : getWriteRpcTimeout(NANOSECONDS); - return requestTimeout - (nanoTime() - queryStartNanoTime); + return requestTime.computeTimeout(now, requestTimeout); } /** @@ -292,7 +290,7 @@ public abstract class AbstractWriteResponseHandler implements RequestCallback if (blockFor() + n > candidateReplicaCount()) signal(); - if (hintOnFailure != null && StorageProxy.shouldHint(replicaPlan.lookup(from))) + if (hintOnFailure != null && StorageProxy.shouldHint(replicaPlan.lookup(from)) && requestTime.shouldSendHints()) StorageProxy.submitHint(hintOnFailure.get(), replicaPlan.lookup(from), null); } @@ -320,7 +318,7 @@ public abstract class AbstractWriteResponseHandler implements RequestCallback } else { - replicaPlan.keyspace().metric.idealCLWriteLatency.addNano(nanoTime() - queryStartNanoTime); + replicaPlan.keyspace().metric.idealCLWriteLatency.addNano(nanoTime() - requestTime.startedAtNanos()); } } } @@ -354,7 +352,8 @@ public abstract class AbstractWriteResponseHandler implements RequestCallback writePerformer.apply(mutation, replicaPlan.withContacts(uncontacted), (AbstractWriteResponseHandler) this, - localDC); + localDC, + requestTime); } } catch (InterruptedException e) diff --git a/src/java/org/apache/cassandra/service/BatchlogResponseHandler.java b/src/java/org/apache/cassandra/service/BatchlogResponseHandler.java index c1c828f75b..0fa2847700 100644 --- a/src/java/org/apache/cassandra/service/BatchlogResponseHandler.java +++ b/src/java/org/apache/cassandra/service/BatchlogResponseHandler.java @@ -25,6 +25,7 @@ import org.apache.cassandra.exceptions.WriteFailureException; import org.apache.cassandra.exceptions.WriteTimeoutException; import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.net.Message; +import org.apache.cassandra.transport.Dispatcher; public class BatchlogResponseHandler extends AbstractWriteResponseHandler { @@ -34,9 +35,9 @@ public class BatchlogResponseHandler extends AbstractWriteResponseHandler private static final AtomicIntegerFieldUpdater requiredBeforeFinishUpdater = AtomicIntegerFieldUpdater.newUpdater(BatchlogResponseHandler.class, "requiredBeforeFinish"); - public BatchlogResponseHandler(AbstractWriteResponseHandler wrapped, int requiredBeforeFinish, BatchlogCleanup cleanup, long queryStartNanoTime) + public BatchlogResponseHandler(AbstractWriteResponseHandler wrapped, int requiredBeforeFinish, BatchlogCleanup cleanup, Dispatcher.RequestTime requestTime) { - super(wrapped.replicaPlan, wrapped.callback, wrapped.writeType, null, queryStartNanoTime); + super(wrapped.replicaPlan, wrapped.callback, wrapped.writeType, null, requestTime); this.wrapped = wrapped; this.requiredBeforeFinish = requiredBeforeFinish; this.cleanup = cleanup; diff --git a/src/java/org/apache/cassandra/service/CassandraDaemon.java b/src/java/org/apache/cassandra/service/CassandraDaemon.java index ae457a5009..4fc4010e85 100644 --- a/src/java/org/apache/cassandra/service/CassandraDaemon.java +++ b/src/java/org/apache/cassandra/service/CassandraDaemon.java @@ -111,6 +111,12 @@ public class CassandraDaemon return instance; } + @VisibleForTesting + public NativeTransportService nativeTransportService() + { + return nativeTransportService; + } + static { // Need to register metrics before instrumented appender is created(first access to LoggerFactory). SharedMetricRegistries.getOrCreate("logback-metrics").addListener(new MetricRegistryListener.Base() @@ -818,12 +824,16 @@ public class CassandraDaemon StorageService.instance.setRpcReady(true); } + @Deprecated(since = "5.0.0") public void stopNativeTransport() + { + stopNativeTransport(false); + } + + public void stopNativeTransport(boolean force) { if (nativeTransportService != null) - { - nativeTransportService.stop(); - } + nativeTransportService.stop(force); } public boolean isNativeTransportRunning() diff --git a/src/java/org/apache/cassandra/service/DatacenterSyncWriteResponseHandler.java b/src/java/org/apache/cassandra/service/DatacenterSyncWriteResponseHandler.java index 2e26bb9a55..e4b208b582 100644 --- a/src/java/org/apache/cassandra/service/DatacenterSyncWriteResponseHandler.java +++ b/src/java/org/apache/cassandra/service/DatacenterSyncWriteResponseHandler.java @@ -31,6 +31,7 @@ import org.apache.cassandra.locator.ReplicaPlan; import org.apache.cassandra.net.Message; import org.apache.cassandra.db.ConsistencyLevel; import org.apache.cassandra.db.WriteType; +import org.apache.cassandra.transport.Dispatcher; /** * This class blocks for a quorum of responses _in all datacenters_ (CL.EACH_QUORUM). @@ -46,10 +47,10 @@ public class DatacenterSyncWriteResponseHandler extends AbstractWriteResponse Runnable callback, WriteType writeType, Supplier hintOnFailure, - long queryStartNanoTime) + Dispatcher.RequestTime requestTime) { // Response is been managed by the map so make it 1 for the superclass. - super(replicaPlan, callback, writeType, hintOnFailure, queryStartNanoTime); + super(replicaPlan, callback, writeType, hintOnFailure, requestTime); assert replicaPlan.consistencyLevel() == ConsistencyLevel.EACH_QUORUM; if (replicaPlan.replicationStrategy() instanceof NetworkTopologyStrategy) diff --git a/src/java/org/apache/cassandra/service/DatacenterWriteResponseHandler.java b/src/java/org/apache/cassandra/service/DatacenterWriteResponseHandler.java index 4920a54516..f33d6607e1 100644 --- a/src/java/org/apache/cassandra/service/DatacenterWriteResponseHandler.java +++ b/src/java/org/apache/cassandra/service/DatacenterWriteResponseHandler.java @@ -23,6 +23,7 @@ import org.apache.cassandra.locator.InOurDc; import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.locator.ReplicaPlan; import org.apache.cassandra.net.Message; +import org.apache.cassandra.transport.Dispatcher; import java.util.function.Predicate; import java.util.function.Supplier; @@ -38,9 +39,9 @@ public class DatacenterWriteResponseHandler extends WriteResponseHandler Runnable callback, WriteType writeType, Supplier hintOnFailure, - long queryStartNanoTime) + Dispatcher.RequestTime requestTime) { - super(replicaPlan, callback, writeType, hintOnFailure, queryStartNanoTime); + super(replicaPlan, callback, writeType, hintOnFailure, requestTime); assert replicaPlan.consistencyLevel().isDatacenterLocal(); } diff --git a/src/java/org/apache/cassandra/service/NativeTransportService.java b/src/java/org/apache/cassandra/service/NativeTransportService.java index cc6ee37d49..c51b29e804 100644 --- a/src/java/org/apache/cassandra/service/NativeTransportService.java +++ b/src/java/org/apache/cassandra/service/NativeTransportService.java @@ -139,7 +139,12 @@ public class NativeTransportService */ public void stop() { - servers.forEach(Server::stop); + stop(false); + } + + public void stop(boolean force) + { + servers.forEach((s) -> s.stop(force)); } /** diff --git a/src/java/org/apache/cassandra/service/StorageProxy.java b/src/java/org/apache/cassandra/service/StorageProxy.java index 2f3d8683f7..7bf8227577 100644 --- a/src/java/org/apache/cassandra/service/StorageProxy.java +++ b/src/java/org/apache/cassandra/service/StorageProxy.java @@ -21,6 +21,7 @@ import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -136,6 +137,7 @@ import org.apache.cassandra.service.reads.ReadCallback; import org.apache.cassandra.service.reads.range.RangeCommands; import org.apache.cassandra.service.reads.repair.ReadRepair; import org.apache.cassandra.tracing.Tracing; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.triggers.TriggerExecutor; import org.apache.cassandra.utils.Clock; import org.apache.cassandra.utils.FBUtilities; @@ -220,10 +222,10 @@ public class StorageProxy implements StorageProxyMBean MBeanWrapper.instance.registerMBean(instance, MBEAN_NAME); HintsService.instance.registerMBean(); - standardWritePerformer = (mutation, targets, responseHandler, localDataCenter) -> + standardWritePerformer = (mutation, targets, responseHandler, localDataCenter, requestTime) -> { assert mutation instanceof Mutation; - sendToHintedReplicas((Mutation) mutation, targets, responseHandler, localDataCenter, Stage.MUTATION); + sendToHintedReplicas((Mutation) mutation, targets, responseHandler, localDataCenter, Stage.MUTATION, requestTime); }; /* @@ -232,19 +234,19 @@ public class StorageProxy implements StorageProxyMBean * but on the latter case, the verb handler already run on the COUNTER_MUTATION stage, so we must not execute the * underlying on the stage otherwise we risk a deadlock. Hence two different performer. */ - counterWritePerformer = (mutation, targets, responseHandler, localDataCenter) -> + counterWritePerformer = (mutation, targets, responseHandler, localDataCenter, requestTime) -> { EndpointsForToken selected = targets.contacts().withoutSelf(); Replicas.temporaryAssertFull(selected); // TODO CASSANDRA-14548 - counterWriteTask(mutation, targets.withContacts(selected), responseHandler, localDataCenter).run(); + counterWriteTask(mutation, targets.withContacts(selected), responseHandler, localDataCenter, requestTime).run(); }; - counterWriteOnCoordinatorPerformer = (mutation, targets, responseHandler, localDataCenter) -> + counterWriteOnCoordinatorPerformer = (mutation, targets, responseHandler, localDataCenter, requestTime) -> { EndpointsForToken selected = targets.contacts().withoutSelf(); Replicas.temporaryAssertFull(selected); // TODO CASSANDRA-14548 Stage.COUNTER_MUTATION.executor() - .execute(counterWriteTask(mutation, targets.withContacts(selected), responseHandler, localDataCenter)); + .execute(counterWriteTask(mutation, targets.withContacts(selected), responseHandler, localDataCenter, requestTime)); }; @@ -310,7 +312,7 @@ public class StorageProxy implements StorageProxyMBean ConsistencyLevel consistencyForCommit, ClientState clientState, long nowInSeconds, - long queryStartNanoTime) + Dispatcher.RequestTime requestTime) throws UnavailableException, IsBootstrappingException, RequestFailureException, RequestTimeoutException, InvalidRequestException, CasWriteUnknownResultException { if (DatabaseDescriptor.getPartitionDenylistEnabled() && DatabaseDescriptor.getDenylistWritesEnabled() && !partitionDenylist.isKeyPermitted(keyspaceName, cfName, key.getKey())) @@ -322,7 +324,7 @@ public class StorageProxy implements StorageProxyMBean return Paxos.useV2() ? Paxos.cas(key, request, consistencyForPaxos, consistencyForCommit, clientState) - : legacyCas(keyspaceName, cfName, key, request, consistencyForPaxos, consistencyForCommit, clientState, nowInSeconds, queryStartNanoTime); + : legacyCas(keyspaceName, cfName, key, request, consistencyForPaxos, consistencyForCommit, clientState, nowInSeconds, requestTime); } public static RowIterator legacyCas(String keyspaceName, @@ -333,10 +335,9 @@ public class StorageProxy implements StorageProxyMBean ConsistencyLevel consistencyForCommit, ClientState clientState, long nowInSeconds, - long queryStartNanoTime) + Dispatcher.RequestTime requestTime) throws UnavailableException, IsBootstrappingException, RequestFailureException, RequestTimeoutException, InvalidRequestException { - final long startTimeForMetrics = nanoTime(); try { TableMetadata metadata = Schema.instance.validateTable(keyspaceName, cfName); @@ -345,11 +346,11 @@ public class StorageProxy implements StorageProxyMBean { // read the current values and check they validate the conditions Tracing.trace("Reading existing values for CAS precondition"); - SinglePartitionReadCommand readCommand = (SinglePartitionReadCommand) request.readCommand(nowInSeconds); + SinglePartitionReadCommand readCommand = request.readCommand(nowInSeconds); ConsistencyLevel readConsistency = consistencyForPaxos == ConsistencyLevel.LOCAL_SERIAL ? ConsistencyLevel.LOCAL_QUORUM : ConsistencyLevel.QUORUM; FilteredPartition current; - try (RowIterator rowIter = readOne(readCommand, readConsistency, queryStartNanoTime)) + try (RowIterator rowIter = readOne(readCommand, readConsistency, requestTime)) { current = FilteredPartition.create(rowIter); } @@ -388,7 +389,7 @@ public class StorageProxy implements StorageProxyMBean consistencyForPaxos, consistencyForCommit, consistencyForCommit, - queryStartNanoTime, + requestTime, casWriteMetrics, updateProposer); @@ -430,7 +431,9 @@ public class StorageProxy implements StorageProxyMBean } finally { - final long latency = nanoTime() - startTimeForMetrics; + // We track latency based on request processing time, since the amount of time that request spends in the queue + // is not a representative metric of replica performance. + long latency = nanoTime() - requestTime.startedAtNanos(); casWriteMetrics.addNano(latency); writeMetricsForLevel(consistencyForPaxos).addNano(latency); } @@ -466,7 +469,7 @@ public class StorageProxy implements StorageProxyMBean * {@link ConsistencyLevel#LOCAL_SERIAL}). * @param consistencyForReplayCommits the consistency for the commit phase of "replayed" in-progress operations. * @param consistencyForCommit the consistency for the commit phase of _this_ operation update. - * @param queryStartNanoTime the nano time for the start of the query this is part of. This is the base time for + * @param requestTime the nano time for the start of the query this is part of. This is the base time for * timeouts. * @param casMetrics the metrics to update for this operation. * @param createUpdateProposal method called after a successful 'prepare' phase to obtain 1) the actual update of @@ -481,7 +484,7 @@ public class StorageProxy implements StorageProxyMBean ConsistencyLevel consistencyForPaxos, ConsistencyLevel consistencyForReplayCommits, ConsistencyLevel consistencyForCommit, - long queryStartNanoTime, + Dispatcher.RequestTime requestTime, CASClientRequestMetrics casMetrics, Function> createUpdateProposal) throws UnavailableException, IsBootstrappingException, RequestFailureException, RequestTimeoutException, InvalidRequestException @@ -496,12 +499,13 @@ public class StorageProxy implements StorageProxyMBean consistencyForCommit.validateForCasCommit(latestRs); long timeoutNanos = DatabaseDescriptor.getCasContentionTimeout(NANOSECONDS); - while (nanoTime() - queryStartNanoTime < timeoutNanos) + long deadline = requestTime.computeDeadline(timeoutNanos); + while (nanoTime() < deadline) { // for simplicity, we'll do a single liveness check at the start of each attempt ReplicaPlan.ForPaxosWrite replicaPlan = ReplicaPlans.forPaxos(keyspace, key, consistencyForPaxos); latestRs = replicaPlan.replicationStrategy(); - PaxosBallotAndContention pair = beginAndRepairPaxos(queryStartNanoTime, + PaxosBallotAndContention pair = beginAndRepairPaxos(requestTime, key, metadata, replicaPlan, @@ -519,14 +523,14 @@ public class StorageProxy implements StorageProxyMBean Commit proposal = Commit.newProposal(ballot, proposalPair.left); Tracing.trace("CAS precondition is met; proposing client-requested updates for {}", ballot); - if (proposePaxos(proposal, replicaPlan, true, queryStartNanoTime)) + if (proposePaxos(proposal, replicaPlan, true, requestTime)) { // We skip committing accepted updates when they are empty. This is an optimization which works // because we also skip replaying those same empty update in beginAndRepairPaxos (see the longer // comment there). As empty update are somewhat common (serial reads and non-applying CAS propose // them), this is worth bothering. if (!proposal.update.isEmpty()) - commitPaxos(proposal, consistencyForCommit, true, queryStartNanoTime); + commitPaxos(proposal, consistencyForCommit, true, requestTime); RowIterator result = proposalPair.right; if (result != null) Tracing.trace("CAS did not apply"); @@ -569,7 +573,7 @@ public class StorageProxy implements StorageProxyMBean * @return the Paxos ballot promised by the replicas if no in-progress requests were seen and a quorum of * nodes have seen the mostRecentCommit. Otherwise, return null. */ - private static PaxosBallotAndContention beginAndRepairPaxos(long queryStartNanoTime, + private static PaxosBallotAndContention beginAndRepairPaxos(Dispatcher.RequestTime requestTime, DecoratedKey key, TableMetadata metadata, ReplicaPlan.ForPaxosWrite paxosPlan, @@ -582,7 +586,8 @@ public class StorageProxy implements StorageProxyMBean PrepareCallback summary = null; int contentions = 0; - while (nanoTime() - queryStartNanoTime < timeoutNanos) + long deadline = requestTime.computeDeadline(timeoutNanos); + while (nanoTime() < deadline) { // We want a timestamp that is guaranteed to be unique for that node (so that the ballot is globally unique), but if we've got a prepare rejected // already we also want to make sure we pick a timestamp that has a chance to be promised, i.e. one that is greater that the most recently known @@ -598,7 +603,7 @@ public class StorageProxy implements StorageProxyMBean { Tracing.trace("Preparing {}", ballot); Commit toPrepare = Commit.newPrepare(key, metadata, ballot); - summary = preparePaxos(toPrepare, paxosPlan, queryStartNanoTime); + summary = preparePaxos(toPrepare, paxosPlan, requestTime); if (!summary.promised) { Tracing.trace("Some replicas have already promised a higher ballot than ours; aborting"); @@ -634,9 +639,9 @@ public class StorageProxy implements StorageProxyMBean Tracing.trace("Finishing incomplete paxos round {}", inProgress); casMetrics.unfinishedCommit.inc(); Commit refreshedInProgress = Commit.newProposal(ballot, inProgress.update); - if (proposePaxos(refreshedInProgress, paxosPlan, false, queryStartNanoTime)) + if (proposePaxos(refreshedInProgress, paxosPlan, false, requestTime)) { - commitPaxos(refreshedInProgress, consistencyForCommit, false, queryStartNanoTime); + commitPaxos(refreshedInProgress, consistencyForCommit, false, requestTime); } else { @@ -686,10 +691,10 @@ public class StorageProxy implements StorageProxyMBean MessagingService.instance().send(message, target); } - private static PrepareCallback preparePaxos(Commit toPrepare, ReplicaPlan.ForPaxosWrite replicaPlan, long queryStartNanoTime) + private static PrepareCallback preparePaxos(Commit toPrepare, ReplicaPlan.ForPaxosWrite replicaPlan, Dispatcher.RequestTime requestTime) throws WriteTimeoutException { - PrepareCallback callback = new PrepareCallback(toPrepare.update.partitionKey(), toPrepare.update.metadata(), replicaPlan.requiredParticipants(), replicaPlan.consistencyLevel(), queryStartNanoTime); + PrepareCallback callback = new PrepareCallback(toPrepare.update.partitionKey(), toPrepare.update.metadata(), replicaPlan.requiredParticipants(), replicaPlan.consistencyLevel(), requestTime); Message message = Message.out(PAXOS_PREPARE_REQ, toPrepare); boolean hasLocalRequest = false; @@ -730,10 +735,10 @@ public class StorageProxy implements StorageProxyMBean * When {@param backoffIfPartial} is true, the proposer backs off when seeing the proposal being accepted by some but not a quorum. * The result of the cooresponding CAS in uncertain as the accepted proposal may or may not be spread to other nodes in later rounds. */ - private static boolean proposePaxos(Commit proposal, ReplicaPlan.ForPaxosWrite replicaPlan, boolean backoffIfPartial, long queryStartNanoTime) + private static boolean proposePaxos(Commit proposal, ReplicaPlan.ForPaxosWrite replicaPlan, boolean backoffIfPartial, Dispatcher.RequestTime requestTime) throws WriteTimeoutException, CasWriteUnknownResultException { - ProposeCallback callback = new ProposeCallback(replicaPlan.contacts().size(), replicaPlan.requiredParticipants(), !backoffIfPartial, replicaPlan.consistencyLevel(), queryStartNanoTime); + ProposeCallback callback = new ProposeCallback(replicaPlan.contacts().size(), replicaPlan.requiredParticipants(), !backoffIfPartial, replicaPlan.consistencyLevel(), requestTime); Message message = Message.out(PAXOS_PROPOSE_REQ, proposal); for (Replica replica : replicaPlan.contacts()) { @@ -767,7 +772,7 @@ public class StorageProxy implements StorageProxyMBean return false; } - private static void commitPaxos(Commit proposal, ConsistencyLevel consistencyLevel, boolean allowHints, long queryStartNanoTime) throws WriteTimeoutException + private static void commitPaxos(Commit proposal, ConsistencyLevel consistencyLevel, boolean allowHints, Dispatcher.RequestTime requestTime) throws WriteTimeoutException { boolean shouldBlock = consistencyLevel != ConsistencyLevel.ANY; Keyspace keyspace = Keyspace.open(proposal.update.metadata().keyspace); @@ -780,7 +785,7 @@ public class StorageProxy implements StorageProxyMBean if (shouldBlock) { AbstractReplicationStrategy rs = replicaPlan.replicationStrategy(); - responseHandler = rs.getWriteResponseHandler(replicaPlan, null, WriteType.SIMPLE, proposal::makeMutation, queryStartNanoTime); + responseHandler = rs.getWriteResponseHandler(replicaPlan, null, WriteType.SIMPLE, proposal::makeMutation, requestTime); } Message message = Message.outWithFlag(PAXOS_COMMIT_REQ, proposal, MessageFlag.CALL_BACK_ON_FAILURE); @@ -794,7 +799,7 @@ public class StorageProxy implements StorageProxyMBean if (shouldBlock) { if (replica.isSelf()) - commitPaxosLocal(replica, message, responseHandler); + commitPaxosLocal(replica, message, responseHandler, requestTime); else MessagingService.instance().sendWriteWithCallback(message, replica, responseHandler); } @@ -825,9 +830,9 @@ public class StorageProxy implements StorageProxyMBean * submit a fake one that executes immediately on the mutation stage, but generates the necessary backpressure * signal for hints */ - private static void commitPaxosLocal(Replica localReplica, final Message message, final AbstractWriteResponseHandler responseHandler) + private static void commitPaxosLocal(Replica localReplica, final Message message, final AbstractWriteResponseHandler responseHandler, Dispatcher.RequestTime requestTime) { - PAXOS_COMMIT_REQ.stage.maybeExecuteImmediately(new LocalMutationRunnable(localReplica) + PAXOS_COMMIT_REQ.stage.maybeExecuteImmediately(new LocalMutationRunnable(localReplica, requestTime) { public void runMayThrow() { @@ -867,16 +872,14 @@ public class StorageProxy implements StorageProxyMBean * * @param mutations the mutations to be applied across the replicas * @param consistencyLevel the consistency level for the operation - * @param queryStartNanoTime the value of nanoTime() when the query started to be processed + * @param requestTime object holding times when request got enqueued and started execution */ - public static void mutate(List mutations, ConsistencyLevel consistencyLevel, long queryStartNanoTime) + public static void mutate(List mutations, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime) throws UnavailableException, OverloadedException, WriteTimeoutException, WriteFailureException { Tracing.trace("Determining replicas for mutation"); final String localDataCenter = DatabaseDescriptor.getEndpointSnitch().getLocalDatacenter(); - long startTime = nanoTime(); - List> responseHandlers = new ArrayList<>(mutations.size()); WriteType plainWriteType = mutations.size() <= 1 ? WriteType.SIMPLE : WriteType.UNLOGGED_BATCH; @@ -885,9 +888,9 @@ public class StorageProxy implements StorageProxyMBean for (IMutation mutation : mutations) { if (mutation instanceof CounterMutation) - responseHandlers.add(mutateCounter((CounterMutation)mutation, localDataCenter, queryStartNanoTime)); + responseHandlers.add(mutateCounter((CounterMutation)mutation, localDataCenter, requestTime)); else - responseHandlers.add(performWrite(mutation, consistencyLevel, localDataCenter, standardWritePerformer, null, plainWriteType, queryStartNanoTime)); + responseHandlers.add(performWrite(mutation, consistencyLevel, localDataCenter, standardWritePerformer, null, plainWriteType, requestTime)); } // upgrade to full quorum any failed cheap quorums @@ -943,7 +946,9 @@ public class StorageProxy implements StorageProxyMBean } finally { - long latency = nanoTime() - startTime; + // We track latency based on request processing time, since the amount of time that request spends in the queue + // is not a representative metric of replica performance. + long latency = nanoTime() - requestTime.startedAtNanos(); writeMetrics.addNano(latency); writeMetricsForLevel(consistencyLevel).addNano(latency); updateCoordinatorWriteLatencyTableMetric(mutations, latency); @@ -999,9 +1004,9 @@ public class StorageProxy implements StorageProxyMBean * @param mutations the mutations to be applied across the replicas * @param writeCommitLog if commitlog should be written * @param baseComplete time from epoch in ms that the local base mutation was(or will be) completed - * @param queryStartNanoTime the value of nanoTime() when the query started to be processed + * @param requestTime object holding times when request got enqueued and started execution */ - public static void mutateMV(ByteBuffer dataKey, Collection mutations, boolean writeCommitLog, AtomicLong baseComplete, long queryStartNanoTime) + public static void mutateMV(ByteBuffer dataKey, Collection mutations, boolean writeCommitLog, AtomicLong baseComplete, Dispatcher.RequestTime requestTime) throws UnavailableException, OverloadedException, WriteTimeoutException { Tracing.trace("Determining replicas for mutation"); @@ -1032,7 +1037,7 @@ public class StorageProxy implements StorageProxyMBean //Since the base -> view replication is 1:1 we only need to store the BL locally ReplicaPlan.ForWrite replicaPlan = ReplicaPlans.forLocalBatchlogWrite(); BatchlogCleanup cleanup = new BatchlogCleanup(mutations.size(), - () -> asyncRemoveFromBatchlog(replicaPlan, batchUUID)); + () -> asyncRemoveFromBatchlog(replicaPlan, batchUUID, requestTime)); // add a handler for each mutation - includes checking availability, but doesn't initiate any writes, yet for (Mutation mutation : mutations) @@ -1087,7 +1092,7 @@ public class StorageProxy implements StorageProxyMBean baseComplete, WriteType.BATCH, cleanup, - queryStartNanoTime)); + requestTime)); } } @@ -1097,7 +1102,7 @@ public class StorageProxy implements StorageProxyMBean // Perform remote writes if (!wrappers.isEmpty()) - asyncWriteBatchedMutations(wrappers, localDataCenter, Stage.VIEW_MUTATION); + asyncWriteBatchedMutations(wrappers, localDataCenter, Stage.VIEW_MUTATION, requestTime); } } finally @@ -1110,7 +1115,7 @@ public class StorageProxy implements StorageProxyMBean public static void mutateWithTriggers(List mutations, ConsistencyLevel consistencyLevel, boolean mutateAtomically, - long queryStartNanoTime) + Dispatcher.RequestTime requestTime) throws WriteTimeoutException, WriteFailureException, UnavailableException, OverloadedException, InvalidRequestException { if (DatabaseDescriptor.getPartitionDenylistEnabled() && DatabaseDescriptor.getDenylistWritesEnabled()) @@ -1143,13 +1148,13 @@ public class StorageProxy implements StorageProxyMBean writeMetricsForLevel(consistencyLevel).mutationSize.update(size); if (augmented != null) - mutateAtomically(augmented, consistencyLevel, updatesView, queryStartNanoTime); + mutateAtomically(augmented, consistencyLevel, updatesView, requestTime); else { if (mutateAtomically || updatesView) - mutateAtomically((Collection) mutations, consistencyLevel, updatesView, queryStartNanoTime); + mutateAtomically((Collection) mutations, consistencyLevel, updatesView, requestTime); else - mutate(mutations, consistencyLevel, queryStartNanoTime); + mutate(mutations, consistencyLevel, requestTime); } } @@ -1162,12 +1167,12 @@ public class StorageProxy implements StorageProxyMBean * @param mutations the Mutations to be applied across the replicas * @param consistency_level the consistency level for the operation * @param requireQuorumForRemove at least a quorum of nodes will see update before deleting batchlog - * @param queryStartNanoTime the value of nanoTime() when the query started to be processed + * @param requestTime object holding times when request got enqueued and started execution */ public static void mutateAtomically(Collection mutations, ConsistencyLevel consistency_level, boolean requireQuorumForRemove, - long queryStartNanoTime) + Dispatcher.RequestTime requestTime) throws UnavailableException, OverloadedException, WriteTimeoutException { Tracing.trace("Determining replicas for atomic batch"); @@ -1198,7 +1203,7 @@ public class StorageProxy implements StorageProxyMBean final TimeUUID batchUUID = nextTimeUUID(); BatchlogCleanup cleanup = new BatchlogCleanup(mutations.size(), - () -> asyncRemoveFromBatchlog(replicaPlan, batchUUID)); + () -> asyncRemoveFromBatchlog(replicaPlan, batchUUID, requestTime)); // add a handler for each mutation - includes checking availability, but doesn't initiate any writes, yet for (Mutation mutation : mutations) @@ -1208,16 +1213,16 @@ public class StorageProxy implements StorageProxyMBean batchConsistencyLevel, WriteType.BATCH, cleanup, - queryStartNanoTime); + requestTime); // exit early if we can't fulfill the CL at this time. wrappers.add(wrapper); } // write to the batchlog - syncWriteToBatchlog(mutations, replicaPlan, batchUUID, queryStartNanoTime); + syncWriteToBatchlog(mutations, replicaPlan, batchUUID, requestTime); // now actually perform the writes and wait for them to complete - syncWriteBatchedMutations(wrappers, Stage.MUTATION); + syncWriteBatchedMutations(wrappers, Stage.MUTATION, requestTime); } catch (UnavailableException e) { @@ -1271,13 +1276,13 @@ public class StorageProxy implements StorageProxyMBean } } - private static void syncWriteToBatchlog(Collection mutations, ReplicaPlan.ForWrite replicaPlan, TimeUUID uuid, long queryStartNanoTime) + private static void syncWriteToBatchlog(Collection mutations, ReplicaPlan.ForWrite replicaPlan, TimeUUID uuid, Dispatcher.RequestTime requestTime) throws WriteTimeoutException, WriteFailureException { - WriteResponseHandler handler = new WriteResponseHandler(replicaPlan, - WriteType.BATCH_LOG, - null, - queryStartNanoTime); + WriteResponseHandler handler = new WriteResponseHandler<>(replicaPlan, + WriteType.BATCH_LOG, + null, + requestTime); Batch batch = Batch.createLocal(uuid, FBUtilities.timestampMicros(), mutations); Message message = Message.out(BATCH_STORE_REQ, batch); @@ -1286,14 +1291,14 @@ public class StorageProxy implements StorageProxyMBean logger.trace("Sending batchlog store request {} to {} for {} mutations", batch.id, replica, batch.size()); if (replica.isSelf()) - performLocally(Stage.MUTATION, replica, () -> BatchlogManager.store(batch), handler, "Batchlog store"); + performLocally(Stage.MUTATION, replica, () -> BatchlogManager.store(batch), handler, "Batchlog store", requestTime); else MessagingService.instance().sendWithCallback(message, replica.endpoint(), handler); } handler.get(); } - private static void asyncRemoveFromBatchlog(ReplicaPlan.ForWrite replicaPlan, TimeUUID uuid) + private static void asyncRemoveFromBatchlog(ReplicaPlan.ForWrite replicaPlan, TimeUUID uuid, Dispatcher.RequestTime requestTime) { Message message = Message.out(Verb.BATCH_REMOVE_REQ, uuid); for (Replica target : replicaPlan.contacts()) @@ -1302,13 +1307,13 @@ public class StorageProxy implements StorageProxyMBean logger.trace("Sending batchlog remove request {} to {}", uuid, target); if (target.isSelf()) - performLocally(Stage.MUTATION, target, () -> BatchlogManager.remove(uuid), "Batchlog remove"); + performLocally(Stage.MUTATION, target, () -> BatchlogManager.remove(uuid), "Batchlog remove", requestTime); else MessagingService.instance().send(message, target.endpoint()); } } - private static void asyncWriteBatchedMutations(List wrappers, String localDataCenter, Stage stage) + private static void asyncWriteBatchedMutations(List wrappers, String localDataCenter, Stage stage, Dispatcher.RequestTime requestTime) { for (WriteResponseHandlerWrapper wrapper : wrappers) { @@ -1317,7 +1322,7 @@ public class StorageProxy implements StorageProxyMBean try { - sendToHintedReplicas(wrapper.mutation, replicas, wrapper.handler, localDataCenter, stage); + sendToHintedReplicas(wrapper.mutation, replicas, wrapper.handler, localDataCenter, stage, requestTime); } catch (OverloadedException | WriteTimeoutException e) { @@ -1326,7 +1331,7 @@ public class StorageProxy implements StorageProxyMBean } } - private static void syncWriteBatchedMutations(List wrappers, Stage stage) + private static void syncWriteBatchedMutations(List wrappers, Stage stage, Dispatcher.RequestTime requestTime) throws WriteTimeoutException, OverloadedException { String localDataCenter = DatabaseDescriptor.getEndpointSnitch().getLocalDatacenter(); @@ -1335,7 +1340,7 @@ public class StorageProxy implements StorageProxyMBean { EndpointsForToken sendTo = wrapper.handler.replicaPlan.liveAndDown(); Replicas.temporaryAssertFull(sendTo); // TODO: CASSANDRA-14549 - sendToHintedReplicas(wrapper.mutation, wrapper.handler.replicaPlan.withContacts(sendTo), wrapper.handler, localDataCenter, stage); + sendToHintedReplicas(wrapper.mutation, wrapper.handler.replicaPlan.withContacts(sendTo), wrapper.handler, localDataCenter, stage, requestTime); } for (WriteResponseHandlerWrapper wrapper : wrappers) @@ -1354,7 +1359,7 @@ public class StorageProxy implements StorageProxyMBean * given the list of write endpoints (either standardWritePerformer for * standard writes or counterWritePerformer for counter writes). * @param callback an optional callback to be run if and when the write is - * @param queryStartNanoTime the value of nanoTime() when the query started to be processed + * @param requestTime object holding times when request got enqueued and started execution */ public static AbstractWriteResponseHandler performWrite(IMutation mutation, ConsistencyLevel consistencyLevel, @@ -1362,7 +1367,7 @@ public class StorageProxy implements StorageProxyMBean WritePerformer performer, Runnable callback, WriteType writeType, - long queryStartNanoTime) + Dispatcher.RequestTime requestTime) { String keyspaceName = mutation.getKeyspaceName(); Keyspace keyspace = Keyspace.open(keyspaceName); @@ -1376,9 +1381,9 @@ public class StorageProxy implements StorageProxyMBean writeMetrics.remoteRequests.mark(); AbstractReplicationStrategy rs = replicaPlan.replicationStrategy(); - AbstractWriteResponseHandler responseHandler = rs.getWriteResponseHandler(replicaPlan, callback, writeType, mutation.hintOnFailure(), queryStartNanoTime); + AbstractWriteResponseHandler responseHandler = rs.getWriteResponseHandler(replicaPlan, callback, writeType, mutation.hintOnFailure(), requestTime); - performer.apply(mutation, replicaPlan, responseHandler, localDataCenter); + performer.apply(mutation, replicaPlan, responseHandler, localDataCenter, requestTime); return responseHandler; } @@ -1388,7 +1393,7 @@ public class StorageProxy implements StorageProxyMBean ConsistencyLevel batchConsistencyLevel, WriteType writeType, BatchlogResponseHandler.BatchlogCleanup cleanup, - long queryStartNanoTime) + Dispatcher.RequestTime requestTime) { Keyspace keyspace = Keyspace.open(mutation.getKeyspaceName()); Token tk = mutation.key().getToken(); @@ -1401,8 +1406,8 @@ public class StorageProxy implements StorageProxyMBean writeMetrics.remoteRequests.mark(); AbstractReplicationStrategy rs = replicaPlan.replicationStrategy(); - AbstractWriteResponseHandler writeHandler = rs.getWriteResponseHandler(replicaPlan, null, writeType, mutation, queryStartNanoTime); - BatchlogResponseHandler batchHandler = new BatchlogResponseHandler<>(writeHandler, batchConsistencyLevel.blockFor(rs), cleanup, queryStartNanoTime); + AbstractWriteResponseHandler writeHandler = rs.getWriteResponseHandler(replicaPlan, null, writeType, mutation, requestTime); + BatchlogResponseHandler batchHandler = new BatchlogResponseHandler<>(writeHandler, batchConsistencyLevel.blockFor(rs), cleanup, requestTime); return new WriteResponseHandlerWrapper(batchHandler, mutation); } @@ -1417,7 +1422,7 @@ public class StorageProxy implements StorageProxyMBean AtomicLong baseComplete, WriteType writeType, BatchlogResponseHandler.BatchlogCleanup cleanup, - long queryStartNanoTime) + Dispatcher.RequestTime requestTime) { Keyspace keyspace = Keyspace.open(mutation.getKeyspaceName()); ReplicaPlan.ForWrite replicaPlan = ReplicaPlans.forWrite(keyspace, consistencyLevel, liveAndDown, ReplicaPlans.writeAll); @@ -1425,8 +1430,8 @@ public class StorageProxy implements StorageProxyMBean AbstractWriteResponseHandler writeHandler = replicationStrategy.getWriteResponseHandler(replicaPlan, () -> { long delay = Math.max(0, currentTimeMillis() - baseComplete.get()); viewWriteMetrics.viewWriteLatency.update(delay, MILLISECONDS); - }, writeType, mutation, queryStartNanoTime); - BatchlogResponseHandler batchHandler = new ViewWriteMetricsWrapped(writeHandler, batchConsistencyLevel.blockFor(replicationStrategy), cleanup, queryStartNanoTime); + }, writeType, mutation, requestTime); + BatchlogResponseHandler batchHandler = new ViewWriteMetricsWrapped(writeHandler, batchConsistencyLevel.blockFor(replicationStrategy), cleanup, requestTime); return new WriteResponseHandlerWrapper(batchHandler, mutation); } @@ -1464,7 +1469,8 @@ public class StorageProxy implements StorageProxyMBean ReplicaPlan.ForWrite plan, AbstractWriteResponseHandler responseHandler, String localDataCenter, - Stage stage) + Stage stage, + Dispatcher.RequestTime requestTime) throws OverloadedException { // this dc replicas: @@ -1502,7 +1508,12 @@ public class StorageProxy implements StorageProxyMBean { // belongs on a different server if (message == null) - message = Message.outWithFlag(MUTATION_REQ, mutation, MessageFlag.CALL_BACK_ON_FAILURE); + { + message = Message.outWithFlags(MUTATION_REQ, + mutation, + requestTime, + Collections.singletonList(MessageFlag.CALL_BACK_ON_FAILURE)); + } String dc = DatabaseDescriptor.getEndpointSnitch().getDatacenter(destination); @@ -1542,13 +1553,13 @@ public class StorageProxy implements StorageProxyMBean } } - if (endpointsToHint != null) + if (endpointsToHint != null && requestTime.shouldSendHints()) submitHint(mutation, EndpointsForToken.copyOf(mutation.key().getToken(), endpointsToHint), responseHandler); if (insertLocal) { Preconditions.checkNotNull(localReplica); - performLocally(stage, localReplica, mutation::apply, responseHandler, mutation); + performLocally(stage, localReplica, mutation::apply, responseHandler, mutation, requestTime); } if (localDc != null) @@ -1623,9 +1634,9 @@ public class StorageProxy implements StorageProxyMBean return select.get(ThreadLocalRandom.current().nextInt(0, select.size())); } - private static void performLocally(Stage stage, Replica localReplica, final Runnable runnable, String description) + private static void performLocally(Stage stage, Replica localReplica, final Runnable runnable, String description, Dispatcher.RequestTime requestTime) { - stage.maybeExecuteImmediately(new LocalMutationRunnable(localReplica) + stage.maybeExecuteImmediately(new LocalMutationRunnable(localReplica, requestTime) { public void runMayThrow() { @@ -1653,9 +1664,9 @@ public class StorageProxy implements StorageProxyMBean }); } - private static void performLocally(Stage stage, Replica localReplica, final Runnable runnable, final RequestCallback handler, Object description) + private static void performLocally(Stage stage, Replica localReplica, final Runnable runnable, final RequestCallback handler, Object description, Dispatcher.RequestTime requestTime) { - stage.maybeExecuteImmediately(new LocalMutationRunnable(localReplica) + stage.maybeExecuteImmediately(new LocalMutationRunnable(localReplica, requestTime) { public void runMayThrow() { @@ -1702,13 +1713,13 @@ public class StorageProxy implements StorageProxyMBean * quicker response and because the WriteResponseHandlers don't make it easy to send back an error. We also always gather * the write latencies at the coordinator node to make gathering point similar to the case of standard writes. */ - public static AbstractWriteResponseHandler mutateCounter(CounterMutation cm, String localDataCenter, long queryStartNanoTime) throws UnavailableException, OverloadedException + public static AbstractWriteResponseHandler mutateCounter(CounterMutation cm, String localDataCenter, Dispatcher.RequestTime requestTime) throws UnavailableException, OverloadedException { Replica replica = findSuitableReplica(cm.getKeyspaceName(), cm.key(), localDataCenter, cm.consistency()); if (replica.isSelf()) { - return applyCounterMutationOnCoordinator(cm, localDataCenter, queryStartNanoTime); + return applyCounterMutationOnCoordinator(cm, localDataCenter, requestTime); } else { @@ -1727,7 +1738,7 @@ public class StorageProxy implements StorageProxyMBean // Forward the actual update to the chosen leader replica AbstractWriteResponseHandler responseHandler = new WriteResponseHandler<>(ReplicaPlans.forForwardingCounterWrite(keyspace, tk, replica), - WriteType.COUNTER, null, queryStartNanoTime); + WriteType.COUNTER, null, requestTime); Tracing.trace("Enqueuing counter update to {}", replica); Message message = Message.outWithFlag(Verb.COUNTER_MUTATION_REQ, cm, MessageFlag.CALL_BACK_ON_FAILURE); @@ -1785,26 +1796,27 @@ public class StorageProxy implements StorageProxyMBean // Must be called on a replica of the mutation. This replica becomes the // leader of this mutation. - public static AbstractWriteResponseHandler applyCounterMutationOnLeader(CounterMutation cm, String localDataCenter, Runnable callback, long queryStartNanoTime) + public static AbstractWriteResponseHandler applyCounterMutationOnLeader(CounterMutation cm, String localDataCenter, Runnable callback, Dispatcher.RequestTime requestTime) throws UnavailableException, OverloadedException { - return performWrite(cm, cm.consistency(), localDataCenter, counterWritePerformer, callback, WriteType.COUNTER, queryStartNanoTime); + return performWrite(cm, cm.consistency(), localDataCenter, counterWritePerformer, callback, WriteType.COUNTER, requestTime); } // Same as applyCounterMutationOnLeader but must with the difference that it use the MUTATION stage to execute the write (while // applyCounterMutationOnLeader assumes it is on the MUTATION stage already) - public static AbstractWriteResponseHandler applyCounterMutationOnCoordinator(CounterMutation cm, String localDataCenter, long queryStartNanoTime) + public static AbstractWriteResponseHandler applyCounterMutationOnCoordinator(CounterMutation cm, String localDataCenter, Dispatcher.RequestTime requestTime) throws UnavailableException, OverloadedException { - return performWrite(cm, cm.consistency(), localDataCenter, counterWriteOnCoordinatorPerformer, null, WriteType.COUNTER, queryStartNanoTime); + return performWrite(cm, cm.consistency(), localDataCenter, counterWriteOnCoordinatorPerformer, null, WriteType.COUNTER, requestTime); } private static Runnable counterWriteTask(final IMutation mutation, final ReplicaPlan.ForWrite replicaPlan, final AbstractWriteResponseHandler responseHandler, - final String localDataCenter) + final String localDataCenter, + final Dispatcher.RequestTime requestTime) { - return new DroppableRunnable(Verb.COUNTER_MUTATION_REQ) + return new DroppableRunnable(Verb.COUNTER_MUTATION_REQ, requestTime) { @Override public void runMayThrow() throws OverloadedException, WriteTimeoutException @@ -1813,7 +1825,7 @@ public class StorageProxy implements StorageProxyMBean Mutation result = ((CounterMutation) mutation).applyCounterMutation(); responseHandler.onResponse(null); - sendToHintedReplicas(result, replicaPlan, responseHandler, localDataCenter, Stage.COUNTER_MUTATION); + sendToHintedReplicas(result, replicaPlan, responseHandler, localDataCenter, Stage.COUNTER_MUTATION, requestTime); } }; } @@ -1826,17 +1838,17 @@ public class StorageProxy implements StorageProxyMBean return true; } - public static RowIterator readOne(SinglePartitionReadCommand command, ConsistencyLevel consistencyLevel, long queryStartNanoTime) + public static RowIterator readOne(SinglePartitionReadCommand command, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime) throws UnavailableException, IsBootstrappingException, ReadFailureException, ReadTimeoutException, InvalidRequestException { - return PartitionIterators.getOnlyElement(read(SinglePartitionReadCommand.Group.one(command), consistencyLevel, queryStartNanoTime), command); + return PartitionIterators.getOnlyElement(read(SinglePartitionReadCommand.Group.one(command), consistencyLevel, requestTime), command); } /** * Performs the actual reading of a row out of the StorageService, fetching * a specific set of column names from a given column family. */ - public static PartitionIterator read(SinglePartitionReadCommand.Group group, ConsistencyLevel consistencyLevel, long queryStartNanoTime) + public static PartitionIterator read(SinglePartitionReadCommand.Group group, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime) throws UnavailableException, IsBootstrappingException, ReadFailureException, ReadTimeoutException, InvalidRequestException { if (!isSafeToPerformRead(group.queries)) @@ -1862,8 +1874,8 @@ public class StorageProxy implements StorageProxyMBean } return consistencyLevel.isSerialConsistency() - ? readWithPaxos(group, consistencyLevel, queryStartNanoTime) - : readRegular(group, consistencyLevel, queryStartNanoTime); + ? readWithPaxos(group, consistencyLevel, requestTime) + : readRegular(group, consistencyLevel, requestTime); } public static boolean isSafeToPerformRead(List queries) @@ -1876,21 +1888,20 @@ public class StorageProxy implements StorageProxyMBean return !StorageService.instance.isBootstrapMode(); } - private static PartitionIterator readWithPaxos(SinglePartitionReadCommand.Group group, ConsistencyLevel consistencyLevel, long queryStartNanoTime) + private static PartitionIterator readWithPaxos(SinglePartitionReadCommand.Group group, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime) throws InvalidRequestException, UnavailableException, ReadFailureException, ReadTimeoutException { return Paxos.useV2() - ? Paxos.read(group, consistencyLevel) - : legacyReadWithPaxos(group, consistencyLevel, queryStartNanoTime); + ? Paxos.read(group, consistencyLevel, requestTime) + : legacyReadWithPaxos(group, consistencyLevel, requestTime); } - private static PartitionIterator legacyReadWithPaxos(SinglePartitionReadCommand.Group group, ConsistencyLevel consistencyLevel, long queryStartNanoTime) + private static PartitionIterator legacyReadWithPaxos(SinglePartitionReadCommand.Group group, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime) throws InvalidRequestException, UnavailableException, ReadFailureException, ReadTimeoutException { if (group.queries.size() > 1) throw new InvalidRequestException("SERIAL/LOCAL_SERIAL consistency may only be requested for one partition at a time"); - long start = nanoTime(); SinglePartitionReadCommand command = group.queries.get(0); TableMetadata metadata = command.metadata(); DecoratedKey key = command.partitionKey(); @@ -1921,7 +1932,7 @@ public class StorageProxy implements StorageProxyMBean consistencyLevel, consistencyForReplayCommitsOrFetch, ConsistencyLevel.ANY, - start, + requestTime, casReadMetrics, updateProposer); } @@ -1934,7 +1945,7 @@ public class StorageProxy implements StorageProxyMBean throw new ReadFailureException(consistencyLevel, e.received, e.blockFor, false, e.failureReasonByEndpoint); } - result = fetchRows(group.queries, consistencyForReplayCommitsOrFetch, queryStartNanoTime); + result = fetchRows(group.queries, consistencyForReplayCommitsOrFetch, requestTime); } catch (UnavailableException e) { @@ -1968,7 +1979,9 @@ public class StorageProxy implements StorageProxyMBean } finally { - long latency = nanoTime() - start; + // We track latency based on request processing time, since the amount of time that request spends in the queue + // is not a representative metric of replica performance. + long latency = nanoTime() - requestTime.startedAtNanos(); readMetrics.addNano(latency); casReadMetrics.addNano(latency); readMetricsForLevel(consistencyLevel).addNano(latency); @@ -1978,13 +1991,13 @@ public class StorageProxy implements StorageProxyMBean return result; } - private static PartitionIterator readRegular(SinglePartitionReadCommand.Group group, ConsistencyLevel consistencyLevel, long queryStartNanoTime) + @SuppressWarnings("resource") + private static PartitionIterator readRegular(SinglePartitionReadCommand.Group group, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime) throws UnavailableException, ReadFailureException, ReadTimeoutException { - long start = nanoTime(); try { - PartitionIterator result = fetchRows(group.queries, consistencyLevel, queryStartNanoTime); + PartitionIterator result = fetchRows(group.queries, consistencyLevel, requestTime); // Note that the only difference between the command in a group must be the partition key on which // they applied. boolean enforceStrictLiveness = group.queries.get(0).metadata().enforceStrictLiveness(); @@ -2021,7 +2034,9 @@ public class StorageProxy implements StorageProxyMBean } finally { - long latency = nanoTime() - start; + // We track latency based on request processing time, since the amount of time that request spends in the queue + // is not a representative metric of replica performance. + long latency = nanoTime() - requestTime.startedAtNanos(); readMetrics.addNano(latency); readMetricsForLevel(consistencyLevel).addNano(latency); // TODO avoid giving every command the same latency number. Can fix this in CASSADRA-5329 @@ -2075,7 +2090,9 @@ public class StorageProxy implements StorageProxyMBean * 4. If the digests (if any) match the data return the data * 5. else carry out read repair by getting data from all the nodes. */ - private static PartitionIterator fetchRows(List commands, ConsistencyLevel consistencyLevel, long queryStartNanoTime) + private static PartitionIterator fetchRows(List commands, + ConsistencyLevel consistencyLevel, + Dispatcher.RequestTime requestTime) throws UnavailableException, ReadFailureException, ReadTimeoutException { int cmdCount = commands.size(); @@ -2086,7 +2103,9 @@ public class StorageProxy implements StorageProxyMBean // for type of speculation we'll use in this read for (int i=0; i> getSchemaVersions() @@ -2523,7 +2545,8 @@ public class StorageProxy implements StorageProxyMBean public void apply(IMutation mutation, ReplicaPlan.ForWrite targets, AbstractWriteResponseHandler responseHandler, - String localDataCenter) throws OverloadedException; + String localDataCenter, + Dispatcher.RequestTime requestTime) throws OverloadedException; } /** @@ -2531,9 +2554,9 @@ public class StorageProxy implements StorageProxyMBean */ private static class ViewWriteMetricsWrapped extends BatchlogResponseHandler { - public ViewWriteMetricsWrapped(AbstractWriteResponseHandler writeHandler, int i, BatchlogCleanup cleanup, long queryStartNanoTime) + public ViewWriteMetricsWrapped(AbstractWriteResponseHandler writeHandler, int i, BatchlogCleanup cleanup, Dispatcher.RequestTime requestTime) { - super(writeHandler, i, cleanup, queryStartNanoTime); + super(writeHandler, i, cleanup, requestTime); viewWriteMetrics.viewReplicasAttempted.inc(candidateReplicaCount()); } @@ -2549,25 +2572,22 @@ public class StorageProxy implements StorageProxyMBean */ private static abstract class DroppableRunnable implements Runnable { - protected final long approxCreationTimeNanos; - protected volatile long approxStartTimeNanos; - final Verb verb; - - public DroppableRunnable(Verb verb) + final Dispatcher.RequestTime requestTime; + public DroppableRunnable(Verb verb, Dispatcher.RequestTime requestTime) { - this.approxCreationTimeNanos = MonotonicClock.Global.approxTime.now(); this.verb = verb; + this.requestTime = requestTime; } public final void run() { - approxStartTimeNanos = MonotonicClock.Global.approxTime.now(); - long expirationTimeNanos = verb.expiresAtNanos(approxCreationTimeNanos); - if (approxStartTimeNanos > expirationTimeNanos) + long nowNanos = MonotonicClock.Global.preciseTime.now(); + long deadline = requestTime.computeDeadline(verb.expiresAfterNanos()); + if (nowNanos > deadline) { - long timeTakenNanos = approxStartTimeNanos - approxCreationTimeNanos; - MessagingService.instance().metrics.recordSelfDroppedMessage(verb, timeTakenNanos, NANOSECONDS); + long elapsed = nowNanos - requestTime.startedAtNanos(); + MessagingService.instance().metrics.recordSelfDroppedMessage(verb, elapsed, NANOSECONDS); return; } try @@ -2589,35 +2609,37 @@ public class StorageProxy implements StorageProxyMBean */ private static abstract class LocalMutationRunnable implements RunnableDebuggableTask { - private final long approxCreationTimeNanos = MonotonicClock.Global.approxTime.now(); - private volatile long approxStartTimeNanos; - private final Replica localReplica; + private final Dispatcher.RequestTime requestTime; - LocalMutationRunnable(Replica localReplica) + LocalMutationRunnable(Replica localReplica, Dispatcher.RequestTime requestTime) { this.localReplica = localReplica; + this.requestTime = requestTime; } public final void run() { final Verb verb = verb(); - approxStartTimeNanos = MonotonicClock.Global.approxTime.now(); - long expirationTimeNanos = verb.expiresAtNanos(approxCreationTimeNanos); - - if (approxStartTimeNanos > expirationTimeNanos) + long now = MonotonicClock.Global.approxTime.now(); + long deadline = requestTime.computeDeadline(verb.expiresAfterNanos()); + + if (now > deadline) { - long timeTakenNanos = approxStartTimeNanos - approxCreationTimeNanos; + long timeTakenNanos = now - startTimeNanos(); MessagingService.instance().metrics.recordSelfDroppedMessage(Verb.MUTATION_REQ, timeTakenNanos, NANOSECONDS); - HintRunnable runnable = new HintRunnable(EndpointsForToken.of(localReplica.range().right, localReplica)) + if (requestTime.shouldSendHints()) { - protected void runMayThrow() throws Exception + HintRunnable runnable = new HintRunnable(EndpointsForToken.of(localReplica.range().right, localReplica)) { - LocalMutationRunnable.this.runMayThrow(); - } - }; - submitHint(runnable); + protected void runMayThrow() throws Exception + { + LocalMutationRunnable.this.runMayThrow(); + } + }; + submitHint(runnable); + } return; } @@ -2634,13 +2656,13 @@ public class StorageProxy implements StorageProxyMBean @Override public long creationTimeNanos() { - return approxCreationTimeNanos; + return requestTime.enqueuedAtNanos(); } @Override public long startTimeNanos() { - return approxStartTimeNanos; + return requestTime.startedAtNanos(); } @Override @@ -2738,14 +2760,14 @@ public class StorageProxy implements StorageProxyMBean } } - public static Future submitHint(Mutation mutation, Replica target, AbstractWriteResponseHandler responseHandler) + public static void submitHint(Mutation mutation, Replica target, AbstractWriteResponseHandler responseHandler) { - return submitHint(mutation, EndpointsForToken.of(target.range().right, target), responseHandler); + submitHint(mutation, EndpointsForToken.of(target.range().right, target), responseHandler); } - public static Future submitHint(Mutation mutation, - EndpointsForToken targets, - AbstractWriteResponseHandler responseHandler) + private static void submitHint(Mutation mutation, + EndpointsForToken targets, + AbstractWriteResponseHandler responseHandler) { Replicas.assertFull(targets); // hints should not be written for transient replicas HintRunnable runnable = new HintRunnable(targets) @@ -2774,7 +2796,7 @@ public class StorageProxy implements StorageProxyMBean } }; - return submitHint(runnable); + submitHint(runnable); } private static Future submitHint(HintRunnable runnable) diff --git a/src/java/org/apache/cassandra/service/StorageService.java b/src/java/org/apache/cassandra/service/StorageService.java index cbdadee1c3..0ebc4d9258 100644 --- a/src/java/org/apache/cassandra/service/StorageService.java +++ b/src/java/org/apache/cassandra/service/StorageService.java @@ -595,13 +595,13 @@ public class StorageService extends NotificationBroadcasterSupport implements IE } } - public void stopNativeTransport() + public void stopNativeTransport(boolean force) { if (daemon == null) { throw new IllegalStateException("No configured daemon"); } - daemon.stopNativeTransport(); + daemon.stopNativeTransport(force); } public boolean isNativeTransportRunning() @@ -7452,4 +7452,71 @@ public class StorageService extends NotificationBroadcasterSupport implements IE if (!skipNotificationListeners) super.addNotificationListener(listener, filter, handback); } + + @Override + public double getNativeTransportQueueMaxItemAgeThreshold() + { + return DatabaseDescriptor.getNativeTransportQueueMaxItemAgeThreshold(); + } + + @Override + public void setNativeTransportQueueMaxItemAgeThreshold(double threshold) + { + DatabaseDescriptor.setNativeTransportMaxQueueItemAgeThreshold(threshold); + } + + @Override + public long getNativeTransportMinBackoffOnQueueOverloadInMillis() + { + return DatabaseDescriptor.getNativeTransportMinBackoffOnQueueOverload(MILLISECONDS); + } + + @Override + public long getNativeTransportMaxBackoffOnQueueOverloadInMillis() + { + return DatabaseDescriptor.getNativeTransportMaxBackoffOnQueueOverload(MILLISECONDS); + } + + @Override + public void setNativeTransportBackoffOnQueueOverloadInMillis(long min, long max) + { + DatabaseDescriptor.setNativeTransportBackoffOnQueueOverload(min, max, MILLISECONDS); + } + + @Override + public boolean getNativeTransportThrowOnOverload() + { + return DatabaseDescriptor.getNativeTransportThrowOnOverload(); + } + + @Override + public void setNativeTransportThrowOnOverload(boolean throwOnOverload) + { + DatabaseDescriptor.setNativeTransportThrowOnOverload(throwOnOverload); + } + + @Override + public long getNativeTransportTimeoutMillis() + { + return DatabaseDescriptor.getNativeTransportTimeout(MILLISECONDS); + } + + @Override + public void setNativeTransportTimeoutMillis(long deadlineMillis) + { + DatabaseDescriptor.setNativeTransportTimeout(deadlineMillis, MILLISECONDS); + } + + @Override + public boolean getEnforceNativeDeadlineForHints() + { + return DatabaseDescriptor.getEnforceNativeDeadlineForHints(); + } + + @Override + public void setEnforceNativeDeadlineForHints(boolean value) + { + DatabaseDescriptor.setEnforceNativeDeadlineForHints(value); + } + } diff --git a/src/java/org/apache/cassandra/service/StorageServiceMBean.java b/src/java/org/apache/cassandra/service/StorageServiceMBean.java index c36c8bd783..d6ed719812 100644 --- a/src/java/org/apache/cassandra/service/StorageServiceMBean.java +++ b/src/java/org/apache/cassandra/service/StorageServiceMBean.java @@ -665,7 +665,8 @@ public interface StorageServiceMBean extends NotificationEmitter // to determine if initialization has completed public boolean isInitialized(); - public void stopNativeTransport(); + @Deprecated(since = "5.0") public default void stopNativeTransport() { stopNativeTransport(false); } + public void stopNativeTransport(boolean force); public void startNativeTransport(); public boolean isNativeTransportRunning(); public void enableNativeTransportOldProtocolVersions(); @@ -1262,4 +1263,20 @@ public interface StorageServiceMBean extends NotificationEmitter public void setSkipStreamDiskSpaceCheck(boolean value); public boolean getSkipStreamDiskSpaceCheck(); -} \ No newline at end of file + + double getNativeTransportQueueMaxItemAgeThreshold(); + void setNativeTransportQueueMaxItemAgeThreshold(double threshold); + + long getNativeTransportMinBackoffOnQueueOverloadInMillis(); + long getNativeTransportMaxBackoffOnQueueOverloadInMillis(); + void setNativeTransportBackoffOnQueueOverloadInMillis(long min, long max); + + boolean getNativeTransportThrowOnOverload(); + void setNativeTransportThrowOnOverload(boolean throwOnOverload); + + long getNativeTransportTimeoutMillis(); + void setNativeTransportTimeoutMillis(long deadlineMillis); + + boolean getEnforceNativeDeadlineForHints(); + void setEnforceNativeDeadlineForHints(boolean value); +} diff --git a/src/java/org/apache/cassandra/service/WriteResponseHandler.java b/src/java/org/apache/cassandra/service/WriteResponseHandler.java index 6fe9e527cd..ec18238f99 100644 --- a/src/java/org/apache/cassandra/service/WriteResponseHandler.java +++ b/src/java/org/apache/cassandra/service/WriteResponseHandler.java @@ -27,6 +27,7 @@ import org.slf4j.LoggerFactory; import org.apache.cassandra.net.Message; import org.apache.cassandra.db.WriteType; +import org.apache.cassandra.transport.Dispatcher; /** * Handles blocking writes for ONE, ANY, TWO, THREE, QUORUM, and ALL consistency levels. @@ -43,15 +44,15 @@ public class WriteResponseHandler extends AbstractWriteResponseHandler Runnable callback, WriteType writeType, Supplier hintOnFailure, - long queryStartNanoTime) + Dispatcher.RequestTime requestTime) { - super(replicaPlan, callback, writeType, hintOnFailure, queryStartNanoTime); + super(replicaPlan, callback, writeType, hintOnFailure, requestTime); responses = blockFor(); } - public WriteResponseHandler(ReplicaPlan.ForWrite replicaPlan, WriteType writeType, Supplier hintOnFailure, long queryStartNanoTime) + public WriteResponseHandler(ReplicaPlan.ForWrite replicaPlan, WriteType writeType, Supplier hintOnFailure, Dispatcher.RequestTime requestTime) { - this(replicaPlan, null, writeType, hintOnFailure, queryStartNanoTime); + this(replicaPlan, null, writeType, hintOnFailure, requestTime); } public void onResponse(Message m) diff --git a/src/java/org/apache/cassandra/service/pager/AbstractQueryPager.java b/src/java/org/apache/cassandra/service/pager/AbstractQueryPager.java index 1dbc75b3cf..292264984f 100644 --- a/src/java/org/apache/cassandra/service/pager/AbstractQueryPager.java +++ b/src/java/org/apache/cassandra/service/pager/AbstractQueryPager.java @@ -24,6 +24,7 @@ import org.apache.cassandra.db.filter.DataLimits; import org.apache.cassandra.db.transform.Transformation; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.service.ClientState; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.transport.ProtocolVersion; abstract class AbstractQueryPager implements QueryPager @@ -59,7 +60,7 @@ abstract class AbstractQueryPager implements QueryPager return query.executionController(); } - public PartitionIterator fetchPage(int pageSize, ConsistencyLevel consistency, ClientState clientState, long queryStartNanoTime) + public PartitionIterator fetchPage(int pageSize, ConsistencyLevel consistency, ClientState clientState, Dispatcher.RequestTime requestTime) { if (isExhausted()) return EmptyIterators.partition(); @@ -72,7 +73,7 @@ abstract class AbstractQueryPager implements QueryPager exhausted = true; return EmptyIterators.partition(); } - return Transformation.apply(readQuery.execute(consistency, clientState, queryStartNanoTime), pager); + return Transformation.apply(readQuery.execute(consistency, clientState, requestTime), pager); } public PartitionIterator fetchPageInternal(int pageSize, ReadExecutionController executionController) diff --git a/src/java/org/apache/cassandra/service/pager/AggregationQueryPager.java b/src/java/org/apache/cassandra/service/pager/AggregationQueryPager.java index f867289986..95d910de60 100644 --- a/src/java/org/apache/cassandra/service/pager/AggregationQueryPager.java +++ b/src/java/org/apache/cassandra/service/pager/AggregationQueryPager.java @@ -28,8 +28,7 @@ import org.apache.cassandra.db.partitions.PartitionIterator; import org.apache.cassandra.db.rows.Row; import org.apache.cassandra.db.rows.RowIterator; import org.apache.cassandra.service.ClientState; - -import static org.apache.cassandra.utils.Clock.Global.nanoTime; +import org.apache.cassandra.transport.Dispatcher; /** * {@code QueryPager} that takes care of fetching the pages for aggregation queries. @@ -55,12 +54,12 @@ public final class AggregationQueryPager implements QueryPager public PartitionIterator fetchPage(int pageSize, ConsistencyLevel consistency, ClientState clientState, - long queryStartNanoTime) + Dispatcher.RequestTime requestTime) { if (limits.isGroupByLimit()) - return new GroupByPartitionIterator(pageSize, consistency, clientState, queryStartNanoTime); + return new GroupByPartitionIterator(pageSize, consistency, clientState, requestTime); - return new AggregationPartitionIterator(pageSize, consistency, clientState, queryStartNanoTime); + return new AggregationPartitionIterator(pageSize, consistency, clientState, requestTime); } @Override @@ -73,9 +72,9 @@ public final class AggregationQueryPager implements QueryPager public PartitionIterator fetchPageInternal(int pageSize, ReadExecutionController executionController) { if (limits.isGroupByLimit()) - return new GroupByPartitionIterator(pageSize, executionController, nanoTime()); + return new GroupByPartitionIterator(pageSize, executionController, Dispatcher.RequestTime.forImmediateExecution()); - return new AggregationPartitionIterator(pageSize, executionController, nanoTime()); + return new AggregationPartitionIterator(pageSize, executionController, Dispatcher.RequestTime.forImmediateExecution()); } @Override @@ -161,34 +160,34 @@ public final class AggregationQueryPager implements QueryPager */ private int initialMaxRemaining; - private long queryStartNanoTime; + private Dispatcher.RequestTime requestTime; public GroupByPartitionIterator(int pageSize, - ConsistencyLevel consistency, - ClientState clientState, - long queryStartNanoTime) + ConsistencyLevel consistency, + ClientState clientState, + Dispatcher.RequestTime requestTime) { - this(pageSize, consistency, clientState, null, queryStartNanoTime); + this(pageSize, consistency, clientState, null, requestTime); } public GroupByPartitionIterator(int pageSize, ReadExecutionController executionController, - long queryStartNanoTime) + Dispatcher.RequestTime requestTime) { - this(pageSize, null, null, executionController, queryStartNanoTime); + this(pageSize, null, null, executionController, requestTime); } private GroupByPartitionIterator(int pageSize, ConsistencyLevel consistency, ClientState clientState, ReadExecutionController executionController, - long queryStartNanoTime) + Dispatcher.RequestTime requestTime) { this.pageSize = handlePagingOff(pageSize); this.consistency = consistency; this.clientState = clientState; this.executionController = executionController; - this.queryStartNanoTime = queryStartNanoTime; + this.requestTime = requestTime; } private int handlePagingOff(int pageSize) @@ -295,7 +294,7 @@ public final class AggregationQueryPager implements QueryPager */ private final PartitionIterator fetchSubPage(int subPageSize) { - return consistency != null ? subPager.fetchPage(subPageSize, consistency, clientState, queryStartNanoTime) + return consistency != null ? subPager.fetchPage(subPageSize, consistency, clientState, requestTime) : subPager.fetchPageInternal(subPageSize, executionController); } @@ -409,16 +408,16 @@ public final class AggregationQueryPager implements QueryPager public AggregationPartitionIterator(int pageSize, ConsistencyLevel consistency, ClientState clientState, - long queryStartNanoTime) + Dispatcher.RequestTime requestTime) { - super(pageSize, consistency, clientState, queryStartNanoTime); + super(pageSize, consistency, clientState, requestTime); } public AggregationPartitionIterator(int pageSize, ReadExecutionController executionController, - long queryStartNanoTime) + Dispatcher.RequestTime requestTime) { - super(pageSize, executionController, queryStartNanoTime); + super(pageSize, executionController, requestTime); } @Override diff --git a/src/java/org/apache/cassandra/service/pager/MultiPartitionPager.java b/src/java/org/apache/cassandra/service/pager/MultiPartitionPager.java index dc6fcba0c7..842eb35e81 100644 --- a/src/java/org/apache/cassandra/service/pager/MultiPartitionPager.java +++ b/src/java/org/apache/cassandra/service/pager/MultiPartitionPager.java @@ -17,6 +17,7 @@ */ package org.apache.cassandra.service.pager; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.utils.AbstractIterator; @@ -30,8 +31,6 @@ import org.apache.cassandra.exceptions.RequestValidationException; import org.apache.cassandra.exceptions.RequestExecutionException; import org.apache.cassandra.service.ClientState; -import static org.apache.cassandra.utils.Clock.Global.nanoTime; - /** * Pager over a list of SinglePartitionReadQuery. * @@ -149,16 +148,18 @@ public class MultiPartitionPager implements throw new AssertionError("Shouldn't be called on an exhausted pager"); } - public PartitionIterator fetchPage(int pageSize, ConsistencyLevel consistency, ClientState clientState, long queryStartNanoTime) throws RequestValidationException, RequestExecutionException + @SuppressWarnings("resource") // iter closed via countingIter + @Override + public PartitionIterator fetchPage(int pageSize, ConsistencyLevel consistency, ClientState clientState, Dispatcher.RequestTime requestTime) throws RequestValidationException, RequestExecutionException { int toQuery = Math.min(remaining, pageSize); - return new PagersIterator(toQuery, consistency, clientState, null, queryStartNanoTime); + return new PagersIterator(toQuery, consistency, clientState, null, requestTime); } public PartitionIterator fetchPageInternal(int pageSize, ReadExecutionController executionController) throws RequestValidationException, RequestExecutionException { int toQuery = Math.min(remaining, pageSize); - return new PagersIterator(toQuery, null, null, executionController, nanoTime()); + return new PagersIterator(toQuery, null, null, executionController, Dispatcher.RequestTime.forImmediateExecution()); } private class PagersIterator extends AbstractIterator implements PartitionIterator @@ -166,7 +167,7 @@ public class MultiPartitionPager implements private final int pageSize; private PartitionIterator result; private boolean closed; - private final long queryStartNanoTime; + private final Dispatcher.RequestTime requestTime; // For "normal" queries private final ConsistencyLevel consistency; @@ -178,13 +179,13 @@ public class MultiPartitionPager implements private int pagerMaxRemaining; private int counted; - public PagersIterator(int pageSize, ConsistencyLevel consistency, ClientState clientState, ReadExecutionController executionController, long queryStartNanoTime) + public PagersIterator(int pageSize, ConsistencyLevel consistency, ClientState clientState, ReadExecutionController executionController, Dispatcher.RequestTime requestTime) { this.pageSize = pageSize; this.consistency = consistency; this.clientState = clientState; this.executionController = executionController; - this.queryStartNanoTime = queryStartNanoTime; + this.requestTime = requestTime; } protected RowIterator computeNext() @@ -213,7 +214,7 @@ public class MultiPartitionPager implements int toQuery = pageSize - counted; result = consistency == null ? pagers[current].fetchPageInternal(toQuery, executionController) - : pagers[current].fetchPage(toQuery, consistency, clientState, queryStartNanoTime); + : pagers[current].fetchPage(toQuery, consistency, clientState, requestTime); } return result.next(); } diff --git a/src/java/org/apache/cassandra/service/pager/QueryPager.java b/src/java/org/apache/cassandra/service/pager/QueryPager.java index ae0dcf74b7..1619af8a38 100644 --- a/src/java/org/apache/cassandra/service/pager/QueryPager.java +++ b/src/java/org/apache/cassandra/service/pager/QueryPager.java @@ -25,6 +25,7 @@ import org.apache.cassandra.db.partitions.PartitionIterator; import org.apache.cassandra.exceptions.RequestExecutionException; import org.apache.cassandra.exceptions.RequestValidationException; import org.apache.cassandra.service.ClientState; +import org.apache.cassandra.transport.Dispatcher; /** * Perform a query, paging it by page of a given size. @@ -54,7 +55,7 @@ public interface QueryPager return ReadExecutionController.empty(); } - public PartitionIterator fetchPage(int pageSize, ConsistencyLevel consistency, ClientState clientState, long queryStartNanoTime) throws RequestValidationException, RequestExecutionException + public PartitionIterator fetchPage(int pageSize, ConsistencyLevel consistency, ClientState clientState, Dispatcher.RequestTime requestTime) throws RequestValidationException, RequestExecutionException { return EmptyIterators.partition(); } @@ -94,7 +95,7 @@ public interface QueryPager * {@code consistency} is a serial consistency. * @return the page of result. */ - public PartitionIterator fetchPage(int pageSize, ConsistencyLevel consistency, ClientState clientState, long queryStartNanoTime) throws RequestValidationException, RequestExecutionException; + public PartitionIterator fetchPage(int pageSize, ConsistencyLevel consistency, ClientState clientState, Dispatcher.RequestTime requestTime) throws RequestValidationException, RequestExecutionException; /** * Starts a new read operation. diff --git a/src/java/org/apache/cassandra/service/paxos/Paxos.java b/src/java/org/apache/cassandra/service/paxos/Paxos.java index 473b5741ad..53b4a8244a 100644 --- a/src/java/org/apache/cassandra/service/paxos/Paxos.java +++ b/src/java/org/apache/cassandra/service/paxos/Paxos.java @@ -90,6 +90,7 @@ import org.apache.cassandra.service.paxos.cleanup.PaxosRepairState; import org.apache.cassandra.service.reads.DataResolver; import org.apache.cassandra.service.reads.repair.NoopReadRepair; import org.apache.cassandra.tracing.Tracing; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.triggers.TriggerExecutor; import org.apache.cassandra.utils.CassandraVersion; import org.apache.cassandra.utils.CollectionSerializer; @@ -820,21 +821,20 @@ public class Paxos return read.rowIterator(); } - public static PartitionIterator read(SinglePartitionReadCommand.Group group, ConsistencyLevel consistencyForConsensus) + public static PartitionIterator read(SinglePartitionReadCommand.Group group, ConsistencyLevel consistencyForConsensus, Dispatcher.RequestTime requestTime) throws InvalidRequestException, UnavailableException, ReadFailureException, ReadTimeoutException { - long start = nanoTime(); - long deadline = start + DatabaseDescriptor.getReadRpcTimeout(NANOSECONDS); - return read(group, consistencyForConsensus, start, deadline); + long deadline = requestTime.computeDeadline(DatabaseDescriptor.getReadRpcTimeout(NANOSECONDS)); + return read(group, consistencyForConsensus, requestTime, deadline); } public static PartitionIterator read(SinglePartitionReadCommand.Group group, ConsistencyLevel consistencyForConsensus, long deadline) throws InvalidRequestException, UnavailableException, ReadFailureException, ReadTimeoutException { - return read(group, consistencyForConsensus, nanoTime(), deadline); + return read(group, consistencyForConsensus, Dispatcher.RequestTime.forImmediateExecution(), deadline); } - private static PartitionIterator read(SinglePartitionReadCommand.Group group, ConsistencyLevel consistencyForConsensus, long start, long deadline) + private static PartitionIterator read(SinglePartitionReadCommand.Group group, ConsistencyLevel consistencyForConsensus, Dispatcher.RequestTime requestTime, long deadline) throws InvalidRequestException, UnavailableException, ReadFailureException, ReadTimeoutException { if (group.queries.size() > 1) @@ -901,7 +901,9 @@ public class Paxos } finally { - long latency = nanoTime() - start; + // We track latency based on request processing time, since the amount of time that request spends in the queue + // is not a representative metric of replica performance. + long latency = nanoTime() - requestTime.startedAtNanos(); readMetrics.addNano(latency); casReadMetrics.addNano(latency); readMetricsMap.get(consistencyForConsensus).addNano(latency); @@ -1039,7 +1041,7 @@ public class Paxos // round's proposal (if any). PaxosPrepare.Success success = prepare.success(); - DataResolver resolver = new DataResolver(query, success.participants, NoopReadRepair.instance, query.creationTimeNanos()); + DataResolver resolver = new DataResolver(query, success.participants, NoopReadRepair.instance, new Dispatcher.RequestTime(query.creationTimeNanos())); for (int i = 0 ; i < success.responses.size() ; ++i) resolver.preprocess(success.responses.get(i)); diff --git a/src/java/org/apache/cassandra/service/paxos/v1/AbstractPaxosCallback.java b/src/java/org/apache/cassandra/service/paxos/v1/AbstractPaxosCallback.java index e6ef1dac00..6ab5fbeb62 100644 --- a/src/java/org/apache/cassandra/service/paxos/v1/AbstractPaxosCallback.java +++ b/src/java/org/apache/cassandra/service/paxos/v1/AbstractPaxosCallback.java @@ -17,17 +17,17 @@ */ package org.apache.cassandra.service.paxos.v1; -import org.apache.cassandra.utils.concurrent.CountDownLatch; - import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.db.ConsistencyLevel; import org.apache.cassandra.db.WriteType; import org.apache.cassandra.exceptions.WriteTimeoutException; import org.apache.cassandra.net.RequestCallback; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException; +import org.apache.cassandra.utils.Clock; +import org.apache.cassandra.utils.concurrent.CountDownLatch; import static java.util.concurrent.TimeUnit.NANOSECONDS; -import static org.apache.cassandra.utils.Clock.Global.nanoTime; import static org.apache.cassandra.utils.concurrent.CountDownLatch.newCountDownLatch; public abstract class AbstractPaxosCallback implements RequestCallback @@ -35,14 +35,14 @@ public abstract class AbstractPaxosCallback implements RequestCallback protected final CountDownLatch latch; protected final int targets; private final ConsistencyLevel consistency; - private final long queryStartNanoTime; + private final Dispatcher.RequestTime requestTime; - public AbstractPaxosCallback(int targets, ConsistencyLevel consistency, long queryStartNanoTime) + public AbstractPaxosCallback(int targets, ConsistencyLevel consistency, Dispatcher.RequestTime requestTime) { this.targets = targets; this.consistency = consistency; latch = newCountDownLatch(targets); - this.queryStartNanoTime = queryStartNanoTime; + this.requestTime = requestTime; } public int getResponseCount() @@ -54,7 +54,9 @@ public abstract class AbstractPaxosCallback implements RequestCallback { try { - long timeout = DatabaseDescriptor.getWriteRpcTimeout(NANOSECONDS) - (nanoTime() - queryStartNanoTime); + long now = Clock.Global.nanoTime(); + long timeout = requestTime.computeTimeout(now, DatabaseDescriptor.getWriteRpcTimeout(NANOSECONDS)); + if (!latch.await(timeout, NANOSECONDS)) throw new WriteTimeoutException(WriteType.CAS, consistency, getResponseCount(), targets); } diff --git a/src/java/org/apache/cassandra/service/paxos/v1/PrepareCallback.java b/src/java/org/apache/cassandra/service/paxos/v1/PrepareCallback.java index a6afc3a6f9..4aedb6d63d 100644 --- a/src/java/org/apache/cassandra/service/paxos/v1/PrepareCallback.java +++ b/src/java/org/apache/cassandra/service/paxos/v1/PrepareCallback.java @@ -34,6 +34,7 @@ import org.slf4j.LoggerFactory; import org.apache.cassandra.net.Message; import org.apache.cassandra.service.paxos.Commit; import org.apache.cassandra.service.paxos.PrepareResponse; +import org.apache.cassandra.transport.Dispatcher; public class PrepareCallback extends AbstractPaxosCallback { @@ -45,9 +46,9 @@ public class PrepareCallback extends AbstractPaxosCallback private final Map commitsByReplica = new ConcurrentHashMap<>(); - public PrepareCallback(DecoratedKey key, TableMetadata metadata, int targets, ConsistencyLevel consistency, long queryStartNanoTime) + public PrepareCallback(DecoratedKey key, TableMetadata metadata, int targets, ConsistencyLevel consistency, Dispatcher.RequestTime requestTime) { - super(targets, consistency, queryStartNanoTime); + super(targets, consistency, requestTime); // need to inject the right key in the empty commit so comparing with empty commits in the response works as expected mostRecentCommit = Commit.emptyCommit(key, metadata); mostRecentInProgressCommit = Commit.emptyCommit(key, metadata); diff --git a/src/java/org/apache/cassandra/service/paxos/v1/ProposeCallback.java b/src/java/org/apache/cassandra/service/paxos/v1/ProposeCallback.java index 1c975cb30e..2d83644e07 100644 --- a/src/java/org/apache/cassandra/service/paxos/v1/ProposeCallback.java +++ b/src/java/org/apache/cassandra/service/paxos/v1/ProposeCallback.java @@ -26,6 +26,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.cassandra.net.Message; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.utils.Nemesis; /** @@ -49,9 +50,9 @@ public class ProposeCallback extends AbstractPaxosCallback private final int requiredAccepts; private final boolean failFast; - public ProposeCallback(int totalTargets, int requiredTargets, boolean failFast, ConsistencyLevel consistency, long queryStartNanoTime) + public ProposeCallback(int totalTargets, int requiredTargets, boolean failFast, ConsistencyLevel consistency, Dispatcher.RequestTime requestTime) { - super(totalTargets, consistency, queryStartNanoTime); + super(totalTargets, consistency, requestTime); this.requiredAccepts = requiredTargets; this.failFast = failFast; } diff --git a/src/java/org/apache/cassandra/service/reads/AbstractReadExecutor.java b/src/java/org/apache/cassandra/service/reads/AbstractReadExecutor.java index 8fc5d1d89b..9676734824 100644 --- a/src/java/org/apache/cassandra/service/reads/AbstractReadExecutor.java +++ b/src/java/org/apache/cassandra/service/reads/AbstractReadExecutor.java @@ -45,10 +45,13 @@ import org.apache.cassandra.service.StorageProxy.LocalReadRunnable; import org.apache.cassandra.service.reads.repair.ReadRepair; import org.apache.cassandra.tracing.TraceState; import org.apache.cassandra.tracing.Tracing; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.utils.FBUtilities; import static com.google.common.collect.Iterables.all; import static java.util.concurrent.TimeUnit.MICROSECONDS; +import static java.util.concurrent.TimeUnit.NANOSECONDS; +import static org.apache.cassandra.utils.MonotonicClock.Global.preciseTime; /** * Sends a read request to the replicas needed to satisfy a given ConsistencyLevel. @@ -69,22 +72,23 @@ public abstract class AbstractReadExecutor protected final ReadCallback handler; protected final TraceState traceState; protected final ColumnFamilyStore cfs; - protected final long queryStartNanoTime; + protected final Dispatcher.RequestTime requestTime; + private final int initialDataRequestCount; protected volatile PartitionIterator result = null; - AbstractReadExecutor(ColumnFamilyStore cfs, ReadCommand command, ReplicaPlan.ForTokenRead replicaPlan, int initialDataRequestCount, long queryStartNanoTime) + AbstractReadExecutor(ColumnFamilyStore cfs, ReadCommand command, ReplicaPlan.ForTokenRead replicaPlan, int initialDataRequestCount, Dispatcher.RequestTime requestTime) { this.command = command; this.replicaPlan = ReplicaPlan.shared(replicaPlan); this.initialDataRequestCount = initialDataRequestCount; // the ReadRepair and DigestResolver both need to see our updated - this.readRepair = ReadRepair.create(command, this.replicaPlan, queryStartNanoTime); - this.digestResolver = new DigestResolver<>(command, this.replicaPlan, queryStartNanoTime); - this.handler = new ReadCallback<>(digestResolver, command, this.replicaPlan, queryStartNanoTime); + this.readRepair = ReadRepair.create(command, this.replicaPlan, requestTime); + this.digestResolver = new DigestResolver<>(command, this.replicaPlan, requestTime); + this.handler = new ReadCallback<>(digestResolver, command, this.replicaPlan, requestTime); this.cfs = cfs; this.traceState = Tracing.instance.get(); - this.queryStartNanoTime = queryStartNanoTime; + this.requestTime = requestTime; // Set the digest version (if we request some digests). This is the smallest version amongst all our target replicas since new nodes @@ -147,7 +151,9 @@ public abstract class AbstractReadExecutor traceState.trace("reading {} from {}", readCommand.isDigestQuery() ? "digest" : "data", endpoint); if (null == message) - message = readCommand.createMessage(false); + { + message = readCommand.createMessage(false, requestTime); + } MessagingService.instance().sendWithCallback(message, endpoint, handler); } @@ -156,7 +162,7 @@ public abstract class AbstractReadExecutor if (hasLocalEndpoint) { logger.trace("reading {} locally", readCommand.isDigestQuery() ? "digest" : "data"); - Stage.READ.maybeExecuteImmediately(new LocalReadRunnable(readCommand, handler)); + Stage.READ.maybeExecuteImmediately(new LocalReadRunnable(readCommand, handler, requestTime)); } } @@ -181,9 +187,7 @@ public abstract class AbstractReadExecutor /** * @return an executor appropriate for the configured speculative read policy */ - public static AbstractReadExecutor getReadExecutor(SinglePartitionReadCommand command, - ConsistencyLevel consistencyLevel, - long queryStartNanoTime) throws UnavailableException + public static AbstractReadExecutor getReadExecutor(SinglePartitionReadCommand command, ConsistencyLevel consistencyLevel, Dispatcher.RequestTime requestTime) throws UnavailableException { Keyspace keyspace = Keyspace.open(command.metadata().keyspace); ColumnFamilyStore cfs = keyspace.getColumnFamilyStore(command.metadata().id); @@ -198,20 +202,20 @@ public abstract class AbstractReadExecutor // Speculative retry is disabled *OR* // 11980: Disable speculative retry if using EACH_QUORUM in order to prevent miscounting DC responses if (retry.equals(NeverSpeculativeRetryPolicy.INSTANCE) || consistencyLevel == ConsistencyLevel.EACH_QUORUM) - return new NeverSpeculatingReadExecutor(cfs, command, replicaPlan, queryStartNanoTime, false); + return new NeverSpeculatingReadExecutor(cfs, command, replicaPlan, requestTime, false); // There are simply no extra replicas to speculate. // Handle this separately so it can record failed attempts to speculate due to lack of replicas if (replicaPlan.contacts().size() == replicaPlan.readCandidates().size()) { boolean recordFailedSpeculation = consistencyLevel != ConsistencyLevel.ALL; - return new NeverSpeculatingReadExecutor(cfs, command, replicaPlan, queryStartNanoTime, recordFailedSpeculation); + return new NeverSpeculatingReadExecutor(cfs, command, replicaPlan, requestTime, recordFailedSpeculation); } if (retry.equals(AlwaysSpeculativeRetryPolicy.INSTANCE)) - return new AlwaysSpeculatingReadExecutor(cfs, command, replicaPlan, queryStartNanoTime); + return new AlwaysSpeculatingReadExecutor(cfs, command, replicaPlan, requestTime); else // PERCENTILE or CUSTOM. - return new SpeculatingReadExecutor(cfs, command, replicaPlan, queryStartNanoTime); + return new SpeculatingReadExecutor(cfs, command, replicaPlan, requestTime); } public boolean hasLocalRead() @@ -225,17 +229,27 @@ public abstract class AbstractReadExecutor */ boolean shouldSpeculateAndMaybeWait() { + long now = preciseTime.now(); + long sampleLatencyNanos = MICROSECONDS.toNanos(cfs.sampleReadLatencyMicros); // no latency information, or we're overloaded - if (cfs.sampleReadLatencyMicros > command.getTimeout(MICROSECONDS)) + if (sampleLatencyNanos > command.getTimeout(NANOSECONDS)) { - if (logger.isTraceEnabled()) - logger.trace("Decided not to speculate as {} > {}", cfs.sampleReadLatencyMicros, command.getTimeout(MICROSECONDS)); + logger.trace("Decided not to speculate as {}ns > {}ns", sampleLatencyNanos, command.getTimeout(NANOSECONDS)); return false; } + if (now + sampleLatencyNanos > requestTime.clientDeadline()) + { + logger.trace("Decided not to speculate as native transport timeout will be reached before speculating"); + return false; + } + + // We track latency based on request processing time, since the amount of time that request spends in the queue + // is not a representative metric of replica performance. if (logger.isTraceEnabled()) - logger.trace("Awaiting {} microseconds before speculating", cfs.sampleReadLatencyMicros); - return !handler.await(cfs.sampleReadLatencyMicros, MICROSECONDS); + logger.trace("Awaiting {}ns before speculating", sampleLatencyNanos); + + return !handler.awaitUntil(requestTime.startedAtNanos() + sampleLatencyNanos); } ReplicaPlan.ForTokenRead replicaPlan() @@ -254,9 +268,13 @@ public abstract class AbstractReadExecutor */ private final boolean logFailedSpeculation; - public NeverSpeculatingReadExecutor(ColumnFamilyStore cfs, ReadCommand command, ReplicaPlan.ForTokenRead replicaPlan, long queryStartNanoTime, boolean logFailedSpeculation) + public NeverSpeculatingReadExecutor(ColumnFamilyStore cfs, + ReadCommand command, + ReplicaPlan.ForTokenRead replicaPlan, + Dispatcher.RequestTime requestTime, + boolean logFailedSpeculation) { - super(cfs, command, replicaPlan, 1, queryStartNanoTime); + super(cfs, command, replicaPlan, 1, requestTime); this.logFailedSpeculation = logFailedSpeculation; } @@ -276,12 +294,12 @@ public abstract class AbstractReadExecutor public SpeculatingReadExecutor(ColumnFamilyStore cfs, ReadCommand command, ReplicaPlan.ForTokenRead replicaPlan, - long queryStartNanoTime) + Dispatcher.RequestTime requestTime) { // We're hitting additional targets for read repair (??). Since our "extra" replica is the least- // preferred by the snitch, we do an extra data read to start with against a replica more // likely to respond; better to let RR fail than the entire query. - super(cfs, command, replicaPlan, replicaPlan.readQuorum() < replicaPlan.contacts().size() ? 2 : 1, queryStartNanoTime); + super(cfs, command, replicaPlan, replicaPlan.readQuorum() < replicaPlan.contacts().size() ? 2 : 1, requestTime); } public void maybeTryAdditionalReplicas() @@ -327,7 +345,8 @@ public abstract class AbstractReadExecutor if (traceState != null) traceState.trace("speculating read retry on {}", extraReplica); logger.trace("speculating read retry on {}", extraReplica); - MessagingService.instance().sendWithCallback(retryCommand.createMessage(false), extraReplica.endpoint(), handler); + + MessagingService.instance().sendWithCallback(retryCommand.createMessage(false, requestTime), extraReplica.endpoint(), handler); } } @@ -346,11 +365,11 @@ public abstract class AbstractReadExecutor public AlwaysSpeculatingReadExecutor(ColumnFamilyStore cfs, ReadCommand command, ReplicaPlan.ForTokenRead replicaPlan, - long queryStartNanoTime) + Dispatcher.RequestTime requestTime) { // presumably, we speculate an extra data request here in case it is our data request that fails to respond, // and there are no more nodes to consult - super(cfs, command, replicaPlan, replicaPlan.contacts().size() > 1 ? 2 : 1, queryStartNanoTime); + super(cfs, command, replicaPlan, replicaPlan.contacts().size() > 1 ? 2 : 1, requestTime); } public void maybeTryAdditionalReplicas() diff --git a/src/java/org/apache/cassandra/service/reads/DataResolver.java b/src/java/org/apache/cassandra/service/reads/DataResolver.java index 684d5c9813..64e4c72b01 100644 --- a/src/java/org/apache/cassandra/service/reads/DataResolver.java +++ b/src/java/org/apache/cassandra/service/reads/DataResolver.java @@ -55,6 +55,7 @@ import org.apache.cassandra.service.reads.repair.NoopReadRepair; import org.apache.cassandra.service.reads.repair.ReadRepair; import org.apache.cassandra.service.reads.repair.RepairedDataTracker; import org.apache.cassandra.service.reads.repair.RepairedDataVerifier; +import org.apache.cassandra.transport.Dispatcher; import static com.google.common.collect.Iterables.*; @@ -64,14 +65,14 @@ public class DataResolver, P extends ReplicaPlan.ForRead< private final ReadRepair readRepair; private final boolean trackRepairedStatus; - public DataResolver(ReadCommand command, Supplier replicaPlan, ReadRepair readRepair, long queryStartNanoTime) + public DataResolver(ReadCommand command, Supplier replicaPlan, ReadRepair readRepair, Dispatcher.RequestTime requestTime) { - this(command, replicaPlan, readRepair, queryStartNanoTime, false); + this(command, replicaPlan, readRepair, requestTime, false); } - public DataResolver(ReadCommand command, Supplier replicaPlan, ReadRepair readRepair, long queryStartNanoTime, boolean trackRepairedStatus) + public DataResolver(ReadCommand command, Supplier replicaPlan, ReadRepair readRepair, Dispatcher.RequestTime requestTime, boolean trackRepairedStatus) { - super(command, replicaPlan, queryStartNanoTime); + super(command, replicaPlan, requestTime); this.enforceStrictLiveness = command.metadata().enforceStrictLiveness(); this.readRepair = readRepair; this.trackRepairedStatus = trackRepairedStatus; @@ -208,7 +209,7 @@ public class DataResolver, P extends ReplicaPlan.ForRead< originalResponse, command, context.mergedResultCounter, - queryStartNanoTime, + requestTime, enforceStrictLiveness) : originalResponse; } @@ -251,7 +252,7 @@ public class DataResolver, P extends ReplicaPlan.ForRead< ReplicaFilteringProtection rfp = new ReplicaFilteringProtection<>(replicaPlan().keyspace(), command, replicaPlan().consistencyLevel(), - queryStartNanoTime, + requestTime, replicas, DatabaseDescriptor.getCachedReplicaRowsWarnThreshold(), DatabaseDescriptor.getCachedReplicaRowsFailThreshold()); diff --git a/src/java/org/apache/cassandra/service/reads/DigestResolver.java b/src/java/org/apache/cassandra/service/reads/DigestResolver.java index f79937421e..cc248422c0 100644 --- a/src/java/org/apache/cassandra/service/reads/DigestResolver.java +++ b/src/java/org/apache/cassandra/service/reads/DigestResolver.java @@ -35,6 +35,7 @@ import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.locator.ReplicaPlan; import org.apache.cassandra.net.Message; import org.apache.cassandra.service.reads.repair.NoopReadRepair; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.utils.ByteBufferUtil; import static com.google.common.collect.Iterables.any; @@ -44,9 +45,9 @@ public class DigestResolver, P extends ReplicaPlan.ForRea { private volatile Message dataResponse; - public DigestResolver(ReadCommand command, ReplicaPlan.Shared replicaPlan, long queryStartNanoTime) + public DigestResolver(ReadCommand command, ReplicaPlan.Shared replicaPlan, Dispatcher.RequestTime requestTime) { - super(command, replicaPlan, queryStartNanoTime); + super(command, replicaPlan, requestTime); Preconditions.checkArgument(command instanceof SinglePartitionReadCommand, "DigestResolver can only be used with SinglePartitionReadCommand commands"); } @@ -86,7 +87,7 @@ public class DigestResolver, P extends ReplicaPlan.ForRea // This path can be triggered only if we've got responses from full replicas and they match, but // transient replica response still contains data, which needs to be reconciled. DataResolver dataResolver - = new DataResolver<>(command, replicaPlan, NoopReadRepair.instance, queryStartNanoTime); + = new DataResolver<>(command, replicaPlan, NoopReadRepair.instance, requestTime); dataResolver.preprocess(dataResponse); // Reconcile with transient replicas diff --git a/src/java/org/apache/cassandra/service/reads/ReadCallback.java b/src/java/org/apache/cassandra/service/reads/ReadCallback.java index c25b1f0f02..899c55a819 100644 --- a/src/java/org/apache/cassandra/service/reads/ReadCallback.java +++ b/src/java/org/apache/cassandra/service/reads/ReadCallback.java @@ -24,13 +24,11 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; -import org.apache.cassandra.config.DatabaseDescriptor; -import org.apache.cassandra.db.MessageParams; -import org.apache.cassandra.locator.ReplicaPlan; -import org.apache.cassandra.utils.concurrent.Condition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.db.MessageParams; import org.apache.cassandra.db.PartitionRangeReadCommand; import org.apache.cassandra.db.ReadCommand; import org.apache.cassandra.db.ReadResponse; @@ -39,6 +37,7 @@ import org.apache.cassandra.exceptions.ReadTimeoutException; import org.apache.cassandra.exceptions.RequestFailureReason; import org.apache.cassandra.locator.Endpoints; import org.apache.cassandra.locator.InetAddressAndPort; +import org.apache.cassandra.locator.ReplicaPlan; import org.apache.cassandra.net.Message; import org.apache.cassandra.net.ParamType; import org.apache.cassandra.net.RequestCallback; @@ -47,13 +46,13 @@ import org.apache.cassandra.service.reads.thresholds.CoordinatorWarnings; import org.apache.cassandra.service.reads.thresholds.WarningContext; import org.apache.cassandra.service.reads.thresholds.WarningsSnapshot; import org.apache.cassandra.tracing.Tracing; +import org.apache.cassandra.transport.Dispatcher; +import org.apache.cassandra.utils.concurrent.Condition; import org.apache.cassandra.utils.concurrent.UncheckedInterruptedException; import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.concurrent.atomic.AtomicIntegerFieldUpdater.newUpdater; import static org.apache.cassandra.tracing.Tracing.isTracing; -import static org.apache.cassandra.utils.Clock.Global.nanoTime; import static org.apache.cassandra.utils.concurrent.Condition.newOneTimeCondition; public class ReadCallback, P extends ReplicaPlan.ForRead> implements RequestCallback @@ -62,7 +61,7 @@ public class ReadCallback, P extends ReplicaPlan.ForRead< public final ResponseResolver resolver; final Condition condition = newOneTimeCondition(); - private final long queryStartNanoTime; + private final Dispatcher.RequestTime requestTime; final int blockFor; // TODO: move to replica plan as well? // this uses a plain reference, but is initialised before handoff to any other threads; the later updates // may not be visible to the threads immediately, but ReplicaPlan only contains final fields, so they will never see an uninitialised object @@ -76,11 +75,11 @@ public class ReadCallback, P extends ReplicaPlan.ForRead< private static final AtomicReferenceFieldUpdater warningsUpdater = AtomicReferenceFieldUpdater.newUpdater(ReadCallback.class, WarningContext.class, "warningContext"); - public ReadCallback(ResponseResolver resolver, ReadCommand command, ReplicaPlan.Shared replicaPlan, long queryStartNanoTime) + public ReadCallback(ResponseResolver resolver, ReadCommand command, ReplicaPlan.Shared replicaPlan, Dispatcher.RequestTime requestTime) { this.command = command; this.resolver = resolver; - this.queryStartNanoTime = queryStartNanoTime; + this.requestTime = requestTime; this.replicaPlan = replicaPlan; this.blockFor = replicaPlan.get().readQuorum(); this.failureReasonByEndpoint = new ConcurrentHashMap<>(); @@ -96,12 +95,29 @@ public class ReadCallback, P extends ReplicaPlan.ForRead< return replicaPlan.get(); } - public boolean await(long timePastStart, TimeUnit unit) + public boolean await(long commandTimeout, TimeUnit unit) + { + return awaitUntil(requestTime.computeDeadline(unit.toNanos(commandTimeout))); + } + + /** + * In case of speculation, we want to time out the request immediately if we have _also_ hit a deadline. + * + * For example, we have a read timeout of 10s, 99% latency of 5 seconds, native_transport_timeout of 12s, + * and time base is QUEUE: + * * Request has spent 3 seconds in the queue. Here, we will wait for 2 seconds and try to speculate + * * Request has spent 10 seconds in the queue. Here, we will wait for 0 seconds and try to speculate + * + * If the time base is REQUEST: + * * Request has spent 10 seconds in the queue. Here, we will only wait 2 seconds and then try to speculate + * + * We should _not_ speculate in all these cases, since by that time we are already past request deadline. + */ + public boolean awaitUntil(long deadline) { - long time = unit.toNanos(timePastStart) - (nanoTime() - queryStartNanoTime); try { - return condition.await(time, NANOSECONDS); + return condition.awaitUntil(deadline); } catch (InterruptedException e) { diff --git a/src/java/org/apache/cassandra/service/reads/ReplicaFilteringProtection.java b/src/java/org/apache/cassandra/service/reads/ReplicaFilteringProtection.java index fa14c38f16..9ec02a5b20 100644 --- a/src/java/org/apache/cassandra/service/reads/ReplicaFilteringProtection.java +++ b/src/java/org/apache/cassandra/service/reads/ReplicaFilteringProtection.java @@ -72,6 +72,7 @@ import org.apache.cassandra.service.ClientWarn; import org.apache.cassandra.service.StorageProxy; import org.apache.cassandra.service.reads.repair.NoopReadRepair; import org.apache.cassandra.tracing.Tracing; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.utils.NoSpamLogger; import org.apache.cassandra.utils.btree.BTreeSet; @@ -100,7 +101,7 @@ public class ReplicaFilteringProtection> private final Keyspace keyspace; private final ReadCommand command; private final ConsistencyLevel consistency; - private final long queryStartNanoTime; + private final Dispatcher.RequestTime requestTime; private final E sources; private final TableMetrics tableMetrics; @@ -121,7 +122,7 @@ public class ReplicaFilteringProtection> ReplicaFilteringProtection(Keyspace keyspace, ReadCommand command, ConsistencyLevel consistency, - long queryStartNanoTime, + Dispatcher.RequestTime requestTime, E sources, int cachedRowsWarnThreshold, int cachedRowsFailThreshold) @@ -129,7 +130,7 @@ public class ReplicaFilteringProtection> this.keyspace = keyspace; this.command = command; this.consistency = consistency; - this.queryStartNanoTime = queryStartNanoTime; + this.requestTime = requestTime; this.sources = sources; this.originalPartitions = new ArrayList<>(sources.size()); @@ -148,19 +149,19 @@ public class ReplicaFilteringProtection> { @SuppressWarnings("unchecked") DataResolver resolver = - new DataResolver<>(cmd, replicaPlan, (NoopReadRepair) NoopReadRepair.instance, queryStartNanoTime); + new DataResolver<>(cmd, replicaPlan, (NoopReadRepair) NoopReadRepair.instance, requestTime); - ReadCallback handler = new ReadCallback<>(resolver, cmd, replicaPlan, queryStartNanoTime); + ReadCallback handler = new ReadCallback<>(resolver, cmd, replicaPlan, requestTime); if (source.isSelf()) { - Stage.READ.maybeExecuteImmediately(new StorageProxy.LocalReadRunnable(cmd, handler)); + Stage.READ.maybeExecuteImmediately(new StorageProxy.LocalReadRunnable(cmd, handler, requestTime)); } else { if (source.isTransient()) cmd = cmd.copyAsTransientQuery(source); - MessagingService.instance().sendWithCallback(cmd.createMessage(false), source.endpoint(), handler); + MessagingService.instance().sendWithCallback(cmd.createMessage(false, requestTime), source.endpoint(), handler); } // We don't call handler.get() because we want to preserve tombstones since we're still in the middle of merging node results. diff --git a/src/java/org/apache/cassandra/service/reads/ResponseResolver.java b/src/java/org/apache/cassandra/service/reads/ResponseResolver.java index 02e565d536..d0d684164b 100644 --- a/src/java/org/apache/cassandra/service/reads/ResponseResolver.java +++ b/src/java/org/apache/cassandra/service/reads/ResponseResolver.java @@ -27,6 +27,7 @@ import org.apache.cassandra.db.ReadResponse; import org.apache.cassandra.locator.Endpoints; import org.apache.cassandra.locator.ReplicaPlan; import org.apache.cassandra.net.Message; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.utils.concurrent.Accumulator; public abstract class ResponseResolver, P extends ReplicaPlan.ForRead> @@ -39,14 +40,14 @@ public abstract class ResponseResolver, P extends Replica // Accumulator gives us non-blocking thread-safety with optimal algorithmic constraints protected final Accumulator> responses; - protected final long queryStartNanoTime; + protected final Dispatcher.RequestTime requestTime; - public ResponseResolver(ReadCommand command, Supplier replicaPlan, long queryStartNanoTime) + public ResponseResolver(ReadCommand command, Supplier replicaPlan, Dispatcher.RequestTime requestTime) { this.command = command; this.replicaPlan = replicaPlan; this.responses = new Accumulator<>(replicaPlan.get().readCandidates().size()); - this.queryStartNanoTime = queryStartNanoTime; + this.requestTime = requestTime; } protected P replicaPlan() diff --git a/src/java/org/apache/cassandra/service/reads/ShortReadPartitionsProtection.java b/src/java/org/apache/cassandra/service/reads/ShortReadPartitionsProtection.java index f49c9fb3a8..e9870f1f1d 100644 --- a/src/java/org/apache/cassandra/service/reads/ShortReadPartitionsProtection.java +++ b/src/java/org/apache/cassandra/service/reads/ShortReadPartitionsProtection.java @@ -46,6 +46,7 @@ import org.apache.cassandra.net.MessagingService; import org.apache.cassandra.service.reads.repair.NoopReadRepair; import org.apache.cassandra.service.StorageProxy; import org.apache.cassandra.tracing.Tracing; +import org.apache.cassandra.transport.Dispatcher; public class ShortReadPartitionsProtection extends Transformation implements MorePartitions { @@ -62,21 +63,21 @@ public class ShortReadPartitionsProtection extends Transformation, P extends ReplicaPlan.ForRead> UnfilteredPartitionIterator executeReadCommand(ReadCommand cmd, ReplicaPlan.Shared replicaPlan) { - DataResolver resolver = new DataResolver<>(cmd, replicaPlan, (NoopReadRepair)NoopReadRepair.instance, queryStartNanoTime); - ReadCallback handler = new ReadCallback<>(resolver, cmd, replicaPlan, queryStartNanoTime); + DataResolver resolver = new DataResolver<>(cmd, replicaPlan, (NoopReadRepair)NoopReadRepair.instance, requestTime); + ReadCallback handler = new ReadCallback<>(resolver, cmd, replicaPlan, requestTime); if (source.isSelf()) { - Stage.READ.maybeExecuteImmediately(new StorageProxy.LocalReadRunnable(cmd, handler)); + Stage.READ.maybeExecuteImmediately(new StorageProxy.LocalReadRunnable(cmd, handler, requestTime)); } else { if (source.isTransient()) cmd = cmd.copyAsTransientQuery(source); - MessagingService.instance().sendWithCallback(cmd.createMessage(false), source.endpoint(), handler); + MessagingService.instance().sendWithCallback(cmd.createMessage(false, requestTime), source.endpoint(), handler); } // We don't call handler.get() because we want to preserve tombstones since we're still in the middle of merging node results. diff --git a/src/java/org/apache/cassandra/service/reads/ShortReadProtection.java b/src/java/org/apache/cassandra/service/reads/ShortReadProtection.java index e38a828e4f..1eca190a73 100644 --- a/src/java/org/apache/cassandra/service/reads/ShortReadProtection.java +++ b/src/java/org/apache/cassandra/service/reads/ShortReadProtection.java @@ -25,6 +25,7 @@ import org.apache.cassandra.db.partitions.UnfilteredPartitionIterator; import org.apache.cassandra.db.transform.MorePartitions; import org.apache.cassandra.db.transform.Transformation; import org.apache.cassandra.locator.Replica; +import org.apache.cassandra.transport.Dispatcher; /** * We have a potential short read if the result from a given node contains the requested number of rows @@ -43,7 +44,7 @@ public class ShortReadProtection UnfilteredPartitionIterator partitions, ReadCommand command, DataLimits.Counter mergedResultCounter, - long queryStartNanoTime, + Dispatcher.RequestTime requestTime, boolean enforceStrictLiveness) { DataLimits.Counter singleResultCounter = command.limits().newCounter(command.nowInSec(), @@ -56,7 +57,7 @@ public class ShortReadProtection preFetchCallback, singleResultCounter, mergedResultCounter, - queryStartNanoTime); + requestTime); /* * The order of extention and transformations is important here. Extending with more partitions has to happen diff --git a/src/java/org/apache/cassandra/service/reads/range/RangeCommandIterator.java b/src/java/org/apache/cassandra/service/reads/range/RangeCommandIterator.java index c258ef7939..3e9ac453c7 100644 --- a/src/java/org/apache/cassandra/service/reads/range/RangeCommandIterator.java +++ b/src/java/org/apache/cassandra/service/reads/range/RangeCommandIterator.java @@ -50,6 +50,7 @@ import org.apache.cassandra.service.reads.DataResolver; import org.apache.cassandra.service.reads.ReadCallback; import org.apache.cassandra.service.reads.repair.ReadRepair; import org.apache.cassandra.tracing.Tracing; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.utils.AbstractIterator; import org.apache.cassandra.utils.CloseableIterator; @@ -66,11 +67,12 @@ public class RangeCommandIterator extends AbstractIterator implemen final int totalRangeCount; final PartitionRangeReadCommand command; final boolean enforceStrictLiveness; - final long queryStartNanoTime; + final Dispatcher.RequestTime requestTime; + int rangesQueried; int batchesRequested = 0; - private final long startTime; + private DataLimits.Counter counter; private PartitionIterator sentQueryIterator; @@ -85,16 +87,14 @@ public class RangeCommandIterator extends AbstractIterator implemen int concurrencyFactor, int maxConcurrencyFactor, int totalRangeCount, - long queryStartNanoTime) + Dispatcher.RequestTime requestTime) { this.replicaPlans = replicaPlans; this.command = command; this.concurrencyFactor = concurrencyFactor; this.maxConcurrencyFactor = maxConcurrencyFactor; this.totalRangeCount = totalRangeCount; - this.queryStartNanoTime = queryStartNanoTime; - - startTime = nanoTime(); + this.requestTime = requestTime; enforceStrictLiveness = command.metadata().enforceStrictLiveness(); } @@ -196,15 +196,15 @@ public class RangeCommandIterator extends AbstractIterator implemen ReplicaPlan.SharedForRangeRead sharedReplicaPlan = ReplicaPlan.shared(replicaPlan); ReadRepair readRepair = - ReadRepair.create(command, sharedReplicaPlan, queryStartNanoTime); + ReadRepair.create(command, sharedReplicaPlan, requestTime); DataResolver resolver = - new DataResolver<>(rangeCommand, sharedReplicaPlan, readRepair, queryStartNanoTime, trackRepairedStatus); + new DataResolver<>(rangeCommand, sharedReplicaPlan, readRepair, requestTime, trackRepairedStatus); ReadCallback handler = - new ReadCallback<>(resolver, rangeCommand, sharedReplicaPlan, queryStartNanoTime); + new ReadCallback<>(resolver, rangeCommand, sharedReplicaPlan, requestTime); if (replicaPlan.contacts().size() == 1 && replicaPlan.contacts().get(0).isSelf()) { - Stage.READ.execute(new StorageProxy.LocalReadRunnable(rangeCommand, handler, trackRepairedStatus)); + Stage.READ.execute(new StorageProxy.LocalReadRunnable(rangeCommand, handler, requestTime, trackRepairedStatus)); } else { @@ -212,7 +212,7 @@ public class RangeCommandIterator extends AbstractIterator implemen { Tracing.trace("Enqueuing request to {}", replica); ReadCommand command = replica.isFull() ? rangeCommand : rangeCommand.copyAsTransientQuery(replica); - Message message = command.createMessage(trackRepairedStatus && replica.isFull()); + Message message = command.createMessage(trackRepairedStatus && replica.isFull(), requestTime); MessagingService.instance().sendWithCallback(message, replica.endpoint(), handler); } } @@ -267,7 +267,9 @@ public class RangeCommandIterator extends AbstractIterator implemen } finally { - long latency = nanoTime() - startTime; + // We track latency based on request processing time, since the amount of time that request spends in the queue + // is not a representative metric of replica performance. + long latency = nanoTime() - requestTime.startedAtNanos(); rangeMetrics.addNano(latency); rangeMetrics.roundTrips.update(batchesRequested); Keyspace.openAndGetStore(command.metadata()).metric.coordinatorScanLatency.update(latency, TimeUnit.NANOSECONDS); diff --git a/src/java/org/apache/cassandra/service/reads/range/RangeCommands.java b/src/java/org/apache/cassandra/service/reads/range/RangeCommands.java index d4ff07ecda..ded4d4cdbe 100644 --- a/src/java/org/apache/cassandra/service/reads/range/RangeCommands.java +++ b/src/java/org/apache/cassandra/service/reads/range/RangeCommands.java @@ -35,6 +35,7 @@ import org.apache.cassandra.index.Index; import org.apache.cassandra.locator.ReplicaPlans; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.tracing.Tracing; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.utils.FBUtilities; public class RangeCommands @@ -54,10 +55,10 @@ public class RangeCommands public static PartitionIterator partitions(PartitionRangeReadCommand command, ConsistencyLevel consistencyLevel, - long queryStartNanoTime) + Dispatcher.RequestTime requestTime) { // Note that in general, a RangeCommandIterator will honor the command limit for each range, but will not enforce it globally. - RangeCommandIterator rangeCommands = rangeCommandIterator(command, consistencyLevel, queryStartNanoTime); + RangeCommandIterator rangeCommands = rangeCommandIterator(command, consistencyLevel, requestTime); return command.limits().filter(command.postReconciliationProcessing(rangeCommands), command.nowInSec(), command.selectsFullPartition(), @@ -67,7 +68,7 @@ public class RangeCommands @VisibleForTesting static RangeCommandIterator rangeCommandIterator(PartitionRangeReadCommand command, ConsistencyLevel consistencyLevel, - long queryStartNanoTime) + Dispatcher.RequestTime requestTime) { Tracing.trace("Computing ranges to query"); @@ -78,7 +79,7 @@ public class RangeCommands consistencyLevel); if (command.isTopK()) - return new ScanAllRangesCommandIterator(keyspace, replicaPlans, command, replicaPlans.size(), queryStartNanoTime); + return new ScanAllRangesCommandIterator(keyspace, replicaPlans, command, replicaPlans.size(), requestTime); int maxConcurrencyFactor = Math.min(replicaPlans.size(), MAX_CONCURRENT_RANGE_REQUESTS); int concurrencyFactor = maxConcurrencyFactor; @@ -111,7 +112,7 @@ public class RangeCommands concurrencyFactor, maxConcurrencyFactor, replicaPlans.size(), - queryStartNanoTime); + requestTime); } /** diff --git a/src/java/org/apache/cassandra/service/reads/range/ScanAllRangesCommandIterator.java b/src/java/org/apache/cassandra/service/reads/range/ScanAllRangesCommandIterator.java index b551c6fb94..53f55f8938 100644 --- a/src/java/org/apache/cassandra/service/reads/range/ScanAllRangesCommandIterator.java +++ b/src/java/org/apache/cassandra/service/reads/range/ScanAllRangesCommandIterator.java @@ -40,6 +40,7 @@ import org.apache.cassandra.service.reads.DataResolver; import org.apache.cassandra.service.reads.ReadCallback; import org.apache.cassandra.service.reads.repair.NoopReadRepair; import org.apache.cassandra.tracing.Tracing; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.utils.CloseableIterator; /** @@ -62,9 +63,9 @@ public class ScanAllRangesCommandIterator extends RangeCommandIterator ScanAllRangesCommandIterator(Keyspace keyspace, CloseableIterator replicaPlans, PartitionRangeReadCommand command, int totalRangeCount, - long queryStartNanoTime) + Dispatcher.RequestTime requestTime) { - super(replicaPlans, command, totalRangeCount, totalRangeCount, totalRangeCount, queryStartNanoTime); + super(replicaPlans, command, totalRangeCount, totalRangeCount, totalRangeCount, requestTime); Preconditions.checkState(command.isTopK()); this.keyspace = keyspace; @@ -91,14 +92,14 @@ public class ScanAllRangesCommandIterator extends RangeCommandIterator ReplicaPlan.ForRangeRead plan = ReplicaPlans.forFullRangeRead(keyspace, consistencyLevel, command.dataRange().keyRange(), replicasToQuery, totalRangeCount); ReplicaPlan.SharedForRangeRead sharedReplicaPlan = ReplicaPlan.shared(plan); - DataResolver resolver = new DataResolver<>(command, sharedReplicaPlan, NoopReadRepair.instance, queryStartNanoTime, false); - ReadCallback handler = new ReadCallback<>(resolver, command, sharedReplicaPlan, queryStartNanoTime); + DataResolver resolver = new DataResolver<>(command, sharedReplicaPlan, NoopReadRepair.instance, requestTime, false); + ReadCallback handler = new ReadCallback<>(resolver, command, sharedReplicaPlan, requestTime); int nodes = 0; for (InetAddressAndPort endpoint : replicasToQuery) { Tracing.trace("Enqueuing request to {}", endpoint); - Message message = command.createMessage(false); + Message message = command.createMessage(false, requestTime); MessagingService.instance().sendWithCallback(message, endpoint, handler); nodes++; } diff --git a/src/java/org/apache/cassandra/service/reads/repair/AbstractReadRepair.java b/src/java/org/apache/cassandra/service/reads/repair/AbstractReadRepair.java index 28f94fdef7..8343b83b07 100644 --- a/src/java/org/apache/cassandra/service/reads/repair/AbstractReadRepair.java +++ b/src/java/org/apache/cassandra/service/reads/repair/AbstractReadRepair.java @@ -20,12 +20,11 @@ package org.apache.cassandra.service.reads.repair; import java.util.function.Consumer; -import com.codahale.metrics.Meter; import com.google.common.base.Preconditions; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.codahale.metrics.Meter; import org.apache.cassandra.concurrent.Stage; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.db.ColumnFamilyStore; @@ -46,6 +45,7 @@ import org.apache.cassandra.service.reads.DataResolver; import org.apache.cassandra.service.reads.DigestResolver; import org.apache.cassandra.service.reads.ReadCallback; import org.apache.cassandra.tracing.Tracing; +import org.apache.cassandra.transport.Dispatcher; import static java.util.concurrent.TimeUnit.MICROSECONDS; @@ -55,7 +55,7 @@ public abstract class AbstractReadRepair, P extends Repli protected static final Logger logger = LoggerFactory.getLogger(AbstractReadRepair.class); protected final ReadCommand command; - protected final long queryStartNanoTime; + protected final Dispatcher.RequestTime requestTime; protected final ReplicaPlan.Shared replicaPlan; protected final ColumnFamilyStore cfs; @@ -77,10 +77,10 @@ public abstract class AbstractReadRepair, P extends Repli public AbstractReadRepair(ReadCommand command, ReplicaPlan.Shared replicaPlan, - long queryStartNanoTime) + Dispatcher.RequestTime requestTime) { this.command = command; - this.queryStartNanoTime = queryStartNanoTime; + this.requestTime = requestTime; this.replicaPlan = replicaPlan; this.cfs = Keyspace.openAndGetStore(command.metadata()); } @@ -96,7 +96,7 @@ public abstract class AbstractReadRepair, P extends Repli if (to.isSelf()) { - Stage.READ.maybeExecuteImmediately(new StorageProxy.LocalReadRunnable(command, readCallback, trackRepairedStatus)); + Stage.READ.maybeExecuteImmediately(new StorageProxy.LocalReadRunnable(command, readCallback, requestTime, trackRepairedStatus)); return; } @@ -116,7 +116,7 @@ public abstract class AbstractReadRepair, P extends Repli Tracing.trace("Enqueuing {} data read to {}", type, to); } - Message message = command.createMessage(trackRepairedStatus && to.isFull()); + Message message = command.createMessage(trackRepairedStatus && to.isFull(), requestTime); MessagingService.instance().sendWithCallback(message, to.endpoint(), readCallback); } @@ -139,8 +139,8 @@ public abstract class AbstractReadRepair, P extends Repli boolean trackRepairedStatus = DatabaseDescriptor.getRepairedDataTrackingForPartitionReadsEnabled(); // Do a full data read to resolve the correct response (and repair node that need be) - DataResolver resolver = new DataResolver<>(command, replicaPlan, this, queryStartNanoTime, trackRepairedStatus); - ReadCallback readCallback = new ReadCallback<>(resolver, command, replicaPlan, queryStartNanoTime); + DataResolver resolver = new DataResolver<>(command, replicaPlan, this, requestTime, trackRepairedStatus); + ReadCallback readCallback = new ReadCallback<>(resolver, command, replicaPlan, requestTime); digestRepair = new DigestRepair<>(resolver, readCallback, resultConsumer); @@ -190,7 +190,7 @@ public abstract class AbstractReadRepair, P extends Repli if (repair == null) return; - if (shouldSpeculate() && !repair.readCallback.await(cfs.sampleReadLatencyMicros, MICROSECONDS)) + if (shouldSpeculate() && !repair.readCallback.awaitUntil(requestTime.startedAtNanos() + MICROSECONDS.toNanos(cfs.sampleReadLatencyMicros))) { Replica uncontacted = replicaPlan().firstUncontactedCandidate(replica -> true); if (uncontacted == null) diff --git a/src/java/org/apache/cassandra/service/reads/repair/BlockingReadRepair.java b/src/java/org/apache/cassandra/service/reads/repair/BlockingReadRepair.java index 9143a475fe..65bea44629 100644 --- a/src/java/org/apache/cassandra/service/reads/repair/BlockingReadRepair.java +++ b/src/java/org/apache/cassandra/service/reads/repair/BlockingReadRepair.java @@ -37,6 +37,7 @@ import org.apache.cassandra.locator.Replica; import org.apache.cassandra.locator.ReplicaPlan; import org.apache.cassandra.metrics.ReadRepairMetrics; import org.apache.cassandra.tracing.Tracing; +import org.apache.cassandra.transport.Dispatcher; import static java.util.concurrent.TimeUnit.MICROSECONDS; import static java.util.concurrent.TimeUnit.NANOSECONDS; @@ -53,9 +54,9 @@ public class BlockingReadRepair, P extends ReplicaPlan.Fo protected final Queue repairs = new ConcurrentLinkedQueue<>(); - BlockingReadRepair(ReadCommand command, ReplicaPlan.Shared replicaPlan, long queryStartNanoTime) + BlockingReadRepair(ReadCommand command, ReplicaPlan.Shared replicaPlan, Dispatcher.RequestTime requestTime) { - super(command, replicaPlan, queryStartNanoTime); + super(command, replicaPlan, requestTime); } public UnfilteredPartitionIterators.MergeListener getMergeListener(P replicaPlan) @@ -84,7 +85,9 @@ public class BlockingReadRepair, P extends ReplicaPlan.Fo BlockingPartitionRepair timedOut = null; for (BlockingPartitionRepair repair : repairs) { - if (!repair.awaitRepairsUntil(DatabaseDescriptor.getReadRpcTimeout(NANOSECONDS) + queryStartNanoTime, NANOSECONDS)) + long deadline = requestTime.computeDeadline(DatabaseDescriptor.getReadRpcTimeout(NANOSECONDS)); + + if (!repair.awaitRepairsUntil(deadline, NANOSECONDS)) { timedOut = repair; break; diff --git a/src/java/org/apache/cassandra/service/reads/repair/ReadOnlyReadRepair.java b/src/java/org/apache/cassandra/service/reads/repair/ReadOnlyReadRepair.java index 72a12980e7..46b30a9279 100644 --- a/src/java/org/apache/cassandra/service/reads/repair/ReadOnlyReadRepair.java +++ b/src/java/org/apache/cassandra/service/reads/repair/ReadOnlyReadRepair.java @@ -29,6 +29,7 @@ import org.apache.cassandra.locator.Endpoints; import org.apache.cassandra.locator.Replica; import org.apache.cassandra.locator.ReplicaPlan; import org.apache.cassandra.metrics.ReadRepairMetrics; +import org.apache.cassandra.transport.Dispatcher; /** * Only performs the collection of data responses and reconciliation of them, doesn't send repair mutations @@ -37,9 +38,9 @@ import org.apache.cassandra.metrics.ReadRepairMetrics; public class ReadOnlyReadRepair, P extends ReplicaPlan.ForRead> extends AbstractReadRepair { - ReadOnlyReadRepair(ReadCommand command, ReplicaPlan.Shared replicaPlan, long queryStartNanoTime) + ReadOnlyReadRepair(ReadCommand command, ReplicaPlan.Shared replicaPlan, Dispatcher.RequestTime requestTime) { - super(command, replicaPlan, queryStartNanoTime); + super(command, replicaPlan, requestTime); } @Override diff --git a/src/java/org/apache/cassandra/service/reads/repair/ReadRepair.java b/src/java/org/apache/cassandra/service/reads/repair/ReadRepair.java index a6a9be29f9..a63cc7f6bf 100644 --- a/src/java/org/apache/cassandra/service/reads/repair/ReadRepair.java +++ b/src/java/org/apache/cassandra/service/reads/repair/ReadRepair.java @@ -31,19 +31,20 @@ import org.apache.cassandra.exceptions.ReadTimeoutException; import org.apache.cassandra.locator.Replica; import org.apache.cassandra.locator.ReplicaPlan; import org.apache.cassandra.service.reads.DigestResolver; +import org.apache.cassandra.transport.Dispatcher; public interface ReadRepair, P extends ReplicaPlan.ForRead> { public interface Factory { , P extends ReplicaPlan.ForRead> - ReadRepair create(ReadCommand command, ReplicaPlan.Shared replicaPlan, long queryStartNanoTime); + ReadRepair create(ReadCommand command, ReplicaPlan.Shared replicaPlan, Dispatcher.RequestTime requestTime); } static , P extends ReplicaPlan.ForRead> - ReadRepair create(ReadCommand command, ReplicaPlan.Shared replicaPlan, long queryStartNanoTime) + ReadRepair create(ReadCommand command, ReplicaPlan.Shared replicaPlan, Dispatcher.RequestTime requestTime) { - return command.metadata().params.readRepair.create(command, replicaPlan, queryStartNanoTime); + return command.metadata().params.readRepair.create(command, replicaPlan, requestTime); } /** diff --git a/src/java/org/apache/cassandra/service/reads/repair/ReadRepairStrategy.java b/src/java/org/apache/cassandra/service/reads/repair/ReadRepairStrategy.java index 0d9caade35..975b8217d2 100644 --- a/src/java/org/apache/cassandra/service/reads/repair/ReadRepairStrategy.java +++ b/src/java/org/apache/cassandra/service/reads/repair/ReadRepairStrategy.java @@ -21,24 +21,25 @@ package org.apache.cassandra.service.reads.repair; import org.apache.cassandra.db.ReadCommand; import org.apache.cassandra.locator.Endpoints; import org.apache.cassandra.locator.ReplicaPlan; +import org.apache.cassandra.transport.Dispatcher; public enum ReadRepairStrategy implements ReadRepair.Factory { NONE { public , P extends ReplicaPlan.ForRead> - ReadRepair create(ReadCommand command, ReplicaPlan.Shared replicaPlan, long queryStartNanoTime) + ReadRepair create(ReadCommand command, ReplicaPlan.Shared replicaPlan, Dispatcher.RequestTime requestTime) { - return new ReadOnlyReadRepair<>(command, replicaPlan, queryStartNanoTime); + return new ReadOnlyReadRepair<>(command, replicaPlan, requestTime); } }, BLOCKING { public , P extends ReplicaPlan.ForRead> - ReadRepair create(ReadCommand command, ReplicaPlan.Shared replicaPlan, long queryStartNanoTime) + ReadRepair create(ReadCommand command, ReplicaPlan.Shared replicaPlan, Dispatcher.RequestTime requestTime) { - return new BlockingReadRepair<>(command, replicaPlan, queryStartNanoTime); + return new BlockingReadRepair<>(command, replicaPlan, requestTime); } }; diff --git a/src/java/org/apache/cassandra/tools/NodeProbe.java b/src/java/org/apache/cassandra/tools/NodeProbe.java index f541eabe54..16acd96e13 100644 --- a/src/java/org/apache/cassandra/tools/NodeProbe.java +++ b/src/java/org/apache/cassandra/tools/NodeProbe.java @@ -1337,9 +1337,9 @@ public class NodeProbe implements AutoCloseable } } - public void stopNativeTransport() + public void stopNativeTransport(boolean force) { - ssProxy.stopNativeTransport(); + ssProxy.stopNativeTransport(force); } public void startNativeTransport() diff --git a/src/java/org/apache/cassandra/tools/nodetool/DisableBinary.java b/src/java/org/apache/cassandra/tools/nodetool/DisableBinary.java index 79b9219082..11e024f289 100644 --- a/src/java/org/apache/cassandra/tools/nodetool/DisableBinary.java +++ b/src/java/org/apache/cassandra/tools/nodetool/DisableBinary.java @@ -18,16 +18,19 @@ package org.apache.cassandra.tools.nodetool; import io.airlift.airline.Command; - +import io.airlift.airline.Option; import org.apache.cassandra.tools.NodeProbe; import org.apache.cassandra.tools.NodeTool.NodeToolCmd; @Command(name = "disablebinary", description = "Disable native transport (binary protocol)") public class DisableBinary extends NodeToolCmd { + @Option(title = "force", name = { "-f", "--force"}, description = "Use -f to interrupt client requests that have already started") + private boolean force = false; + @Override public void execute(NodeProbe probe) { - probe.stopNativeTransport(); + probe.stopNativeTransport(force); } } diff --git a/src/java/org/apache/cassandra/tracing/TraceStateImpl.java b/src/java/org/apache/cassandra/tracing/TraceStateImpl.java index 113420dd36..edc2cb796a 100644 --- a/src/java/org/apache/cassandra/tracing/TraceStateImpl.java +++ b/src/java/org/apache/cassandra/tracing/TraceStateImpl.java @@ -33,6 +33,7 @@ import org.apache.cassandra.db.Mutation; import org.apache.cassandra.exceptions.OverloadedException; import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.service.StorageProxy; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.utils.JVMStabilityInspector; import org.apache.cassandra.utils.TimeUUID; import org.apache.cassandra.utils.concurrent.Future; @@ -40,7 +41,6 @@ import org.apache.cassandra.utils.concurrent.FutureCombiner; import static java.util.Collections.singletonList; import static org.apache.cassandra.db.ConsistencyLevel.ANY; -import static org.apache.cassandra.utils.Clock.Global.nanoTime; /** * ThreadLocal state for a tracing session. The presence of an instance of this class as a ThreadLocal denotes that an @@ -113,7 +113,7 @@ public class TraceStateImpl extends TraceState { try { - StorageProxy.mutate(singletonList(mutation), ANY, nanoTime()); + StorageProxy.mutate(singletonList(mutation), ANY, Dispatcher.RequestTime.forImmediateExecution()); } catch (OverloadedException e) { diff --git a/src/java/org/apache/cassandra/transport/CQLMessageHandler.java b/src/java/org/apache/cassandra/transport/CQLMessageHandler.java index 09e9996841..a2527a0cc8 100644 --- a/src/java/org/apache/cassandra/transport/CQLMessageHandler.java +++ b/src/java/org/apache/cassandra/transport/CQLMessageHandler.java @@ -21,10 +21,9 @@ package org.apache.cassandra.transport; import java.io.IOException; import java.nio.ByteBuffer; import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; import com.google.common.primitives.Ints; -import org.apache.cassandra.transport.ClientResourceLimits.Overload; -import org.apache.cassandra.utils.concurrent.NonBlockingRateLimiter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,8 +32,8 @@ import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.exceptions.OverloadedException; -import org.apache.cassandra.metrics.ClientMetrics; import org.apache.cassandra.metrics.ClientMessageSizeMetrics; +import org.apache.cassandra.metrics.ClientMetrics; import org.apache.cassandra.net.AbstractMessageHandler; import org.apache.cassandra.net.FrameDecoder; import org.apache.cassandra.net.FrameDecoder.IntactFrame; @@ -42,9 +41,11 @@ import org.apache.cassandra.net.FrameEncoder; import org.apache.cassandra.net.ResourceLimits; import org.apache.cassandra.net.ResourceLimits.Limit; import org.apache.cassandra.net.ShareableBytes; +import org.apache.cassandra.transport.ClientResourceLimits.Overload; import org.apache.cassandra.transport.Flusher.FlushItem.Framed; import org.apache.cassandra.transport.messages.ErrorMessage; import org.apache.cassandra.utils.NoSpamLogger; +import org.apache.cassandra.utils.concurrent.NonBlockingRateLimiter; import static org.apache.cassandra.utils.MonotonicClock.Global.approxTime; @@ -81,6 +82,7 @@ public class CQLMessageHandler extends AbstractMessageHandler public static final int LARGE_MESSAGE_THRESHOLD = FrameEncoder.Payload.MAX_SIZE - 1; public static final TimeUnit RATE_LIMITER_DELAY_UNIT = TimeUnit.NANOSECONDS; + private final QueueBackpressure queueBackpressure; private final Envelope.Decoder envelopeDecoder; private final Message.Decoder messageDecoder; private final FrameEncoder.PayloadAllocator payloadAllocator; @@ -95,7 +97,8 @@ public class CQLMessageHandler extends AbstractMessageHandler interface MessageConsumer { - void accept(Channel channel, M message, Dispatcher.FlushItemConverter toFlushItem, Overload backpressure); + void dispatch(Channel channel, M message, Dispatcher.FlushItemConverter toFlushItem, Overload backpressure); + boolean hasQueueCapacity(); } interface ErrorHandler @@ -111,6 +114,7 @@ public class CQLMessageHandler extends AbstractMessageHandler MessageConsumer dispatcher, FrameEncoder.PayloadAllocator payloadAllocator, int queueCapacity, + QueueBackpressure queueBackpressure, ClientResourceLimits.ResourceProvider resources, OnHandlerClosed onClosed, ErrorHandler errorHandler, @@ -129,6 +133,7 @@ public class CQLMessageHandler extends AbstractMessageHandler this.messageDecoder = messageDecoder; this.payloadAllocator = payloadAllocator; this.dispatcher = dispatcher; + this.queueBackpressure = queueBackpressure; this.errorHandler = errorHandler; this.throwOnOverload = throwOnOverload; this.version = version; @@ -160,6 +165,7 @@ public class CQLMessageHandler extends AbstractMessageHandler * @throws ErrorMessage.WrappedException with an {@link OverloadedException} if overload occurs and the * connection is configured to throw on overload */ + @Override protected boolean processOneContainedMessage(ShareableBytes bytes, Limit endpointReserve, Limit globalReserve) { ByteBuffer buf = bytes.get(); @@ -178,7 +184,8 @@ public class CQLMessageHandler extends AbstractMessageHandler // max CQL message size defaults to 256mb, so should be safe to downcast int messageSize = Ints.checkedCast(header.bodySizeInBytes); - + + Overload backpressure = Overload.NONE; if (throwOnOverload) { if (!acquireCapacity(header, endpointReserve, globalReserve)) @@ -188,51 +195,61 @@ public class CQLMessageHandler extends AbstractMessageHandler } if (DatabaseDescriptor.getNativeTransportRateLimitingEnabled() && !requestRateLimiter.tryReserve()) + backpressure = Overload.REQUESTS; + else if (!dispatcher.hasQueueCapacity()) + backpressure = Overload.QUEUE_TIME; + + if (backpressure != Overload.NONE) { // We've already allocated against the bytes-in-flight limits, so release those resources. release(header); - discardAndThrow(endpointReserve, globalReserve, buf, header, messageSize, Overload.REQUESTS); + discardAndThrow(endpointReserve, globalReserve, buf, header, messageSize, backpressure); return true; } } else { - Overload backpressure = Overload.NONE; + long delay = -1; if (!acquireCapacityAndQueueOnFailure(header, endpointReserve, globalReserve)) - { - if (processRequestAndUpdateMetrics(bytes, header, messageSize, Overload.BYTES_IN_FLIGHT)) - { - if (decoder.isActive()) - ClientMetrics.instance.pauseConnection(); - } - backpressure = Overload.BYTES_IN_FLIGHT; - } - + + // Apply rate limiting, if enabled if (DatabaseDescriptor.getNativeTransportRateLimitingEnabled()) { // Reserve a permit even if we've already triggered backpressure on bytes in flight. - long delay = requestRateLimiter.reserveAndGetDelay(RATE_LIMITER_DELAY_UNIT); - - if (backpressure == Overload.NONE && delay > 0) - { - if (processRequestAndUpdateMetrics(bytes, header, messageSize, Overload.REQUESTS)) - { - if (decoder.isActive()) - ClientMetrics.instance.pauseConnection(); + delay = requestRateLimiter.reserveAndGetDelay(RATE_LIMITER_DELAY_UNIT); - // Schedule a wakup here if we process successfully. The connection should be closing otherwise. + if (backpressure == Overload.NONE && delay > 0) + backpressure = Overload.REQUESTS; + } + + // Check queue time, if enabled + if (backpressure == Overload.NONE && !dispatcher.hasQueueCapacity()) + { + delay = queueBackpressure.markAndGetDelay(RATE_LIMITER_DELAY_UNIT); + + if (delay > 0) + backpressure = Overload.QUEUE_TIME; + } + + if (backpressure != Overload.NONE) + { + if (processRequestAndUpdateMetrics(bytes, header, messageSize, backpressure)) + { + if (decoder.isActive()) + ClientMetrics.instance.pauseConnection(); + + if (delay > 0) + { + // Schedule a wakeup here if we process successfully. The connection should be closing otherwise. scheduleConnectionWakeupTask(delay, RATE_LIMITER_DELAY_UNIT); } - - backpressure = Overload.REQUESTS; } - } - - // If we triggered backpressure, make sure the caller stops processing frames after the request completes. - if (backpressure != Overload.NONE) + + // If we triggered backpressure, make sure the caller stops processing frames after the request completes. return false; + } } return processRequestAndUpdateMetrics(bytes, header, messageSize, Overload.NONE); @@ -252,7 +269,7 @@ public class CQLMessageHandler extends AbstractMessageHandler ClientMetrics.instance.markRequestDiscarded(); logOverload(endpointReserve, globalReserve, header, messageSize); - OverloadedException exception = buildOverloadedException(endpointReserve, globalReserve, overload); + OverloadedException exception = buildOverloadedException(endpointReserve, globalReserve, requestRateLimiter, overload); handleError(exception, header); // Don't stop processing incoming messages, as we rely on the client to apply @@ -262,14 +279,33 @@ public class CQLMessageHandler extends AbstractMessageHandler buf.position(buf.position() + Envelope.Header.LENGTH + messageSize); } - private OverloadedException buildOverloadedException(Limit endpointReserve, Limit globalReserve, Overload overload) { - return overload == Overload.REQUESTS - ? new OverloadedException(String.format("Request breached global limit of %d requests/second. Server is " + - "currently in an overloaded state and cannot accept more requests.", - requestRateLimiter.getRate())) - : new OverloadedException(String.format("Request breached limit on bytes in flight. (Endpoint: %d/%d bytes, Global: %d/%d bytes.) " + - "Server is currently in an overloaded state and cannot accept more requests.", - endpointReserve.using(), endpointReserve.limit(), globalReserve.using(), globalReserve.limit())); + public static OverloadedException buildOverloadedException(Limit endpointReserve, Limit globalReserve, NonBlockingRateLimiter requestRateLimiter, Overload overload) + { + return buildOverloadedException(() -> String.format("Endpoint: %d/%d bytes, Global: %d/%d bytes.", endpointReserve.using(), endpointReserve.limit(), + globalReserve.using(), globalReserve.limit()), + requestRateLimiter, + overload); + } + public static OverloadedException buildOverloadedException(Supplier endpointLimits, NonBlockingRateLimiter requestRateLimiter, Overload overload) + { + switch (overload) + { + case REQUESTS: + return new OverloadedException(String.format("Request breached global limit of %d requests/second. Server is " + + "currently in an overloaded state and cannot accept more requests.", + requestRateLimiter.getRate())); + case BYTES_IN_FLIGHT: + return new OverloadedException(String.format("Request breached limit on bytes in flight. (%s)" + + "Server is currently in an overloaded state and cannot accept more requests.", + endpointLimits.get())); + case QUEUE_TIME: + return new OverloadedException(String.format("Request has spent over %s time of the maximum timeout %dms in the queue", + DatabaseDescriptor.getNativeTransportQueueMaxItemAgeThreshold(), + DatabaseDescriptor.getNativeTransportTimeout(TimeUnit.MILLISECONDS))); + default: + throw new IllegalArgumentException("Overload exception should not have been thrown with " + overload); + + } } private void logOverload(Limit endpointReserve, Limit globalReserve, Envelope.Header header, int messageSize) @@ -348,7 +384,7 @@ public class CQLMessageHandler extends AbstractMessageHandler try { message = messageDecoder.decode(channel, request); - dispatcher.accept(channel, message, this::toFlushItem, backpressure); + dispatcher.dispatch(channel, message, this::toFlushItem, backpressure); // sucessfully delivered a CQL message to the execution // stage, so reset the counter of consecutive errors @@ -494,55 +530,79 @@ public class CQLMessageHandler extends AbstractMessageHandler LargeMessage largeMessage = new LargeMessage(header); - if (!acquireCapacity(header, endpointReserve, globalReserve)) + if (throwOnOverload) { - // In the case of large messages, never stop processing incoming frames - // as this will halt the client meaning no further frames will be sent, - // leading to starvation. - // If the throwOnOverload option is set, don't process the message once - // read, return an error response to notify the client that resource - // limits have been exceeded. If the option isn't set, the only thing we - // can do is to consume the subsequent frames and process the message. - // Large and small messages are never interleaved for a single client, so - // we know that this client will finish sending the large message before - // anything else. Other clients sending small messages concurrently will - // be backpressured by the global resource limits. The server is still - // vulnerable to overload by multiple clients sending large messages - // concurrently. - if (throwOnOverload) + if (!acquireCapacity(header, endpointReserve, globalReserve)) { + // In the case of large messages, never stop processing incoming frames + // as this will halt the client meaning no further frames will be sent, + // leading to starvation. + // If the throwOnOverload option is set, don't process the message once + // read, return an error response to notify the client that resource + // limits have been exceeded. If the option isn't set, the only thing we + // can do is to consume the subsequent frames and process the message. + // Large and small messages are never interleaved for a single client, so + // we know that this client will finish sending the large message before + // anything else. Other clients sending small messages concurrently will + // be backpressured by the global resource limits. The server is still + // vulnerable to overload by multiple clients sending large messages + // concurrently. + // Mark as overloaded so that discard the message after consuming any subsequent frames. ClientMetrics.instance.markRequestDiscarded(); logOverload(endpointReserve, globalReserve, header, messageSize); largeMessage.markOverloaded(Overload.BYTES_IN_FLIGHT); } - } - else if (DatabaseDescriptor.getNativeTransportRateLimitingEnabled()) - { - if (throwOnOverload) + else { - if (!requestRateLimiter.tryReserve()) + Overload backpressure = Overload.NONE; + if (DatabaseDescriptor.getNativeTransportRateLimitingEnabled() && !requestRateLimiter.tryReserve()) + backpressure = Overload.REQUESTS; + else if (!dispatcher.hasQueueCapacity()) + backpressure = Overload.QUEUE_TIME; + + if (backpressure != Overload.NONE) { ClientMetrics.instance.markRequestDiscarded(); logOverload(endpointReserve, globalReserve, header, messageSize); - + // Mark as overloaded so that we discard the message after consuming any subsequent frames. // (i.e. Request resources we may already have acquired above will be released.) - largeMessage.markOverloaded(Overload.REQUESTS); - + largeMessage.markOverloaded(backpressure); + this.largeMessage = largeMessage; largeMessage.supply(frame); return true; } } - else + } + else + { + if (acquireCapacity(header, endpointReserve, globalReserve)) { - long delay = requestRateLimiter.reserveAndGetDelay(RATE_LIMITER_DELAY_UNIT); + long delay = -1; + Overload backpressure = Overload.NONE; + + if (DatabaseDescriptor.getNativeTransportRateLimitingEnabled()) + { + delay = requestRateLimiter.reserveAndGetDelay(RATE_LIMITER_DELAY_UNIT); + + if (delay > 0) + backpressure = Overload.REQUESTS; + } + + if (backpressure == Overload.NONE && !dispatcher.hasQueueCapacity()) + { + delay = queueBackpressure.markAndGetDelay(RATE_LIMITER_DELAY_UNIT); + + if (delay > 0) + backpressure = Overload.QUEUE_TIME; + } if (delay > 0) { this.largeMessage = largeMessage; - largeMessage.markBackpressure(Overload.REQUESTS); + largeMessage.markBackpressure(backpressure); largeMessage.supply(frame); if (decoder.isActive()) @@ -553,7 +613,7 @@ public class CQLMessageHandler extends AbstractMessageHandler } } } - + this.largeMessage = largeMessage; largeMessage.supply(frame); return true; @@ -695,7 +755,7 @@ public class CQLMessageHandler extends AbstractMessageHandler protected void onComplete() { if (overload != Overload.NONE) - handleErrorAndRelease(buildOverloadedException(endpointReserveCapacity, globalReserveCapacity, overload), header); + handleErrorAndRelease(buildOverloadedException(endpointReserveCapacity, globalReserveCapacity, requestRateLimiter, overload), header); else if (!isCorrupt) processRequest(assembleFrame(), backpressure); } diff --git a/src/java/org/apache/cassandra/transport/ClientResourceLimits.java b/src/java/org/apache/cassandra/transport/ClientResourceLimits.java index e38cfdb812..4bc029c339 100644 --- a/src/java/org/apache/cassandra/transport/ClientResourceLimits.java +++ b/src/java/org/apache/cassandra/transport/ClientResourceLimits.java @@ -45,7 +45,7 @@ public class ClientResourceLimits public static final NonBlockingRateLimiter GLOBAL_REQUEST_LIMITER = new NonBlockingRateLimiter(getNativeTransportMaxRequestsPerSecond()); - public enum Overload { NONE, REQUESTS, BYTES_IN_FLIGHT } + public enum Overload { NONE, REQUESTS, BYTES_IN_FLIGHT, QUEUE_TIME } public static Allocator getAllocatorForEndpoint(InetAddress endpoint) { diff --git a/src/java/org/apache/cassandra/transport/Connection.java b/src/java/org/apache/cassandra/transport/Connection.java index cae10c2825..074b558a10 100644 --- a/src/java/org/apache/cassandra/transport/Connection.java +++ b/src/java/org/apache/cassandra/transport/Connection.java @@ -83,5 +83,6 @@ public class Connection public interface Tracker { void addConnection(Channel ch, Connection connection); + boolean isRunning(); } } diff --git a/src/java/org/apache/cassandra/transport/Dispatcher.java b/src/java/org/apache/cassandra/transport/Dispatcher.java index 38cb9326de..d6cb5e822f 100644 --- a/src/java/org/apache/cassandra/transport/Dispatcher.java +++ b/src/java/org/apache/cassandra/transport/Dispatcher.java @@ -31,9 +31,10 @@ import org.slf4j.LoggerFactory; import io.netty.channel.Channel; import io.netty.channel.EventLoop; import io.netty.util.AttributeKey; -import org.apache.cassandra.concurrent.DebuggableTask.RunnableDebuggableTask; +import org.apache.cassandra.concurrent.DebuggableTask; import org.apache.cassandra.concurrent.LocalAwareExecutorPlus; import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.exceptions.OverloadedException; import org.apache.cassandra.metrics.ClientMetrics; import org.apache.cassandra.net.FrameEncoder; import org.apache.cassandra.service.ClientWarn; @@ -44,12 +45,12 @@ import org.apache.cassandra.transport.Flusher.FlushItem; import org.apache.cassandra.transport.messages.ErrorMessage; import org.apache.cassandra.transport.messages.EventMessage; import org.apache.cassandra.utils.JVMStabilityInspector; +import org.apache.cassandra.utils.MonotonicClock; import org.apache.cassandra.utils.NoSpamLogger; import static org.apache.cassandra.concurrent.SharedExecutorPool.SHARED; -import static org.apache.cassandra.utils.Clock.Global.nanoTime; -public class Dispatcher +public class Dispatcher implements CQLMessageHandler.MessageConsumer { private static final Logger logger = LoggerFactory.getLogger(Dispatcher.class); @@ -99,8 +100,23 @@ public class Dispatcher this.useLegacyFlusher = useLegacyFlusher; } + @Override public void dispatch(Channel channel, Message.Request request, FlushItemConverter forFlusher, Overload backpressure) { + if (!request.connection().getTracker().isRunning()) + { + // We can not respond with a custom, transport, or server exceptions since, given current implementation of clients, + // they will defunct the connection. Without a protocol version bump that introduces an "I am going away message", + // we have to stick to an existing error code. + Message.Response response = ErrorMessage.fromException(new OverloadedException("Server is shutting down")); + response.setStreamId(request.getStreamId()); + response.setWarnings(ClientWarn.instance.getWarnings()); + response.attach(request.connection); + FlushItem toFlush = forFlusher.toFlushItem(channel, request, response); + flush(toFlush); + return; + } + // if native_transport_max_auth_threads is < 1, don't delegate to new pool on auth messages boolean isAuthQuery = DatabaseDescriptor.getNativeTransportMaxAuthThreads() > 0 && (request.type == Message.Type.AUTH_RESPONSE || request.type == Message.Type.CREDENTIALS); @@ -112,16 +128,170 @@ public class Dispatcher ClientMetrics.instance.markRequestDispatched(); } - public class RequestProcessor implements RunnableDebuggableTask + public static class RequestTime + { + private final long enqueuedAtNanos; + private final long startedAtNanos; + + public RequestTime(long createdAtNanos) + { + this(createdAtNanos, createdAtNanos); + } + + public RequestTime(long enqueuedAtNanos, long startedAtNanos) + { + this.enqueuedAtNanos = enqueuedAtNanos; + this.startedAtNanos = startedAtNanos; + } + + public static RequestTime forImmediateExecution() + { + return new RequestTime(MonotonicClock.Global.preciseTime.now()); + } + + public long startedAtNanos() + { + return startedAtNanos; + } + + public long enqueuedAtNanos() + { + return enqueuedAtNanos; + } + + /** + * Base time is used by timeouts, and can be set to either when the request was added to the queue, + * or when the processing has started, which is controlled by {@link DatabaseDescriptor#getCQLStartTime()} + * + * Since client read/write timeouts are usually aligned with server-side timeouts, it is desireable to use + * enqueue time as a base. However, since client removes the handler `readTimeoutMillis` (which is 12 seconds + * by default), the upper bound for any execution on the coordinator is 12 seconds (thanks to CASSANDRA-7392, + * any replica-side query is capped by the verb timeout), if REQUEST option is used. But even simply allowing + * such long timeouts also implicitly allows queues to grow large, since our queues are currently unbounded. + * + * Latency, however, is _always_ based on request processing time, since the amount of time that request spends + * in the queue is not a representative metric of replica performance. + */ + public long baseTimeNanos() + { + switch (DatabaseDescriptor.getCQLStartTime()) + { + case REQUEST: + return startedAtNanos(); + case QUEUE: + return enqueuedAtNanos(); + default: + throw new IllegalArgumentException("Unknown start time: " + DatabaseDescriptor.getCQLStartTime()); + } + } + + /** + * Given the current time and a base timeout for the verb return a request's expiration deadline, + * the time at which it becomes eligible for load shedding. + * The two factors to consider are the per-verb and client timeouts. Both are calculated by subtracting the + * time already elapsed during the lifetime of the request from some base value. + * + * When deriving verb timeout, two alternative semantics are available. This timeout may represent either: + * * the total time available for a coordinator to process a client request and return its response + * * a time bound for a coordinator to send internode requests and gather responses from replicas + * + * The point from which elapsed time is measured here is configurable to accommodate these two different + * options. For the former, the clock starts when a client request is received and enqueued by the coordinator. + * For the latter, it starts when the request is dequeued by the coordinator and processing is started. + * See {@link #baseTimeNanos()} for details. + * + * The client timeout represents how long the sender of a request is prepared to wait for a response. By + * implication, after this window has passed any further work done on the server side is wasted effort. Ideally, + * the base for this timeout would be set on a per-request basis but as this not currently supported in the + * protocol, it is configured uniformly for all requests. See {@link DatabaseDescriptor#getNativeTransportTimeout}. + * For this calculation, elapsed time is always measured from the point when a request is received and enqueued. + * + * Where verb timeout is based on queue admission, deadline computation is straightforward. The expiration + * deadline is simply the current time plus the smaller of the verb and client timeouts. + * + * However, if verb timeout is based on when the request is dequeued, the implications are more nuanced. + * In this scenario, while there may still be "headroom" available within the verb timeout, using it could + * exceed the client timeout (which is always based on admission time). + * + * For example: + * + * * Client timeout base is 10 (cb), verb timeout base is 5 (vb) + * * Request is enqueued at t1 (e) + * * Request is dequeued at t8 (d) + * * computeDeadline is called at t9 (n) + * + * If verb timeout is based on dequeuing, there would still some time remaining before a verb-based deadline. + * elapsed = (n - d) ; 1 + * timeout = (vb - elapsed) ; 4 + * deadline = (n + timeout) ; t13 + * ostensibly, the coordinator has until t13 to complete processing + * + * But as client timeout is measured from admission time, the request may exceeded the maximum wait period for + * the client sooner. + * elapsed = (n - e) ; 8 + * timeout = (cb - elapsed) ; 2 + * deadline = (n + timeout) ; t11 + * So the coordinator actually only has until t11 to complete processing, beyond then the client will not accept + * any response. + * + * @param verbExpiresAfterNanos the base timeout value for the verb being executed + * @return the point in time after which no further processing should occur + */ + public long computeDeadline(long verbExpiresAfterNanos) + { + long clientDeadline = clientDeadline(); + + long verbDeadline = baseTimeNanos() + verbExpiresAfterNanos; + // Whichever one is closer + return Math.min(verbDeadline, clientDeadline); + } + + public long computeTimeout(long now, long verbExpiresAfterNanos) + { + return computeDeadline(verbExpiresAfterNanos) - now; + } + + /** + * No request should survive native request deadline, but in order to err on the side of caution, we have this + * swtich that allows hints to be submitted to mutation stage when cluster is potentially overloaded. Allowing + * hints to be not bound by deadline can exacerbate overload, but since there are also correctness implications, + * this seemed like a reasonable configuration option. + */ + public boolean shouldSendHints() + { + if (!DatabaseDescriptor.getEnforceNativeDeadlineForHints()) + return true; + + long now = MonotonicClock.Global.preciseTime.now(); + long clientDeadline = clientDeadline(); + return now < clientDeadline; + } + + public long clientDeadline() + { + return enqueuedAtNanos() + DatabaseDescriptor.getNativeTransportTimeout(TimeUnit.NANOSECONDS); + } + + public long timeSpentInQueueNanos() + { + return startedAtNanos - enqueuedAtNanos; + } + } + + /** + * It is important to keep this class an instance of {@link DebuggableTask}, either runnable or callable since this + * is the only way we can keep it not wrapped into a callable on SEPExecutor submission path. And we need this + * functionality for tracking time purposes. + */ + public class RequestProcessor implements DebuggableTask.RunnableDebuggableTask { private final Channel channel; private final Message.Request request; private final FlushItemConverter forFlusher; private final Overload backpressure; - - private final long creationTimeNanos = nanoTime(); + private volatile long startTimeNanos; - + public RequestProcessor(Channel channel, Message.Request request, FlushItemConverter forFlusher, Overload backpressure) { this.channel = channel; @@ -133,14 +303,14 @@ public class Dispatcher @Override public void run() { - startTimeNanos = nanoTime(); - processRequest(channel, request, forFlusher, backpressure, startTimeNanos); + startTimeNanos = MonotonicClock.Global.preciseTime.now(); + processRequest(channel, request, forFlusher, backpressure, new RequestTime(request.createdAtNanos, startTimeNanos)); } @Override public long creationTimeNanos() { - return creationTimeNanos; + return request.createdAtNanos; } @Override @@ -154,14 +324,49 @@ public class Dispatcher { return request.toString(); } + + @Override + public String toString() + { + return "RequestProcessor{" + + "request=" + request + + ", approxStartTimeNanos=" + startTimeNanos + + '}'; + } + } + + /** + * Checks if the item in the head of the queue has spent more than allowed time in the queue. + */ + @Override + public boolean hasQueueCapacity() + { + double threshold = DatabaseDescriptor.getNativeTransportQueueMaxItemAgeThreshold(); + if (threshold <= 0) + return true; + + return requestExecutor.oldestTaskQueueTime() < (DatabaseDescriptor.getNativeTransportTimeout(TimeUnit.NANOSECONDS) * threshold); } /** * Note: this method may be executed on the netty event loop, during initial protocol negotiation; the caller is * responsible for cleaning up any global or thread-local state. (ex. tracing, client warnings, etc.). */ - private static Message.Response processRequest(ServerConnection connection, Message.Request request, Overload backpressure, long startTimeNanos) + private static Message.Response processRequest(ServerConnection connection, Message.Request request, Overload backpressure, RequestTime requestTime) { + long queueTime = requestTime.timeSpentInQueueNanos(); + + // If we have already crossed the max timeout for all possible RPCs, we time out the query immediately. + // We do not differentiate between query types here, since if we got into a situation when, say, we have a PREPARE + // query that is stuck behind the EXECUTE query, we would rather time it out and catch up with a backlog, expecting + // that the bursts are going to be short-lived. + ClientMetrics.instance.queueTime(queueTime, TimeUnit.NANOSECONDS); + if (queueTime > DatabaseDescriptor.getNativeTransportTimeout(TimeUnit.NANOSECONDS)) + { + ClientMetrics.instance.markTimedOutBeforeProcessing(); + return ErrorMessage.fromException(new OverloadedException("Query timed out before it could start")); + } + if (connection.getVersion().isGreaterOrEqualTo(ProtocolVersion.V4)) ClientWarn.instance.captureWarnings(); @@ -170,28 +375,45 @@ public class Dispatcher if (request.isTrackable()) CoordinatorWarnings.init(); - if (backpressure == Overload.REQUESTS) + switch (backpressure) { - String message = String.format("Request breached global limit of %d requests/second and triggered backpressure.", - ClientResourceLimits.getNativeTransportMaxRequestsPerSecond()); + case NONE: + break; + case REQUESTS: + { + String message = String.format("Request breached global limit of %d requests/second and triggered backpressure.", + ClientResourceLimits.getNativeTransportMaxRequestsPerSecond()); - NoSpamLogger.log(logger, NoSpamLogger.Level.INFO, 1, TimeUnit.MINUTES, message); - ClientWarn.instance.warn(message); - } - else if (backpressure == Overload.BYTES_IN_FLIGHT) - { - String message = String.format("Request breached limit(s) on bytes in flight (Endpoint: %d, Global: %d) and triggered backpressure.", - ClientResourceLimits.getEndpointLimit(), ClientResourceLimits.getGlobalLimit()); + NoSpamLogger.log(logger, NoSpamLogger.Level.INFO, 1, TimeUnit.MINUTES, message); + ClientWarn.instance.warn(message); + break; + } + case BYTES_IN_FLIGHT: + { + String message = String.format("Request breached limit(s) on bytes in flight (Endpoint: %d, Global: %d) and triggered backpressure.", + ClientResourceLimits.getEndpointLimit(), ClientResourceLimits.getGlobalLimit()); - NoSpamLogger.log(logger, NoSpamLogger.Level.INFO, 1, TimeUnit.MINUTES, message); - ClientWarn.instance.warn(message); + NoSpamLogger.log(logger, NoSpamLogger.Level.INFO, 1, TimeUnit.MINUTES, message); + ClientWarn.instance.warn(message); + break; + } + case QUEUE_TIME: + { + String message = String.format("Request has spent over %s time of the maximum timeout %dms in the queue", + DatabaseDescriptor.getNativeTransportQueueMaxItemAgeThreshold(), + DatabaseDescriptor.getNativeTransportTimeout(TimeUnit.MILLISECONDS)); + + NoSpamLogger.log(logger, NoSpamLogger.Level.INFO, 1, TimeUnit.MINUTES, message); + ClientWarn.instance.warn(message); + break; + } } QueryState qstate = connection.validateNewMessage(request.type, connection.getVersion()); Message.logger.trace("Received: {}, v={}", request, connection.getVersion()); connection.requests.inc(); - Message.Response response = request.execute(qstate, startTimeNanos); + Message.Response response = request.execute(qstate, requestTime); if (request.isTrackable()) CoordinatorWarnings.done(); @@ -206,11 +428,11 @@ public class Dispatcher /** * Note: this method may be executed on the netty event loop. */ - static Message.Response processRequest(Channel channel, Message.Request request, Overload backpressure, long startTimeNanos) + static Message.Response processRequest(Channel channel, Message.Request request, Overload backpressure, RequestTime requestTime) { try { - return processRequest((ServerConnection) request.connection(), request, backpressure, startTimeNanos); + return processRequest((ServerConnection) request.connection(), request, backpressure, requestTime); } catch (Throwable t) { @@ -235,9 +457,9 @@ public class Dispatcher /** * Note: this method is not expected to execute on the netty event loop. */ - void processRequest(Channel channel, Message.Request request, FlushItemConverter forFlusher, Overload backpressure, long startTimeNanos) + void processRequest(Channel channel, Message.Request request, FlushItemConverter forFlusher, Overload backpressure, RequestTime requestTime) { - Message.Response response = processRequest(channel, request, backpressure, startTimeNanos); + Message.Response response = processRequest(channel, request, backpressure, requestTime); FlushItem toFlush = forFlusher.toFlushItem(channel, request, response); Message.logger.trace("Responding: {}, v={}", response, request.connection().getVersion()); flush(toFlush); @@ -259,6 +481,11 @@ public class Dispatcher flusher.start(); } + public boolean isDone() + { + return requestExecutor.getPendingTaskCount() == 0 && requestExecutor.getActiveTaskCount() == 0; + } + public static void shutdown() { requestExecutor.shutdown(); @@ -270,7 +497,7 @@ public class Dispatcher * for delivering events to registered clients is dependent on protocol version and the configuration * of the pipeline. For v5 and newer connections, the event message is encoded into an Envelope, * wrapped in a FlushItem and then delivered via the pipeline's flusher, in a similar way to - * a Response returned from {@link #processRequest(Channel, Message.Request, FlushItemConverter, Overload, long)}. + * a Response returned from {@link #processRequest(Channel, Message.Request, FlushItemConverter, Overload, RequestTime)}. * It's worth noting that events are not generally fired as a direct response to a client request, * so this flush item has a null request attribute. The dispatcher itself is created when the * pipeline is first configured during protocol negotiation and is attached to the channel for diff --git a/src/java/org/apache/cassandra/transport/InitialConnectionHandler.java b/src/java/org/apache/cassandra/transport/InitialConnectionHandler.java index e4cff99acb..576af3e6dc 100644 --- a/src/java/org/apache/cassandra/transport/InitialConnectionHandler.java +++ b/src/java/org/apache/cassandra/transport/InitialConnectionHandler.java @@ -26,7 +26,6 @@ import java.util.List; import java.util.Map; import org.apache.cassandra.transport.ClientResourceLimits.Overload; -import org.apache.cassandra.utils.MonotonicClock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -149,8 +148,7 @@ public class InitialConnectionHandler extends ByteToMessageDecoder promise = new VoidChannelPromise(ctx.channel(), false); } - long approxStartTimeNanos = MonotonicClock.Global.approxTime.now(); - final Message.Response response = Dispatcher.processRequest(ctx.channel(), startup, Overload.NONE, approxStartTimeNanos); + final Message.Response response = Dispatcher.processRequest(ctx.channel(), startup, Overload.NONE, Dispatcher.RequestTime.forImmediateExecution()); outbound = response.encode(inbound.header.version); ctx.writeAndFlush(outbound, promise); diff --git a/src/java/org/apache/cassandra/transport/Message.java b/src/java/org/apache/cassandra/transport/Message.java index 82015c1150..ed853c0cbd 100644 --- a/src/java/org/apache/cassandra/transport/Message.java +++ b/src/java/org/apache/cassandra/transport/Message.java @@ -36,6 +36,7 @@ import org.apache.cassandra.service.StorageService; import org.apache.cassandra.tracing.Tracing; import org.apache.cassandra.transport.messages.*; import org.apache.cassandra.service.QueryState; +import org.apache.cassandra.utils.MonotonicClock; import org.apache.cassandra.utils.ReflectionUtils; import org.apache.cassandra.utils.TimeUUID; @@ -203,11 +204,13 @@ public abstract class Message public static abstract class Request extends Message { private boolean tracingRequested; - + public final long createdAtNanos; protected Request(Type type) { super(type); + createdAtNanos = MonotonicClock.Global.preciseTime.now(); + if (type.direction != Direction.REQUEST) throw new IllegalArgumentException(); } @@ -228,9 +231,9 @@ public abstract class Message return false; } - protected abstract Response execute(QueryState queryState, long queryStartNanoTime, boolean traceRequest); + protected abstract Response execute(QueryState queryState, Dispatcher.RequestTime requestTime, boolean traceRequest); - public final Response execute(QueryState queryState, long queryStartNanoTime) + public final Response execute(QueryState queryState, Dispatcher.RequestTime requestTime) { boolean shouldTrace = false; TimeUUID tracingSessionId = null; @@ -253,7 +256,7 @@ public abstract class Message Response response; try { - response = execute(queryState, queryStartNanoTime, shouldTrace); + response = execute(queryState, requestTime, shouldTrace); } finally { @@ -276,6 +279,15 @@ public abstract class Message { return tracingRequested; } + + @Override + public String toString() + { + return "Request{" + + "tracingRequested=" + tracingRequested + + ", createdAtNanos=" + createdAtNanos + + '}'; + } } public static abstract class Response extends Message diff --git a/src/java/org/apache/cassandra/transport/PipelineConfigurator.java b/src/java/org/apache/cassandra/transport/PipelineConfigurator.java index d19af211e2..864241a663 100644 --- a/src/java/org/apache/cassandra/transport/PipelineConfigurator.java +++ b/src/java/org/apache/cassandra/transport/PipelineConfigurator.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Strings; import org.slf4j.Logger; @@ -97,16 +98,32 @@ public class PipelineConfigurator private final boolean keepAlive; private final EncryptionOptions.TlsEncryptionPolicy tlsEncryptionPolicy; private final Dispatcher dispatcher; + // Shared between pre-v5 and CQLMessage handlers + private final QueueBackpressure queueBackpressure; public PipelineConfigurator(boolean epoll, boolean keepAlive, - boolean legacyFlusher, + EncryptionOptions.TlsEncryptionPolicy encryptionPolicy, + Dispatcher dispatcher) + { + this.epoll = epoll; + this.keepAlive = keepAlive; + this.tlsEncryptionPolicy = encryptionPolicy; + this.dispatcher = dispatcher; + this.queueBackpressure = QueueBackpressure.DEFAULT; + } + + @VisibleForTesting + public PipelineConfigurator(boolean epoll, + boolean keepAlive, + boolean useLegacyFlusher, EncryptionOptions.TlsEncryptionPolicy encryptionPolicy) { this.epoll = epoll; this.keepAlive = keepAlive; this.tlsEncryptionPolicy = encryptionPolicy; - this.dispatcher = dispatcher(legacyFlusher); + this.dispatcher = new Dispatcher(useLegacyFlusher); + this.queueBackpressure = QueueBackpressure.DEFAULT; } public ChannelFuture initializeChannel(final EventLoopGroup workerGroup, @@ -288,7 +305,12 @@ public class PipelineConfigurator int queueCapacity = DatabaseDescriptor.getNativeTransportReceiveQueueCapacityInBytes(); ClientResourceLimits.ResourceProvider resourceProvider = resourceProvider(resourceAllocator); AbstractMessageHandler.OnHandlerClosed onClosed = handler -> resourceProvider.release(); - boolean throwOnOverload = "1".equals(options.get(StartupMessage.THROW_ON_OVERLOAD)); + String fromOptions = options.get(StartupMessage.THROW_ON_OVERLOAD); + boolean throwOnOverload; + if (fromOptions == null) + throwOnOverload = DatabaseDescriptor.getNativeTransportThrowOnOverload(); + else + throwOnOverload = "1".equals(fromOptions); CQLMessageHandler.MessageConsumer messageConsumer = messageConsumer(); CQLMessageHandler processor = @@ -300,6 +322,7 @@ public class PipelineConfigurator messageConsumer, payloadAllocator, queueCapacity, + queueBackpressure, resourceProvider, onClosed, errorHandler, @@ -334,7 +357,7 @@ public class PipelineConfigurator protected CQLMessageHandler.MessageConsumer messageConsumer() { - return dispatcher::dispatch; + return dispatcher; } protected Message.Decoder messageDecoder() @@ -368,7 +391,7 @@ public class PipelineConfigurator pipeline.addBefore(INITIAL_HANDLER, MESSAGE_COMPRESSOR, Envelope.Compressor.instance); pipeline.addBefore(INITIAL_HANDLER, MESSAGE_DECODER, PreV5Handlers.ProtocolDecoder.instance); pipeline.addBefore(INITIAL_HANDLER, MESSAGE_ENCODER, PreV5Handlers.ProtocolEncoder.instance); - pipeline.addBefore(INITIAL_HANDLER, LEGACY_MESSAGE_PROCESSOR, new PreV5Handlers.LegacyDispatchHandler(dispatcher, limits)); + pipeline.addBefore(INITIAL_HANDLER, LEGACY_MESSAGE_PROCESSOR, new PreV5Handlers.LegacyDispatchHandler(dispatcher, queueBackpressure, limits)); pipeline.remove(INITIAL_HANDLER); onNegotiationComplete(pipeline); } diff --git a/src/java/org/apache/cassandra/transport/PreV5Handlers.java b/src/java/org/apache/cassandra/transport/PreV5Handlers.java index ea850cc992..3b98f41b87 100644 --- a/src/java/org/apache/cassandra/transport/PreV5Handlers.java +++ b/src/java/org/apache/cassandra/transport/PreV5Handlers.java @@ -21,9 +21,6 @@ package org.apache.cassandra.transport; import java.util.List; import com.google.common.base.Predicate; - -import org.apache.cassandra.config.DatabaseDescriptor; -import org.apache.cassandra.transport.ClientResourceLimits.Overload; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,9 +34,11 @@ import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.MessageToMessageDecoder; import io.netty.handler.codec.MessageToMessageEncoder; +import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.exceptions.OverloadedException; import org.apache.cassandra.metrics.ClientMetrics; import org.apache.cassandra.net.ResourceLimits; +import org.apache.cassandra.transport.ClientResourceLimits.Overload; import org.apache.cassandra.transport.messages.ErrorMessage; import org.apache.cassandra.utils.JVMStabilityInspector; @@ -59,6 +58,8 @@ public class PreV5Handlers private final Dispatcher dispatcher; private final ClientResourceLimits.Allocator endpointPayloadTracker; + private final QueueBackpressure queueBackpressure; + /** * Current count of *request* bytes that are live on the channel. *

@@ -69,9 +70,10 @@ public class PreV5Handlers /** The cause of the current connection pause, or {@link Overload#NONE} if it is unpaused. */ private Overload backpressure = Overload.NONE; - LegacyDispatchHandler(Dispatcher dispatcher, ClientResourceLimits.Allocator endpointPayloadTracker) + LegacyDispatchHandler(Dispatcher dispatcher, QueueBackpressure queueBackpressure, ClientResourceLimits.Allocator endpointPayloadTracker) { this.dispatcher = dispatcher; + this.queueBackpressure = queueBackpressure; this.endpointPayloadTracker = endpointPayloadTracker; } @@ -152,12 +154,19 @@ public class PreV5Handlers discardAndThrow(request, requestSize, Overload.BYTES_IN_FLIGHT); } + Overload backpressure = Overload.NONE; if (DatabaseDescriptor.getNativeTransportRateLimitingEnabled() && !GLOBAL_REQUEST_LIMITER.tryReserve()) + backpressure = Overload.REQUESTS; + else if (!dispatcher.hasQueueCapacity()) + backpressure = Overload.QUEUE_TIME; + + if (backpressure != Overload.NONE) { // We've already allocated against the payload tracker here, so release those resources. endpointPayloadTracker.release(requestSize); - discardAndThrow(request, requestSize, Overload.REQUESTS); + discardAndThrow(request, requestSize, backpressure); } + } else { @@ -172,20 +181,33 @@ public class PreV5Handlers backpressure = Overload.BYTES_IN_FLIGHT; } + long delay = -1; + if (DatabaseDescriptor.getNativeTransportRateLimitingEnabled()) { // Reserve a permit even if we've already triggered backpressure on bytes in flight. - long delay = GLOBAL_REQUEST_LIMITER.reserveAndGetDelay(RATE_LIMITER_DELAY_UNIT); + delay = GLOBAL_REQUEST_LIMITER.reserveAndGetDelay(RATE_LIMITER_DELAY_UNIT); // If we've already triggered backpressure on bytes in flight, no further action is necessary. if (backpressure == Overload.NONE && delay > 0) - { - pauseConnection(ctx); - - // A permit isn't immediately available, so schedule an unpause for when it is. - ctx.channel().eventLoop().schedule(() -> unpauseConnection(ctx.channel().config()), delay, RATE_LIMITER_DELAY_UNIT); backpressure = Overload.REQUESTS; - } + } + + if (backpressure == Overload.NONE && !dispatcher.hasQueueCapacity()) + { + delay = queueBackpressure.markAndGetDelay(RATE_LIMITER_DELAY_UNIT); + + if (delay > 0) + backpressure = Overload.QUEUE_TIME; + } + + if (delay > 0) + { + assert backpressure == Overload.REQUESTS || backpressure == Overload.QUEUE_TIME : backpressure; + pauseConnection(ctx); + + // A permit isn't immediately available, so schedule an unpause for when it is. + ctx.channel().eventLoop().schedule(() -> unpauseConnection(ctx.channel().config()), delay, RATE_LIMITER_DELAY_UNIT); } } } @@ -219,15 +241,9 @@ public class PreV5Handlers requestSize, channelPayloadBytesInFlight, endpointPayloadTracker, GLOBAL_REQUEST_LIMITER, request); - OverloadedException exception = overload == Overload.REQUESTS - ? new OverloadedException(String.format("Request breached global limit of %d requests/second. Server is " + - "currently in an overloaded state and cannot accept more requests.", - GLOBAL_REQUEST_LIMITER.getRate())) - : new OverloadedException(String.format("Request breached limit on bytes in flight. (%s)) " + - "Server is currently in an overloaded state and cannot accept more requests.", - - endpointPayloadTracker)); - + OverloadedException exception = CQLMessageHandler.buildOverloadedException(endpointPayloadTracker::toString, + GLOBAL_REQUEST_LIMITER, + overload); throw ErrorMessage.wrap(exception, request.getSource().header.streamId); } diff --git a/src/java/org/apache/cassandra/transport/QueueBackpressure.java b/src/java/org/apache/cassandra/transport/QueueBackpressure.java new file mode 100644 index 0000000000..09c9e95b75 --- /dev/null +++ b/src/java/org/apache/cassandra/transport/QueueBackpressure.java @@ -0,0 +1,146 @@ +/* + * 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.transport; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.LongSupplier; + +import com.google.common.annotations.VisibleForTesting; + +import org.apache.cassandra.config.DatabaseDescriptor; + +import static org.apache.cassandra.utils.MonotonicClock.Global.preciseTime; + +/** + * Native Queue Backpressure mechanism. If the queue fills up above {@link DatabaseDescriptor#getNativeTransportQueueMaxItemAgeThreshold()}. + * In other words, request has been sitting more than a % of {@link DatabaseDescriptor#getNativeTransportQueueMaxItemAgeThreshold()} in the queue, + * we start an incident. + *

+ * Incident starts by marking the incident time and raising severity level to 1. Each time we observe an old item in the head of + * the queue, we first bump then number of times we have applied the backpressure. After bumping it 10 times, we increase severity level by 1. + *

+ * Backpressure delay applied to the client socket is computed by multiplying the severity level by the minimum delay. + *

+ * If we have not seen old requests in the head of the queue for 1 second, we close the incident. + *

+ * If the queue remains saturated for a prolonged period (meaning {@link Dispatcher#hasQueueCapacity} returns false), the amount of delay + * will increase in proportion to the request rate as appliedTimes & severityLevel are incremented. If no new requests are considered + * overloaded in this way for a second, the incident will be reset and so the delay will drop back down to {@link Incident#minDelayNanos()}. + */ +public interface QueueBackpressure +{ + QueueBackpressure NO_OP = timeUnit -> 0; + + QueueBackpressure DEFAULT = new QueueBackpressure() + { + private final AtomicReference state = new AtomicReference<>(noBackpressure(() -> DatabaseDescriptor.getNativeTransportMinBackoffOnQueueOverload(TimeUnit.NANOSECONDS), + () -> DatabaseDescriptor.getNativeTransportMaxBackoffOnQueueOverload(TimeUnit.NANOSECONDS))); + + public long markAndGetDelay(TimeUnit timeUnit) + { + return state.updateAndGet(Incident::mark).delay(timeUnit); + } + }; + + long markAndGetDelay(TimeUnit timeUnit); + + static Incident noBackpressure(LongSupplier minDelayNanos, LongSupplier maxDelayNanos) + { + return new Incident(minDelayNanos, maxDelayNanos, + -1, 0, 0); + } + + @VisibleForTesting + class Incident + { + private final long appliedAt; + + private final int severityLevel; + private final int appliedTimes; + + private final LongSupplier minDelayNanos; + private final LongSupplier maxDelayNanos; + + @VisibleForTesting + public Incident(LongSupplier minDelayNanos, LongSupplier maxDelayNanos, long appliedAt, int severityLevel, int appliedTimes) + { + this.minDelayNanos = minDelayNanos; + this.maxDelayNanos = maxDelayNanos; + this.appliedAt = appliedAt; + this.severityLevel = severityLevel; + this.appliedTimes = appliedTimes; + } + + public Incident mark() + { + return mark(preciseTime.now()); + } + + @VisibleForTesting + public Incident mark(long now) + { + // Last time we have applied backpressure was over a second ago, consider this a new incident + if (appliedAt > 0 && now - appliedAt >= TimeUnit.SECONDS.toNanos(1)) + { + return new Incident(minDelayNanos, maxDelayNanos, now, 1, 1); + } + // Continuing incident: apply backpressure but do not bump severity level yet + else if (appliedTimes < 10) + { + return new Incident(minDelayNanos, maxDelayNanos, now, severityLevel == 0 ? 1 : severityLevel, appliedTimes + 1); + } + // + else + { + return new Incident(minDelayNanos, maxDelayNanos, now, severityLevel + 1, 1); + } + } + + public long appliedAt() + { + return appliedAt; + } + + public long delay(TimeUnit timeUnit) + { + return timeUnit.convert(Math.min(maxDelayNanos(), severityLevel * minDelayNanos()), TimeUnit.NANOSECONDS); + } + + public long minDelayNanos() + { + return minDelayNanos.getAsLong(); + } + + public long maxDelayNanos() + { + return maxDelayNanos.getAsLong(); + } + + public String toString() + { + return "QueueBackpressure{" + + "appliedAgo=" + (appliedAt == -1 ? "never" : TimeUnit.NANOSECONDS.toMillis(preciseTime.now() - appliedAt)) + + ", severityLevel=" + severityLevel + + ", appliedTimes=" + appliedTimes + + ", currentDelay=" + delay(TimeUnit.MILLISECONDS) + "ms" + + '}'; + } + } +} \ No newline at end of file diff --git a/src/java/org/apache/cassandra/transport/Server.java b/src/java/org/apache/cassandra/transport/Server.java index 5ec6d20187..6b1b84d043 100644 --- a/src/java/org/apache/cassandra/transport/Server.java +++ b/src/java/org/apache/cassandra/transport/Server.java @@ -22,7 +22,10 @@ import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.LockSupport; +import java.util.function.BooleanSupplier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -64,7 +67,7 @@ public class Server implements CassandraDaemon.Server private static final Logger logger = LoggerFactory.getLogger(Server.class); private static final boolean useEpoll = NativeTransportService.useEpoll(); - private final ConnectionTracker connectionTracker = new ConnectionTracker(); + private final ConnectionTracker connectionTracker; private final Connection.Factory connectionFactory = new Connection.Factory() { @@ -79,7 +82,7 @@ public class Server implements CassandraDaemon.Server private final AtomicBoolean isRunning = new AtomicBoolean(false); private final PipelineConfigurator pipelineConfigurator; private final EventLoopGroup workerGroup; - + private final Dispatcher dispatcher; private Server (Builder builder) { this.socket = builder.getSocket(); @@ -96,14 +99,16 @@ public class Server implements CassandraDaemon.Server workerGroup = new NioEventLoopGroup(); } + dispatcher = new Dispatcher(DatabaseDescriptor.useNativeTransportLegacyFlusher()); pipelineConfigurator = builder.pipelineConfigurator != null ? builder.pipelineConfigurator : new PipelineConfigurator(useEpoll, DatabaseDescriptor.getRpcKeepAlive(), - DatabaseDescriptor.useNativeTransportLegacyFlusher(), - builder.tlsEncryptionPolicy); + builder.tlsEncryptionPolicy, + dispatcher); EventNotifier notifier = builder.eventNotifier != null ? builder.eventNotifier : new EventNotifier(); + connectionTracker = new ConnectionTracker(isRunning::get); notifier.registerConnectionTracker(connectionTracker); StorageService.instance.register(notifier); Schema.instance.registerListener(notifier); @@ -111,8 +116,13 @@ public class Server implements CassandraDaemon.Server public void stop() { - if (isRunning.compareAndSet(true, false)) - close(); + stop(false); + } + + public void stop(boolean force) + { + if (isRunning.compareAndSet(true, false)) + close(force); } public boolean isRunning() @@ -168,8 +178,15 @@ public class Server implements CassandraDaemon.Server connectionTracker.protocolVersionTracker.clear(); } - private void close() + + private void close(boolean force) { + if (!force) + { + while (!dispatcher.isDone()) + LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(100)); + } + // Close opened connections connectionTracker.closeAll(); @@ -257,11 +274,13 @@ public class Server implements CassandraDaemon.Server public final ChannelGroup allChannels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); private final EnumMap groups = new EnumMap<>(Event.Type.class); private final ProtocolVersionTracker protocolVersionTracker = new ProtocolVersionTracker(); + private final BooleanSupplier isRunning; - public ConnectionTracker() + private ConnectionTracker(BooleanSupplier isRunning) { for (Event.Type type : Event.Type.values()) groups.put(type, new DefaultChannelGroup(type.toString(), GlobalEventExecutor.INSTANCE)); + this.isRunning = isRunning; } public void addConnection(Channel ch, Connection connection) @@ -272,6 +291,11 @@ public class Server implements CassandraDaemon.Server protocolVersionTracker.addConnection(((InetSocketAddress) ch.remoteAddress()).getAddress(), connection.getVersion()); } + public boolean isRunning() + { + return isRunning.getAsBoolean(); + } + public void register(Event.Type type, Channel ch) { groups.get(type).add(ch); @@ -293,7 +317,7 @@ public class Server implements CassandraDaemon.Server void closeAll() { - allChannels.close().awaitUninterruptibly(); + allChannels.flush().close().awaitUninterruptibly(); } int countConnectedClients() diff --git a/src/java/org/apache/cassandra/transport/SimpleClient.java b/src/java/org/apache/cassandra/transport/SimpleClient.java index 726e161ff5..c69814c6bc 100644 --- a/src/java/org/apache/cassandra/transport/SimpleClient.java +++ b/src/java/org/apache/cassandra/transport/SimpleClient.java @@ -367,6 +367,12 @@ public class SimpleClient implements Closeable private static class ConnectionTracker implements Connection.Tracker { public void addConnection(Channel ch, Connection connection) {} + + @Override + public boolean isRunning() + { + return true; + } } private static class HandlerNames @@ -447,8 +453,17 @@ public class SimpleClient implements Closeable FrameEncoder frameEncoder = frameEncoder(ctx); FrameEncoder.PayloadAllocator payloadAllocator = frameEncoder.allocator(); - CQLMessageHandler.MessageConsumer responseConsumer = (c, message, converter, backpressured) -> { - responseHandler.handleResponse(c, message); + CQLMessageHandler.MessageConsumer responseConsumer = new CQLMessageHandler.MessageConsumer() + { + public void dispatch(Channel channel, Message.Response message, Dispatcher.FlushItemConverter toFlushItem, Overload backpressure) + { + responseHandler.handleResponse(channel, message); + } + + public boolean hasQueueCapacity() + { + return true; + } }; CQLMessageHandler.ErrorHandler errorHandler = (error) -> { @@ -503,6 +518,7 @@ public class SimpleClient implements Closeable responseConsumer, payloadAllocator, queueCapacity, + QueueBackpressure.NO_OP, resources, handler -> {}, errorHandler, @@ -541,7 +557,7 @@ public class SimpleClient implements Closeable pipeline.remove(this); Message.Response message = messageDecoder.decode(ctx.channel(), response); - responseConsumer.accept(channel, message, (ch, req, resp) -> null, Overload.NONE); + responseConsumer.dispatch(channel, message, (ch, req, resp) -> null, Overload.NONE); } private FrameDecoder frameDecoder(ChannelHandlerContext ctx, BufferPoolAllocator allocator) diff --git a/src/java/org/apache/cassandra/transport/messages/AuthResponse.java b/src/java/org/apache/cassandra/transport/messages/AuthResponse.java index 81002c8281..16247ed6c8 100644 --- a/src/java/org/apache/cassandra/transport/messages/AuthResponse.java +++ b/src/java/org/apache/cassandra/transport/messages/AuthResponse.java @@ -69,7 +69,7 @@ public class AuthResponse extends Message.Request } @Override - protected Response execute(QueryState queryState, long queryStartNanoTime, boolean traceRequest) + protected Response execute(QueryState queryState, Dispatcher.RequestTime requestTime, boolean traceRequest) { try { diff --git a/src/java/org/apache/cassandra/transport/messages/BatchMessage.java b/src/java/org/apache/cassandra/transport/messages/BatchMessage.java index de575ce045..8a87efc781 100644 --- a/src/java/org/apache/cassandra/transport/messages/BatchMessage.java +++ b/src/java/org/apache/cassandra/transport/messages/BatchMessage.java @@ -40,6 +40,7 @@ import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.QueryState; import org.apache.cassandra.tracing.Tracing; import org.apache.cassandra.transport.CBUtil; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.transport.Message; import org.apache.cassandra.transport.ProtocolException; import org.apache.cassandra.transport.ProtocolVersion; @@ -169,7 +170,7 @@ public class BatchMessage extends Message.Request } @Override - protected Message.Response execute(QueryState state, long queryStartNanoTime, boolean traceRequest) + protected Message.Response execute(QueryState state, Dispatcher.RequestTime requestTime, boolean traceRequest) { List prepared = null; try @@ -226,7 +227,7 @@ public class BatchMessage extends Message.Request BatchStatement batch = new BatchStatement(batchType, VariableSpecifications.empty(), statements, Attributes.none()); long queryTime = currentTimeMillis(); - Message.Response response = handler.processBatch(batch, state, batchOptions, getCustomPayload(), queryStartNanoTime); + Message.Response response = handler.processBatch(batch, state, batchOptions, getCustomPayload(), requestTime); if (queries != null) QueryEvents.instance.notifyBatchSuccess(batchType, statements, queries, values, options, state, queryTime, response); return response; diff --git a/src/java/org/apache/cassandra/transport/messages/ExecuteMessage.java b/src/java/org/apache/cassandra/transport/messages/ExecuteMessage.java index dabb21e97b..6199d543d7 100644 --- a/src/java/org/apache/cassandra/transport/messages/ExecuteMessage.java +++ b/src/java/org/apache/cassandra/transport/messages/ExecuteMessage.java @@ -35,6 +35,7 @@ import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.QueryState; import org.apache.cassandra.tracing.Tracing; import org.apache.cassandra.transport.CBUtil; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.transport.Message; import org.apache.cassandra.transport.ProtocolException; import org.apache.cassandra.transport.ProtocolVersion; @@ -125,7 +126,7 @@ public class ExecuteMessage extends Message.Request } @Override - protected Message.Response execute(QueryState state, long queryStartNanoTime, boolean traceRequest) + protected Message.Response execute(QueryState state, Dispatcher.RequestTime requestTime, boolean traceRequest) { QueryHandler.Prepared prepared = null; try @@ -148,7 +149,6 @@ public class ExecuteMessage extends Message.Request nospam.error(msg); } - CQLStatement statement = prepared.statement; options.prepare(statement.getBindVariables()); @@ -164,7 +164,7 @@ public class ExecuteMessage extends Message.Request long requestStartTime = currentTimeMillis(); - Message.Response response = handler.processPrepared(statement, state, queryOptions, getCustomPayload(), queryStartNanoTime); + Message.Response response = handler.processPrepared(statement, state, queryOptions, getCustomPayload(), requestTime); QueryEvents.instance.notifyExecuteSuccess(prepared.statement, prepared.rawCQLStatement, options, state, requestStartTime, response); diff --git a/src/java/org/apache/cassandra/transport/messages/OptionsMessage.java b/src/java/org/apache/cassandra/transport/messages/OptionsMessage.java index 828548d3f9..1ed109db2e 100644 --- a/src/java/org/apache/cassandra/transport/messages/OptionsMessage.java +++ b/src/java/org/apache/cassandra/transport/messages/OptionsMessage.java @@ -27,6 +27,7 @@ import io.netty.buffer.ByteBuf; import org.apache.cassandra.cql3.QueryProcessor; import org.apache.cassandra.service.QueryState; import org.apache.cassandra.transport.Compressor; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.transport.Message; import org.apache.cassandra.transport.ProtocolVersion; @@ -58,7 +59,7 @@ public class OptionsMessage extends Message.Request } @Override - protected Message.Response execute(QueryState state, long queryStartNanoTime, boolean traceRequest) + protected Message.Response execute(QueryState state, Dispatcher.RequestTime requestTime, boolean traceRequest) { List cqlVersions = new ArrayList(); cqlVersions.add(QueryProcessor.CQL_VERSION.toString()); diff --git a/src/java/org/apache/cassandra/transport/messages/PrepareMessage.java b/src/java/org/apache/cassandra/transport/messages/PrepareMessage.java index 20861d0faa..bebea3cc5b 100644 --- a/src/java/org/apache/cassandra/transport/messages/PrepareMessage.java +++ b/src/java/org/apache/cassandra/transport/messages/PrepareMessage.java @@ -31,6 +31,7 @@ import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.QueryState; import org.apache.cassandra.tracing.Tracing; import org.apache.cassandra.transport.CBUtil; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.transport.Message; import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.utils.JVMStabilityInspector; @@ -114,7 +115,7 @@ public class PrepareMessage extends Message.Request } @Override - protected Message.Response execute(QueryState state, long queryStartNanoTime, boolean traceRequest) + protected Message.Response execute(QueryState state, Dispatcher.RequestTime requestTime, boolean traceRequest) { try { diff --git a/src/java/org/apache/cassandra/transport/messages/QueryMessage.java b/src/java/org/apache/cassandra/transport/messages/QueryMessage.java index c295216d2c..665d62a8cb 100644 --- a/src/java/org/apache/cassandra/transport/messages/QueryMessage.java +++ b/src/java/org/apache/cassandra/transport/messages/QueryMessage.java @@ -30,6 +30,7 @@ import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.QueryState; import org.apache.cassandra.tracing.Tracing; import org.apache.cassandra.transport.CBUtil; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.transport.Message; import org.apache.cassandra.transport.ProtocolException; import org.apache.cassandra.transport.ProtocolVersion; @@ -98,7 +99,7 @@ public class QueryMessage extends Message.Request } @Override - protected Message.Response execute(QueryState state, long queryStartNanoTime, boolean traceRequest) + protected Message.Response execute(QueryState state, Dispatcher.RequestTime requestTime, boolean traceRequest) { CQLStatement statement = null; try @@ -113,7 +114,7 @@ public class QueryMessage extends Message.Request QueryHandler queryHandler = ClientState.getCQLQueryHandler(); statement = queryHandler.parse(query, state, options); - Message.Response response = queryHandler.process(statement, state, options, getCustomPayload(), queryStartNanoTime); + Message.Response response = queryHandler.process(statement, state, options, getCustomPayload(), requestTime); QueryEvents.instance.notifyQuerySuccess(statement, query, options, state, queryStartTime, response); if (options.skipMetadata() && response instanceof ResultMessage.Rows) diff --git a/src/java/org/apache/cassandra/transport/messages/RegisterMessage.java b/src/java/org/apache/cassandra/transport/messages/RegisterMessage.java index f8eb55e2ae..83f9cac316 100644 --- a/src/java/org/apache/cassandra/transport/messages/RegisterMessage.java +++ b/src/java/org/apache/cassandra/transport/messages/RegisterMessage.java @@ -63,7 +63,7 @@ public class RegisterMessage extends Message.Request } @Override - protected Response execute(QueryState state, long queryStartNanoTime, boolean traceRequest) + protected Response execute(QueryState state, Dispatcher.RequestTime requestTime, boolean traceRequest) { assert connection instanceof ServerConnection; Connection.Tracker tracker = connection.getTracker(); diff --git a/src/java/org/apache/cassandra/transport/messages/StartupMessage.java b/src/java/org/apache/cassandra/transport/messages/StartupMessage.java index 37afb22868..f3948f4e05 100644 --- a/src/java/org/apache/cassandra/transport/messages/StartupMessage.java +++ b/src/java/org/apache/cassandra/transport/messages/StartupMessage.java @@ -68,7 +68,7 @@ public class StartupMessage extends Message.Request } @Override - protected Message.Response execute(QueryState state, long queryStartNanoTime, boolean traceRequest) + protected Message.Response execute(QueryState state, Dispatcher.RequestTime requestTime, boolean traceRequest) { String cqlVersion = options.get(CQL_VERSION); if (cqlVersion == null) diff --git a/test/burn/org/apache/cassandra/transport/DriverBurnTest.java b/test/burn/org/apache/cassandra/transport/DriverBurnTest.java index 7856b1b40d..42b7c6bbaa 100644 --- a/test/burn/org/apache/cassandra/transport/DriverBurnTest.java +++ b/test/burn/org/apache/cassandra/transport/DriverBurnTest.java @@ -78,7 +78,7 @@ public class DriverBurnTest extends CQLTester { QueryMessage queryMessage = QueryMessage.codec.decode(body, version); return new QueryMessage(queryMessage.query, queryMessage.options) { - protected Message.Response execute(QueryState state, long queryStartNanoTime, boolean traceRequest) + protected Message.Response execute(QueryState state, Dispatcher.RequestTime requestTime, boolean traceRequest) { try { @@ -89,7 +89,7 @@ public class DriverBurnTest extends CQLTester catch (NumberFormatException e) { // for the requests driver issues under the hood - return super.execute(state, queryStartNanoTime, traceRequest); + return super.execute(state, requestTime, traceRequest); } } }; @@ -338,7 +338,7 @@ public class DriverBurnTest extends CQLTester { QueryMessage queryMessage = QueryMessage.codec.decode(body, version); return new QueryMessage(queryMessage.query, queryMessage.options) { - protected Message.Response execute(QueryState state, long queryStartNanoTime, boolean traceRequest) + protected Message.Response execute(QueryState state, Dispatcher.RequestTime requestTime, boolean traceRequest) { try { @@ -348,7 +348,7 @@ public class DriverBurnTest extends CQLTester catch (NumberFormatException e) { // for the requests driver issues under the hood - return super.execute(state, queryStartNanoTime, traceRequest); + return super.execute(state, requestTime, traceRequest); } } }; diff --git a/test/burn/org/apache/cassandra/transport/SimpleClientBurnTest.java b/test/burn/org/apache/cassandra/transport/SimpleClientBurnTest.java index 2d863cf020..ef29146be2 100644 --- a/test/burn/org/apache/cassandra/transport/SimpleClientBurnTest.java +++ b/test/burn/org/apache/cassandra/transport/SimpleClientBurnTest.java @@ -113,7 +113,8 @@ public class SimpleClientBurnTest QueryMessage queryMessage = QueryMessage.codec.decode(body, version); return new QueryMessage(queryMessage.query, queryMessage.options) { - protected Message.Response execute(QueryState state, long queryStartNanoTime, boolean traceRequest) + @Override + protected Message.Response execute(QueryState state, Dispatcher.RequestTime requestTime, boolean traceRequest) { int idx = Integer.parseInt(queryMessage.query); SizeCaps caps = idx % largeMessageFrequency == 0 ? largeMessageCap : smallMessageCap; diff --git a/test/burn/org/apache/cassandra/transport/SimpleClientPerfTest.java b/test/burn/org/apache/cassandra/transport/SimpleClientPerfTest.java index d15c4e56a7..3f990cbca3 100644 --- a/test/burn/org/apache/cassandra/transport/SimpleClientPerfTest.java +++ b/test/burn/org/apache/cassandra/transport/SimpleClientPerfTest.java @@ -169,7 +169,8 @@ public class SimpleClientPerfTest QueryMessage queryMessage = QueryMessage.codec.decode(body, version); return new QueryMessage(queryMessage.query, queryMessage.options) { - protected Message.Response execute(QueryState state, long queryStartNanoTime, boolean traceRequest) + @Override + protected Message.Response execute(QueryState state, Dispatcher.RequestTime requestTime, boolean traceRequest) { int idx = Integer.parseInt(queryMessage.query); // unused return generateRows(idx, responseCaps); diff --git a/test/distributed/org/apache/cassandra/distributed/impl/Coordinator.java b/test/distributed/org/apache/cassandra/distributed/impl/Coordinator.java index 19d80626d3..71cf5aaa5a 100644 --- a/test/distributed/org/apache/cassandra/distributed/impl/Coordinator.java +++ b/test/distributed/org/apache/cassandra/distributed/impl/Coordinator.java @@ -18,7 +18,6 @@ package org.apache.cassandra.distributed.impl; -import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; @@ -40,18 +39,14 @@ import org.apache.cassandra.distributed.api.QueryResult; import org.apache.cassandra.distributed.api.QueryResults; import org.apache.cassandra.distributed.api.SimpleQueryResult; import org.apache.cassandra.service.ClientState; -import org.apache.cassandra.service.ClientWarn; import org.apache.cassandra.service.QueryState; -import org.apache.cassandra.service.reads.thresholds.CoordinatorWarnings; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.tracing.Tracing; import org.apache.cassandra.transport.messages.ResultMessage; import org.apache.cassandra.utils.ByteBufferUtil; -import org.apache.cassandra.utils.FBUtilities; import org.apache.cassandra.utils.TimeUUID; -import static org.apache.cassandra.utils.Clock.Global.nanoTime; - public class Coordinator implements ICoordinator { final Instance instance; @@ -93,60 +88,12 @@ public class Coordinator implements ICoordinator } } - protected static org.apache.cassandra.db.ConsistencyLevel toCassandraSerialCL(ConsistencyLevel cl) - { - return toCassandraCL(cl == null ? ConsistencyLevel.SERIAL : cl); - } public static SimpleQueryResult unsafeExecuteInternal(String query, ConsistencyLevel consistencyLevel, Object[] boundValues) { - return unsafeExecuteInternal(query, null, consistencyLevel, boundValues); + return CoordinatorHelper.unsafeExecuteInternal(query, null, consistencyLevel, boundValues); } - public static SimpleQueryResult unsafeExecuteInternal(String query, ConsistencyLevel serialConsistencyLevel, ConsistencyLevel commitConsistencyLevel, Object[] boundValues) - { - ClientState clientState = makeFakeClientState(); - CQLStatement prepared = QueryProcessor.getStatement(query, clientState); - List boundBBValues = new ArrayList<>(); - for (Object boundValue : boundValues) - boundBBValues.add(ByteBufferUtil.objectToBytes(boundValue)); - - prepared.validate(QueryState.forInternalCalls().getClientState()); - - // Start capturing warnings on this thread. Note that this will implicitly clear out any previous - // warnings as it sets a new State instance on the ThreadLocal. - ClientWarn.instance.captureWarnings(); - CoordinatorWarnings.init(); - try - { - ResultMessage res = prepared.execute(QueryState.forInternalCalls(), - QueryOptions.create(toCassandraCL(commitConsistencyLevel), - boundBBValues, - false, - Integer.MAX_VALUE, - null, - toCassandraSerialCL(serialConsistencyLevel), - ProtocolVersion.CURRENT, - null), - nanoTime()); - // Collect warnings reported during the query. - CoordinatorWarnings.done(); - if (res != null) - res.setWarnings(ClientWarn.instance.getWarnings()); - - return RowUtil.toQueryResult(res); - } - catch (Exception | Error e) - { - CoordinatorWarnings.done(); - throw e; - } - finally - { - CoordinatorWarnings.reset(); - ClientWarn.instance.resetWarnings(); - } - } public Object[][] executeWithTracing(UUID sessionId, String query, ConsistencyLevel consistencyLevelOrigin, Object... boundValues) { @@ -161,7 +108,7 @@ public class Coordinator implements ICoordinator @Override public SimpleQueryResult executeWithResult(String query, ConsistencyLevel serialConsistencyLevel, ConsistencyLevel commitConsistencyLevel, Object... boundValues) { - return instance.sync(() -> unsafeExecuteInternal(query, serialConsistencyLevel, commitConsistencyLevel, boundValues)).call(); + return instance.sync(() -> CoordinatorHelper.unsafeExecuteInternal(query, serialConsistencyLevel, commitConsistencyLevel, boundValues)).call(); } @Override @@ -171,7 +118,7 @@ public class Coordinator implements ICoordinator throw new IllegalArgumentException("Page size should be strictly positive but was " + pageSize); return instance.sync(() -> { - ClientState clientState = makeFakeClientState(); + ClientState clientState = CoordinatorHelper.makeFakeClientState(); ConsistencyLevel consistencyLevel = ConsistencyLevel.valueOf(consistencyLevelOrigin.name()); CQLStatement prepared = QueryProcessor.getStatement(query, clientState); final List boundBBValues = new ArrayList<>(); @@ -181,7 +128,7 @@ public class Coordinator implements ICoordinator prepared.validate(clientState); assert prepared instanceof SelectStatement : "Only SELECT statements can be executed with paging"; - long nanoTime = nanoTime(); + Dispatcher.RequestTime requestTime = Dispatcher.RequestTime.forImmediateExecution(); SelectStatement selectStatement = (SelectStatement) prepared; QueryState queryState = new QueryState(clientState); @@ -195,9 +142,9 @@ public class Coordinator implements ICoordinator selectStatement.keyspace()); - ResultMessage.Rows initialRows = selectStatement.execute(queryState, initialOptions, nanoTime); + ResultMessage.Rows initialRows = selectStatement.execute(queryState, initialOptions, requestTime); Iterator iter = new Iterator() { - ResultMessage.Rows rows = selectStatement.execute(queryState, initialOptions, nanoTime); + ResultMessage.Rows rows = selectStatement.execute(queryState, initialOptions, requestTime); Iterator iter = RowUtil.toIter(rows); public boolean hasNext() @@ -217,7 +164,7 @@ public class Coordinator implements ICoordinator ProtocolVersion.CURRENT, selectStatement.keyspace()); - rows = selectStatement.execute(queryState, nextOptions, nanoTime); + rows = selectStatement.execute(queryState, nextOptions, requestTime); iter = Iterators.forArray(RowUtil.toObjects(initialRows.result.metadata.names, rows.result.rows)); return hasNext(); @@ -233,8 +180,4 @@ public class Coordinator implements ICoordinator }).call(); } - public static ClientState makeFakeClientState() - { - return ClientState.forExternalCalls(new InetSocketAddress(FBUtilities.getJustLocalAddress(), 9042)); - } } diff --git a/test/distributed/org/apache/cassandra/distributed/impl/CoordinatorHelper.java b/test/distributed/org/apache/cassandra/distributed/impl/CoordinatorHelper.java new file mode 100644 index 0000000000..414b30e05c --- /dev/null +++ b/test/distributed/org/apache/cassandra/distributed/impl/CoordinatorHelper.java @@ -0,0 +1,106 @@ +/* + * 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.distributed.impl; + +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +import org.apache.cassandra.cql3.CQLStatement; +import org.apache.cassandra.cql3.QueryOptions; +import org.apache.cassandra.cql3.QueryProcessor; +import org.apache.cassandra.distributed.api.ConsistencyLevel; +import org.apache.cassandra.distributed.api.SimpleQueryResult; +import org.apache.cassandra.service.ClientState; +import org.apache.cassandra.service.ClientWarn; +import org.apache.cassandra.service.QueryState; +import org.apache.cassandra.service.reads.thresholds.CoordinatorWarnings; +import org.apache.cassandra.transport.Dispatcher; +import org.apache.cassandra.transport.ProtocolVersion; +import org.apache.cassandra.transport.messages.ResultMessage; +import org.apache.cassandra.utils.ByteBufferUtil; +import org.apache.cassandra.utils.FBUtilities; + +import static org.apache.cassandra.distributed.impl.Coordinator.toCassandraCL; + +public class CoordinatorHelper +{ + public static SimpleQueryResult unsafeExecuteInternal(String query, ConsistencyLevel serialConsistencyLevel, ConsistencyLevel commitConsistencyLevel, Object[] boundValues) + { + return unsafeExecuteInternal(query, serialConsistencyLevel, commitConsistencyLevel, Dispatcher.RequestTime.forImmediateExecution(), boundValues); + } + + public static SimpleQueryResult unsafeExecuteInternal(String query, ConsistencyLevel serialConsistencyLevel, ConsistencyLevel commitConsistencyLevel, Dispatcher.RequestTime requestTime, Object... boundValues) + { + ClientState clientState = makeFakeClientState(); + CQLStatement prepared = QueryProcessor.getStatement(query, clientState); + List boundBBValues = new ArrayList<>(); + for (Object boundValue : boundValues) + boundBBValues.add(ByteBufferUtil.objectToBytes(boundValue)); + + prepared.validate(QueryState.forInternalCalls().getClientState()); + prepared.validate(clientState); + + // Start capturing warnings on this thread. Note that this will implicitly clear out any previous + // warnings as it sets a new State instance on the ThreadLocal. + ClientWarn.instance.captureWarnings(); + CoordinatorWarnings.init(); + try + { + ResultMessage res = prepared.execute(QueryState.forInternalCalls(), + QueryOptions.create(toCassandraCL(commitConsistencyLevel), + boundBBValues, + false, + Integer.MAX_VALUE, + null, + toCassandraSerialCL(serialConsistencyLevel), + ProtocolVersion.CURRENT, + null), + requestTime); + // Collect warnings reported during the query. + CoordinatorWarnings.done(); + if (res != null) + res.setWarnings(ClientWarn.instance.getWarnings()); + + return RowUtil.toQueryResult(res); + } + catch (Exception | Error e) + { + CoordinatorWarnings.done(); + throw e; + } + finally + { + CoordinatorWarnings.reset(); + ClientWarn.instance.resetWarnings(); + } + } + + public static ClientState makeFakeClientState() + { + return ClientState.forExternalCalls(new InetSocketAddress(FBUtilities.getJustLocalAddress(), 9042)); + } + + + protected static org.apache.cassandra.db.ConsistencyLevel toCassandraSerialCL(ConsistencyLevel cl) + { + return toCassandraCL(cl == null ? ConsistencyLevel.SERIAL : cl); + } +} diff --git a/test/distributed/org/apache/cassandra/distributed/impl/Query.java b/test/distributed/org/apache/cassandra/distributed/impl/Query.java index 823113f857..57aefe3a81 100644 --- a/test/distributed/org/apache/cassandra/distributed/impl/Query.java +++ b/test/distributed/org/apache/cassandra/distributed/impl/Query.java @@ -30,13 +30,12 @@ import org.apache.cassandra.distributed.api.IIsolatedExecutor; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.ClientWarn; import org.apache.cassandra.service.QueryState; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.transport.messages.ResultMessage; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.FBUtilities; -import static org.apache.cassandra.utils.Clock.Global.nanoTime; - public class Query implements IIsolatedExecutor.SerializableCallable { private static final long serialVersionUID = 1L; @@ -60,7 +59,7 @@ public class Query implements IIsolatedExecutor.SerializableCallable { ConsistencyLevel commitConsistency = toCassandraCL(commitConsistencyOrigin); ConsistencyLevel serialConsistency = serialConsistencyOrigin == null ? null : toCassandraCL(serialConsistencyOrigin); - ClientState clientState = Coordinator.makeFakeClientState(); + ClientState clientState = CoordinatorHelper.makeFakeClientState(); CQLStatement prepared = QueryProcessor.getStatement(query, clientState); List boundBBValues = new ArrayList<>(); for (Object boundValue : boundValues) @@ -83,7 +82,7 @@ public class Query implements IIsolatedExecutor.SerializableCallable null, timestamp, FBUtilities.nowInSeconds()), - nanoTime()); + Dispatcher.RequestTime.forImmediateExecution()); // Collect warnings reported during the query. if (res != null) diff --git a/test/distributed/org/apache/cassandra/distributed/test/ByteBuddyExamplesTest.java b/test/distributed/org/apache/cassandra/distributed/test/ByteBuddyExamplesTest.java index d6abd35788..897bc8db41 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/ByteBuddyExamplesTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/ByteBuddyExamplesTest.java @@ -36,6 +36,7 @@ import org.apache.cassandra.cql3.statements.SelectStatement; import org.apache.cassandra.distributed.Cluster; import org.apache.cassandra.distributed.api.ConsistencyLevel; import org.apache.cassandra.service.QueryState; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.transport.messages.ResultMessage; import org.apache.cassandra.transport.messages.ResultMessage.Rows; @@ -116,7 +117,7 @@ public class ByteBuddyExamplesTest extends TestBaseImpl .load(cl, ClassLoadingStrategy.Default.INJECTION); } - public static ResultMessage.Rows execute(QueryState state, QueryOptions options, long queryStartNanoTime, @SuperCall Callable r) throws Exception + public static ResultMessage.Rows execute(QueryState state, QueryOptions options, Dispatcher.RequestTime requestTime, @SuperCall Callable r) throws Exception { Rows res = r.call(); diff --git a/test/distributed/org/apache/cassandra/distributed/test/DisableBinaryTest.java b/test/distributed/org/apache/cassandra/distributed/test/DisableBinaryTest.java new file mode 100644 index 0000000000..a5c0b1a5c3 --- /dev/null +++ b/test/distributed/org/apache/cassandra/distributed/test/DisableBinaryTest.java @@ -0,0 +1,209 @@ +/* + * 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.distributed.test; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.LockSupport; + +import org.junit.Assert; +import org.junit.Test; + +import com.datastax.driver.core.PoolingOptions; +import com.datastax.driver.core.Session; +import com.datastax.driver.core.SocketOptions; +import com.datastax.driver.core.exceptions.OverloadedException; +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; +import net.bytebuddy.implementation.MethodDelegation; +import net.bytebuddy.implementation.bind.annotation.SuperCall; +import org.apache.cassandra.cql3.QueryOptions; +import org.apache.cassandra.cql3.statements.SelectStatement; +import org.apache.cassandra.distributed.Cluster; +import org.apache.cassandra.distributed.api.NodeToolResult; +import org.apache.cassandra.service.CassandraDaemon; +import org.apache.cassandra.service.QueryState; +import org.apache.cassandra.transport.Dispatcher; +import org.apache.cassandra.transport.messages.ResultMessage; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; +import static org.apache.cassandra.distributed.api.Feature.GOSSIP; +import static org.apache.cassandra.distributed.api.Feature.NATIVE_PROTOCOL; +import static org.apache.cassandra.distributed.api.Feature.NETWORK; +import static org.assertj.core.api.Assertions.fail; + +public class DisableBinaryTest extends TestBaseImpl +{ + private static int REQUESTS = 100; + + private static com.datastax.driver.core.Cluster driver(Cluster sut) + { + return com.datastax.driver.core.Cluster.builder().addContactPoint(sut.get(1).broadcastAddress().getHostString()) + .withSocketOptions(new SocketOptions().setReadTimeoutMillis(Integer.MAX_VALUE)) + .withPoolingOptions(new PoolingOptions().setHeartbeatIntervalSeconds(Integer.MAX_VALUE)) + .build(); + } + + @Test + public void testFinishInProgressQueries() throws Throwable + { + ExecutorService executor = Executors.newFixedThreadPool(REQUESTS); + try (Cluster control = init(Cluster.build().withNodes(1) + .withInstanceInitializer(BlockingSelect::install) + .withConfig(config -> config.with(GOSSIP, NETWORK, NATIVE_PROTOCOL)).start()); + com.datastax.driver.core.Cluster cluster = driver(control); + Session session = cluster.connect(KEYSPACE)) + { + control.schemaChange(withKeyspace("CREATE TABLE %s.tbl (pk int PRIMARY KEY, v int)")); + control.get(1).runOnInstance(() -> { + Assert.assertTrue(BlockingSelect.enabled.compareAndSet(false, true)); + }); + List> futures = new ArrayList<>(); + for (int i = 0; i < REQUESTS; i++) + futures.add(CompletableFuture.supplyAsync(() -> session.execute("select * from tbl").one(), executor)); + + control.get(1).runOnInstance(() -> { + try + { + BlockingSelect.latch.await(); + } + catch (InterruptedException e) + { + throw new RuntimeException(e); + } + }); + CompletableFuture result = CompletableFuture.supplyAsync(() -> control.get(1).nodetoolResult("disablebinary")); + + control.get(1).runOnInstance(() -> BlockingSelect.signal.countDown()); + result.get(); + for (Future future : futures) + future.get(); + // There are no assertions in this test as we simply expect there were no transport exceptions, and all queries succeed. + } + finally + { + executor.shutdown(); + } + } + + + @Test + public void testDisallowsNewRequests() throws Throwable + { + ExecutorService executor = Executors.newFixedThreadPool(REQUESTS); + try (Cluster control = init(Cluster.build().withNodes(1) + .withInstanceInitializer(BlockingSelect::install) + .withConfig(config -> config.with(GOSSIP, NETWORK, NATIVE_PROTOCOL)).start()); + com.datastax.driver.core.Cluster cluster = driver(control); + Session session = cluster.connect(KEYSPACE)) + { + control.schemaChange(withKeyspace("CREATE TABLE %s.tbl (pk int PRIMARY KEY, v int)")); + control.get(1).runOnInstance(() -> { + Assert.assertTrue(BlockingSelect.enabled.compareAndSet(false, true)); + }); + List> futures = new ArrayList<>(); + for (int i = 0; i < REQUESTS; i++) + futures.add(CompletableFuture.supplyAsync(() -> session.execute("select * from tbl").one(), executor)); + + control.get(1).runOnInstance(() -> { + try + { + BlockingSelect.latch.await(); + } + catch (InterruptedException e) + { + throw new RuntimeException(e); + } + }); + CompletableFuture result = CompletableFuture.supplyAsync(() -> control.get(1).nodetoolResult("disablebinary")); + + control.get(1).runOnInstance(() -> { + while(CassandraDaemon.getInstanceForTesting().nativeTransportService().isRunning()) { + LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(100)); + } + }); + + Future afterShutdown = CompletableFuture.supplyAsync(() -> session.execute("select * from tbl").one()); + try + { + session.execute("select * from tbl").one(); + fail("Should have thrown OverloadedException"); + } + catch (OverloadedException e) {} + + control.get(1).runOnInstance(() -> BlockingSelect.signal.countDown()); + result.get(); + for (Future future : futures) + future.get(); + + try + { + afterShutdown.get(); + fail("Should have thrown OverloadedException"); + } + catch (ExecutionException e) + { + Assert.assertTrue(e.getCause() instanceof OverloadedException); + } + } + finally + { + executor.shutdown(); + } + } + + public static class BlockingSelect + { + static CountDownLatch latch = new CountDownLatch(REQUESTS); + static CountDownLatch signal = new CountDownLatch(1); + static AtomicBoolean enabled = new AtomicBoolean(false); + + static void install(ClassLoader cl, int nodeNumber) + { + new ByteBuddy().rebase(SelectStatement.class) + .method(named("execute").and(takesArguments(QueryState.class, QueryOptions.class, Dispatcher.RequestTime.class))) + .intercept(MethodDelegation.to(BlockingSelect.class)) + .make() + .load(cl, ClassLoadingStrategy.Default.INJECTION); + } + + public static ResultMessage.Rows execute(QueryState state, QueryOptions options, Dispatcher.RequestTime requestTime, @SuperCall Callable r) throws Exception + { + if (enabled.get() && !state.getClientState().isInternal) + { + latch.countDown(); + signal.await(); + // Sleep for one more second to make sure disable binary reacts + Thread.sleep(1000); + } + return r.call(); + } + } + +} diff --git a/test/distributed/org/apache/cassandra/distributed/test/FailingResponseDoesNotLogTest.java b/test/distributed/org/apache/cassandra/distributed/test/FailingResponseDoesNotLogTest.java index 4032cc7752..f410715add 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/FailingResponseDoesNotLogTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/FailingResponseDoesNotLogTest.java @@ -45,6 +45,7 @@ import org.apache.cassandra.exceptions.RequestExecutionException; import org.apache.cassandra.exceptions.RequestValidationException; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.QueryState; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.transport.SimpleClient; import org.apache.cassandra.transport.messages.ResultMessage; import org.apache.cassandra.utils.MD5Digest; @@ -108,7 +109,7 @@ public class FailingResponseDoesNotLogTest extends TestBaseImpl } @Override - public ResultMessage process(CQLStatement statement, QueryState state, QueryOptions options, Map customPayload, long queryStartNanoTime) throws RequestExecutionException, RequestValidationException + public ResultMessage process(CQLStatement statement, QueryState state, QueryOptions options, Map customPayload, Dispatcher.RequestTime requestTime) throws RequestExecutionException, RequestValidationException { throw new AssertionError("reject"); } @@ -126,13 +127,13 @@ public class FailingResponseDoesNotLogTest extends TestBaseImpl } @Override - public ResultMessage processPrepared(CQLStatement statement, QueryState state, QueryOptions options, Map customPayload, long queryStartNanoTime) throws RequestExecutionException, RequestValidationException + public ResultMessage processPrepared(CQLStatement statement, QueryState state, QueryOptions options, Map customPayload, Dispatcher.RequestTime requestTime) throws RequestExecutionException, RequestValidationException { throw new AssertionError("reject"); } @Override - public ResultMessage processBatch(BatchStatement statement, QueryState state, BatchQueryOptions options, Map customPayload, long queryStartNanoTime) throws RequestExecutionException, RequestValidationException + public ResultMessage processBatch(BatchStatement statement, QueryState state, BatchQueryOptions options, Map customPayload, Dispatcher.RequestTime requestTime) throws RequestExecutionException, RequestValidationException { throw new AssertionError("reject"); } diff --git a/test/distributed/org/apache/cassandra/distributed/test/FailureLoggingTest.java b/test/distributed/org/apache/cassandra/distributed/test/FailureLoggingTest.java index ff8234d7d3..58d44f558c 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/FailureLoggingTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/FailureLoggingTest.java @@ -40,6 +40,7 @@ import org.apache.cassandra.exceptions.UnavailableException; import org.apache.cassandra.service.StorageProxy; import org.apache.cassandra.service.StorageService; import org.apache.cassandra.service.reads.range.RangeCommandIterator; +import org.apache.cassandra.transport.Dispatcher; import static net.bytebuddy.matcher.ElementMatchers.named; import static org.junit.Assert.assertEquals; @@ -168,8 +169,8 @@ public class FailureLoggingTest extends TestBaseImpl @SuppressWarnings("unused") public static PartitionIterator fetchRows(List commands, - org.apache.cassandra.db.ConsistencyLevel consistencyLevel, - long queryStartNanoTime) + org.apache.cassandra.db.ConsistencyLevel consistencyLevel, + Dispatcher.RequestTime requestTime) { throw UnavailableException.create(org.apache.cassandra.db.ConsistencyLevel.ALL, 1, 0); } diff --git a/test/distributed/org/apache/cassandra/distributed/test/LargeColumnTest.java b/test/distributed/org/apache/cassandra/distributed/test/LargeColumnTest.java index b03171b850..e8a40aaab7 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/LargeColumnTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/LargeColumnTest.java @@ -68,6 +68,8 @@ public class LargeColumnTest extends TestBaseImpl config.set("commitlog_segment_size", String.format("%dMiB",(columnSize * 3) >> 20)) .set("internode_application_send_queue_reserve_endpoint_capacity", String.format("%dB", (columnSize * 2))) .set("internode_application_send_queue_reserve_global_capacity", String.format("%dB", (columnSize * 3))) + .set("native_transport_max_request_data_in_flight_per_ip", String.format("%dB", (columnSize * 3))) + .set("native_transport_max_request_data_in_flight", String.format("%dB", (columnSize * 6))) .set("write_request_timeout", "30s") .set("read_request_timeout", "30s") .set("memtable_heap_space", "1024MiB") diff --git a/test/distributed/org/apache/cassandra/distributed/test/MessageTimestampTest.java b/test/distributed/org/apache/cassandra/distributed/test/MessageTimestampTest.java new file mode 100644 index 0000000000..11f6ab5ad0 --- /dev/null +++ b/test/distributed/org/apache/cassandra/distributed/test/MessageTimestampTest.java @@ -0,0 +1,111 @@ +/* + * 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.distributed.test; + +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.junit.Assert; +import org.junit.Test; + +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; +import net.bytebuddy.implementation.MethodDelegation; +import net.bytebuddy.implementation.bind.annotation.SuperCall; +import org.apache.cassandra.config.Config; +import org.apache.cassandra.distributed.Cluster; +import org.apache.cassandra.distributed.api.ConsistencyLevel; +import org.apache.cassandra.metrics.InternodeOutboundMetrics; +import org.apache.cassandra.transport.Dispatcher; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static org.apache.cassandra.distributed.api.Feature.GOSSIP; +import static org.apache.cassandra.distributed.api.Feature.NATIVE_PROTOCOL; +import static org.apache.cassandra.distributed.api.Feature.NETWORK; + +public class MessageTimestampTest extends TestBaseImpl +{ + @Test + public void testFinishInProgressQueries() throws Throwable + { + ExecutorService executor = Executors.newCachedThreadPool(); + try (Cluster cluster = init(Cluster.build().withNodes(2) + .withInstanceInitializer(EnqueuedInThePast::install) + .withConfig(config -> config.with(GOSSIP, NETWORK, NATIVE_PROTOCOL) + .set("native_transport_max_threads", 2) + .set("read_request_timeout_in_ms", 5000) + .set("cql_start_time", Config.CQLStartTime.QUEUE) + ) + .start())) + { + cluster.schemaChange(withKeyspace("CREATE TABLE %s.tbl (pk int PRIMARY KEY, v int)")); + + long expiredBefore = cluster.get(1).callsOnInstance(() -> InternodeOutboundMetrics.totalExpiredCallbacks.getCount()).call(); + + cluster.get(1).runOnInstance(() -> { + Assert.assertTrue(EnqueuedInThePast.enabled.compareAndSet(false, true)); + }); + CompletableFuture.supplyAsync(() -> cluster.coordinator(1).execute("select * from " + KEYSPACE + ".tbl where pk = " + 1, ConsistencyLevel.ALL), executor); + CompletableFuture.supplyAsync(() -> cluster.coordinator(1).execute("select * from " + KEYSPACE + ".tbl where pk = " + 2, ConsistencyLevel.ALL), executor) ; + + long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(10); + long expiredAfter = 0; + while (System.nanoTime() < deadline) { + long computed = cluster.get(1).callsOnInstance(() -> InternodeOutboundMetrics.totalExpiredCallbacks.getCount()).call(); + if (computed > expiredBefore) + { + expiredAfter = computed; + break; + } + } + + Assert.assertTrue(String.format("%d should be larger than %d", expiredAfter, expiredBefore), + expiredAfter > expiredBefore); + + + } + } + + public static class EnqueuedInThePast + { + static AtomicBoolean enabled = new AtomicBoolean(false); + + static void install(ClassLoader cl, int nodeNumber) + { + new ByteBuddy().rebase(Dispatcher.RequestTime.class) + .method(named("enqueuedAtNanos")) + .intercept(MethodDelegation.to(EnqueuedInThePast.class)) + .make() + .load(cl, ClassLoadingStrategy.Default.INJECTION); + } + + public static long enqueuedAtNanos(@SuperCall Callable r) throws Exception + { + if (enabled.get()) + { + return System.nanoTime() - TimeUnit.SECONDS.toNanos(30); + } + return r.call(); + } + } +} diff --git a/test/distributed/org/apache/cassandra/distributed/test/NodeToolEnableDisableBinaryTest.java b/test/distributed/org/apache/cassandra/distributed/test/NodeToolEnableDisableBinaryTest.java index d69029446a..36803fbdfd 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/NodeToolEnableDisableBinaryTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/NodeToolEnableDisableBinaryTest.java @@ -45,9 +45,12 @@ public class NodeToolEnableDisableBinaryTest extends TestBaseImpl public void setupEnv() throws IOException { if (cluster == null) + { cluster = init(builder().withNodes(1) .withConfig(config -> config.with(NETWORK, GOSSIP, NATIVE_PROTOCOL)) .start()); + cluster.get(1).nodetool("disableautocompaction"); + } } @AfterClass @@ -60,68 +63,70 @@ public class NodeToolEnableDisableBinaryTest extends TestBaseImpl public void testMaybeChangeDocs() { // If you added, modified options or help, please update docs if necessary - ToolResult tool = ToolRunner.invokeNodetoolJvmDtest(cluster.get(1), "help", "disablebinary"); - String help = "NAME\n" + - " nodetool disablebinary - Disable native transport (binary protocol)\n" + - "\n" + - "SYNOPSIS\n" + - " nodetool [(-h | --host )] [(-p | --port )]\n" + - " [(-pp | --print-port)] [(-pw | --password )]\n" + - " [(-pwf | --password-file )]\n" + - " [(-u | --username )] disablebinary\n" + - "\n" + - "OPTIONS\n" + - " -h , --host \n" + - " Node hostname or ip address\n" + - "\n" + - " -p , --port \n" + - " Remote jmx agent port number\n" + - "\n" + - " -pp, --print-port\n" + - " Operate in 4.0 mode with hosts disambiguated by port number\n" + - "\n" + - " -pw , --password \n" + - " Remote jmx agent password\n" + - "\n" + - " -pwf , --password-file \n" + - " Path to the JMX password file\n" + - "\n" + - " -u , --username \n" + - " Remote jmx agent username\n" + - "\n" + + String help = "NAME\n" + + " nodetool disablebinary - Disable native transport (binary protocol)\n" + + "\n" + + "SYNOPSIS\n" + + " nodetool [(-h | --host )] [(-p | --port )]\n" + + " [(-pp | --print-port)] [(-pw | --password )]\n" + + " [(-pwf | --password-file )]\n" + + " [(-u | --username )] disablebinary [(-f | --force)]\n" + + "\n" + + "OPTIONS\n" + + " -f, --force\n" + + " Use -f to interrupt client requests that have already started\n" + + "\n" + + " -h , --host \n" + + " Node hostname or ip address\n" + + "\n" + + " -p , --port \n" + + " Remote jmx agent port number\n" + + "\n" + + " -pp, --print-port\n" + + " Operate in 4.0 mode with hosts disambiguated by port number\n" + + "\n" + + " -pw , --password \n" + + " Remote jmx agent password\n" + + "\n" + + " -pwf , --password-file \n" + + " Path to the JMX password file\n" + + "\n" + + " -u , --username \n" + + " Remote jmx agent username\n" + + "\n" + "\n"; Assertions.assertThat(tool.getStdout()).isEqualTo(help); tool = ToolRunner.invokeNodetoolJvmDtest(cluster.get(1), "help", "enablebinary"); - help = "NAME\n" + - " nodetool enablebinary - Reenable native transport (binary protocol)\n" + - "\n" + - "SYNOPSIS\n" + - " nodetool [(-h | --host )] [(-p | --port )]\n" + - " [(-pp | --print-port)] [(-pw | --password )]\n" + - " [(-pwf | --password-file )]\n" + - " [(-u | --username )] enablebinary\n" + - "\n" + - "OPTIONS\n" + - " -h , --host \n" + - " Node hostname or ip address\n" + - "\n" + - " -p , --port \n" + - " Remote jmx agent port number\n" + - "\n" + - " -pp, --print-port\n" + - " Operate in 4.0 mode with hosts disambiguated by port number\n" + - "\n" + - " -pw , --password \n" + - " Remote jmx agent password\n" + - "\n" + - " -pwf , --password-file \n" + - " Path to the JMX password file\n" + - "\n" + - " -u , --username \n" + - " Remote jmx agent username\n" + - "\n" + + help = "NAME\n" + + " nodetool enablebinary - Reenable native transport (binary protocol)\n" + + "\n" + + "SYNOPSIS\n" + + " nodetool [(-h | --host )] [(-p | --port )]\n" + + " [(-pp | --print-port)] [(-pw | --password )]\n" + + " [(-pwf | --password-file )]\n" + + " [(-u | --username )] enablebinary\n" + + "\n" + + "OPTIONS\n" + + " -h , --host \n" + + " Node hostname or ip address\n" + + "\n" + + " -p , --port \n" + + " Remote jmx agent port number\n" + + "\n" + + " -pp, --print-port\n" + + " Operate in 4.0 mode with hosts disambiguated by port number\n" + + "\n" + + " -pw , --password \n" + + " Remote jmx agent password\n" + + "\n" + + " -pwf , --password-file \n" + + " Path to the JMX password file\n" + + "\n" + + " -u , --username \n" + + " Remote jmx agent username\n" + + "\n" + "\n"; Assertions.assertThat(tool.getStdout()).isEqualTo(help); } diff --git a/test/distributed/org/apache/cassandra/distributed/test/OverloadTest.java b/test/distributed/org/apache/cassandra/distributed/test/OverloadTest.java new file mode 100644 index 0000000000..fdb21b131e --- /dev/null +++ b/test/distributed/org/apache/cassandra/distributed/test/OverloadTest.java @@ -0,0 +1,247 @@ +/* + * 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.distributed.test; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.junit.Assert; +import org.junit.Test; + +import com.datastax.driver.core.PoolingOptions; +import com.datastax.driver.core.ProtocolVersion; +import com.datastax.driver.core.Session; +import com.datastax.driver.core.SocketOptions; +import com.datastax.driver.core.exceptions.OperationTimedOutException; +import com.datastax.driver.core.exceptions.OverloadedException; +import com.datastax.driver.core.exceptions.ReadTimeoutException; +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; +import net.bytebuddy.implementation.MethodDelegation; +import net.bytebuddy.implementation.bind.annotation.SuperCall; +import org.apache.cassandra.cql3.QueryOptions; +import org.apache.cassandra.cql3.statements.SelectStatement; +import org.apache.cassandra.distributed.Cluster; +import org.apache.cassandra.metrics.ClientMetrics; +import org.apache.cassandra.service.QueryState; +import org.apache.cassandra.transport.Dispatcher; +import org.apache.cassandra.transport.messages.ResultMessage; + +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.takesArguments; +import static org.apache.cassandra.distributed.api.Feature.GOSSIP; +import static org.apache.cassandra.distributed.api.Feature.NATIVE_PROTOCOL; +import static org.apache.cassandra.distributed.api.Feature.NETWORK; + +public class OverloadTest extends TestBaseImpl +{ + @Test + public void clientBackpressureDisabled() throws Throwable + { + testApplyClientBackpressure(0); + } + + @Test + public void applyClientBackpressure() throws Throwable + { + testApplyClientBackpressure(0.5); + } + + private void testApplyClientBackpressure(double threshold) throws Throwable + { + try (Cluster control = init(Cluster.build().withNodes(1) + .withInstanceInitializer(SlowSelect::install) + .withConfig(config -> config.with(GOSSIP, NETWORK, NATIVE_PROTOCOL) + .set("native_transport_queue_max_item_age_threshold", threshold) + .set("concurrent_reads", 2) + .set("native_transport_max_threads", 2) + .set("read_request_timeout_in_ms", 1000) + .set("write_request_timeout_in_ms", 1000) + .set("range_request_timeout_in_ms", 1000) + .set("counter_write_request_timeout_in_ms", 1000) + .set("native_transport_timeout", "1000ms") + ) + .start())) + { + control.schemaChange(withKeyspace("CREATE TABLE %s.tbl (pk int PRIMARY KEY, v int)")); + + for (ProtocolVersion protocolVersion : new ProtocolVersion[]{ ProtocolVersion.V4, ProtocolVersion.V5 }) + { + ExecutorService executor = Executors.newCachedThreadPool(); + try (com.datastax.driver.core.Cluster cluster = driver(control, protocolVersion); + Session session = cluster.connect(KEYSPACE)) + { + int pausedBefore = control.get(1).callsOnInstance(() -> { + return ClientMetrics.instance.getNumberOfPausedConnections(); + }).call(); + + session.execute("select * from tbl").one(); + control.get(1).runOnInstance(() -> { + Assert.assertTrue(SlowSelect.enabled.compareAndSet(false, true)); + }); + List> futures = new ArrayList<>(); + int count = 128; // 128 at most because we do not want to exceed client-side queue size + for (int i = 0; i < count; i++) + futures.add(CompletableFuture.supplyAsync(() -> session.execute("select * from tbl").one(), executor)); + // Wait and let requests in the head of the line queue up before the ones already submitted (they all got admitted + // to the queue since it was completely empty) + Thread.sleep(700); + for (int i = 0; i < count; i++) + futures.add(CompletableFuture.supplyAsync(() -> session.execute("select * from tbl").one(), executor)); + int success = 0; + int timedOut = 0; + int overloaded = 0; + for (Future future : futures) + { + try + { + future.get(); + success++; + } + catch (ExecutionException e) + { + if (e.getCause() instanceof OperationTimedOutException) + timedOut++; + else if (e.getCause() instanceof ReadTimeoutException) + timedOut++; + else if (e.getCause() instanceof OverloadedException) + overloaded++; + else throw e; + } + } + + Assert.assertEquals(count * 2, success + timedOut + overloaded); + int pausedAfter = control.get(1).callsOnInstance(() -> { + return ClientMetrics.instance.getNumberOfPausedConnections(); + }).call(); + if (threshold > 0) + { + Assert.assertTrue(String.format("Number of pauses after the test (%d) should have been larger than %s", pausedAfter, pausedBefore), + pausedAfter > pausedBefore); + } + else + { + Assert.assertEquals(String.format("Number of pauses after the test (%d) should have been the same as before (%s)", pausedAfter, pausedBefore), + pausedAfter, pausedBefore); + + } + } + finally + { + executor.shutdown(); + control.get(1).runOnInstance(() -> SlowSelect.enabled.set(false)); + } + } + } + } + + @Test + public void testFinishInProgressQueries() throws Throwable + { + ExecutorService executor = Executors.newCachedThreadPool(); + try (Cluster control = init(Cluster.build().withNodes(1) + .withInstanceInitializer(SlowSelect::install) + .withConfig(config -> config.with(GOSSIP, NETWORK, NATIVE_PROTOCOL) + .set("read_request_timeout_in_ms", 1000) + .set("write_request_timeout_in_ms", 1000) + .set("range_request_timeout_in_ms", 1000) + .set("counter_write_request_timeout_in_ms", 1000) + ) + .start()); + com.datastax.driver.core.Cluster cluster = driver(control, ProtocolVersion.V5); + Session session = cluster.connect(KEYSPACE)) + { + control.schemaChange(withKeyspace("CREATE TABLE %s.tbl (pk int PRIMARY KEY, v int)")); + session.execute("select * from tbl").one(); + control.get(1).runOnInstance(() -> { + Assert.assertTrue(SlowSelect.enabled.compareAndSet(false, true)); + }); + List> futures = new ArrayList<>(); + for (int i = 0; i < 1000; i++) + futures.add(CompletableFuture.supplyAsync(() -> session.execute("select * from tbl").one(), executor)); + + int success = 0; + int timedOut = 0; + int overloaded = 0; + for (Future future : futures) + { + try + { + future.get(); + success++; + } + catch (ExecutionException e) + { + if (e.getCause() instanceof OperationTimedOutException) + timedOut++; + else if (e.getCause() instanceof ReadTimeoutException) + timedOut++; + else if (e.getCause() instanceof OverloadedException) + overloaded++; + else throw e; + } + } + + Assert.assertEquals(1000, success + timedOut + overloaded); + } + finally + { + executor.shutdown(); + } + } + + public static com.datastax.driver.core.Cluster driver(Cluster sut, ProtocolVersion protocolVersion) + { + return com.datastax.driver.core.Cluster.builder().addContactPoint(sut.get(1).broadcastAddress().getHostString()) + .withSocketOptions(new SocketOptions().setReadTimeoutMillis(2000)) + .withPoolingOptions(new PoolingOptions().setHeartbeatIntervalSeconds(5000)) + .withProtocolVersion(protocolVersion) + .build(); + } + + public static class SlowSelect + { + static AtomicBoolean enabled = new AtomicBoolean(false); + + static void install(ClassLoader cl, int nodeNumber) + { + new ByteBuddy().rebase(SelectStatement.class) + .method(named("execute").and(takesArguments(QueryState.class, QueryOptions.class, Dispatcher.RequestTime.class))) + .intercept(MethodDelegation.to(SlowSelect.class)) + .make() + .load(cl, ClassLoadingStrategy.Default.INJECTION); + } + + public static ResultMessage.Rows execute(QueryState state, QueryOptions options, Dispatcher.RequestTime requestTime, @SuperCall Callable r) throws Exception + { + if (enabled.get() && !state.getClientState().isInternal) + { + Thread.sleep(1100); + } + return r.call(); + } + } +} \ No newline at end of file diff --git a/test/distributed/org/apache/cassandra/distributed/test/ReadRepairTest.java b/test/distributed/org/apache/cassandra/distributed/test/ReadRepairTest.java index 83c2806079..ba57ab1533 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/ReadRepairTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/ReadRepairTest.java @@ -356,7 +356,9 @@ public class ReadRepairTest extends TestBaseImpl String key = "test1"; try (Cluster cluster = init(Cluster.build() .withConfig(config -> config.with(Feature.GOSSIP, Feature.NETWORK) - .set("read_request_timeout", String.format("%dms", Integer.MAX_VALUE))) + .set("read_request_timeout", String.format("%dms", Integer.MAX_VALUE)) + .set("native_transport_timeout", String.format("%dms", Integer.MAX_VALUE)) + ) .withTokenSupplier(TokenSupplier.evenlyDistributedTokens(4)) .withNodeIdTopology(NetworkTopology.singleDcNetworkTopology(4, "dc0", "rack0")) .withNodes(3) diff --git a/test/distributed/org/apache/cassandra/distributed/test/ReadSpeculationTest.java b/test/distributed/org/apache/cassandra/distributed/test/ReadSpeculationTest.java new file mode 100644 index 0000000000..fce1de26ea --- /dev/null +++ b/test/distributed/org/apache/cassandra/distributed/test/ReadSpeculationTest.java @@ -0,0 +1,209 @@ +/* + * 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.distributed.test; + +import java.util.concurrent.TimeUnit; + +import org.junit.Assert; +import org.junit.Test; + +import org.apache.cassandra.concurrent.ScheduledExecutors; +import org.apache.cassandra.concurrent.ScheduledThreadPoolExecutorPlus; +import org.apache.cassandra.config.Config; +import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.db.ColumnFamilyStore; +import org.apache.cassandra.db.Keyspace; +import org.apache.cassandra.distributed.Cluster; +import org.apache.cassandra.distributed.api.ConsistencyLevel; +import org.apache.cassandra.distributed.impl.CoordinatorHelper; +import org.apache.cassandra.exceptions.ReadTimeoutException; +import org.apache.cassandra.service.CassandraDaemon; +import org.apache.cassandra.transport.Dispatcher; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +public class ReadSpeculationTest extends TestBaseImpl +{ + + @Test + public void speculateTest() throws Throwable + { + try (Cluster cluster = builder().withNodes(3) + .start()) + { + cluster.get(1).runOnInstance(() -> { + // Disable updater since we will force time + ((ScheduledThreadPoolExecutorPlus) ScheduledExecutors.optionalTasks).remove(CassandraDaemon.SPECULATION_THRESHOLD_UPDATER); + }); + cluster.schemaChange("CREATE KEYSPACE IF NOT EXISTS " + KEYSPACE + " WITH replication = {'class': 'SimpleStrategy', 'replication_factor': " + 3 + "}"); + cluster.schemaChange("CREATE TABLE IF NOT EXISTS " + KEYSPACE + ".tbl (pk int, ck int, v int, PRIMARY KEY (pk, ck)) WITH speculative_retry = '2000ms';"); + + // force speculation; rely on IP order + cluster.filters().allVerbs().from(1).to(2).drop(); + + + cluster.get(1).runOnInstance(() -> { + + + // Will speculate: have enough time till RPC timeout and client deadline + // Request has been submitted 2 seconds ago, and spent 1 second in the queue. Execution has started 1 second ago, and + // tracked latency is 3 seconds. Speculation timeout is always computed against the start time, so we will speculate + // exactly once, and after 3 - 1 = 2 seconds. + new TestScenario(12_000, + 12_000, + TimeUnit.SECONDS.toMicros(3), + Config.CQLStartTime.REQUEST, + TimeUnit.SECONDS.toNanos(2), + TimeUnit.SECONDS.toNanos(1)) + .assertWillSpeculate(); + new TestScenario(12_000, + 12_000, + TimeUnit.SECONDS.toMicros(3), + Config.CQLStartTime.QUEUE, + TimeUnit.SECONDS.toNanos(2), + TimeUnit.SECONDS.toNanos(1)) + .assertWillSpeculate(); + + // Will not speculate: latency is greater than RPC timeout + // Request has been submitted 2 seconds ago, and spent 1 second in the queue. Execution has started 1 second ago, and + // tracked latency is 2 seconds. Speculation timeout is always computed against the start time, so we will not speculate + // since we only have 1.5 seconds to execute request. + new TestScenario(12_000, + 1_500, + TimeUnit.SECONDS.toMicros(2), + Config.CQLStartTime.REQUEST, + TimeUnit.SECONDS.toNanos(3), + TimeUnit.SECONDS.toNanos(1)) + .assertWillNotSpeculate(); + new TestScenario(12_000, + 1_500, + TimeUnit.SECONDS.toMicros(2), + Config.CQLStartTime.QUEUE, + TimeUnit.SECONDS.toNanos(3), + TimeUnit.SECONDS.toNanos(1)) + .assertWillNotSpeculate(); + // Will not speculate: speculating will put us over the client deadline, even though RPC timeout is far enough + // Request has been submitted 11 seconds ago, and spent 10 second in the queue. Execution has started 1 second ago, and + // tracked latency is 2 seconds. Speculation timeout is always computed against the start time, so we will not speculate + // since there is only + new TestScenario(12_000, + 10_000, + TimeUnit.SECONDS.toMicros(2), + Config.CQLStartTime.REQUEST, + TimeUnit.SECONDS.toNanos(11), + TimeUnit.SECONDS.toNanos(1)) + .assertWillNotSpeculate(); + new TestScenario(12_000, + 1_500, + TimeUnit.SECONDS.toMicros(2), + Config.CQLStartTime.QUEUE, + TimeUnit.SECONDS.toNanos(11), + TimeUnit.SECONDS.toNanos(1)) + .assertWillNotSpeculate(); + new TestScenario(12_000, + 2000, + TimeUnit.SECONDS.toMicros(3), + Config.CQLStartTime.QUEUE, + TimeUnit.SECONDS.toNanos(9), + TimeUnit.SECONDS.toNanos(0)) + .assertWillNotSpeculate(); + }); + } + } + + private static class TestScenario + { + final long nativeTimeoutMs; + final long rpcTimeoutMs; + // After how much time speculation should be triggered + final long speculationTimeoutMicros; + + final Config.CQLStartTime cqlStartTime; + + final long enqueuedNsAgo; + final long startedNsAgo; + + TestScenario(long nativeTimeoutMs, long rpcTimeoutMs, long speculationTimeoutMicros, Config.CQLStartTime cqlStartTime, long enqueuedNsAgo, long startedNsAgo) + { + this.nativeTimeoutMs = nativeTimeoutMs; + this.rpcTimeoutMs = rpcTimeoutMs; + this.speculationTimeoutMicros = speculationTimeoutMicros; + + this.cqlStartTime = cqlStartTime; + + this.enqueuedNsAgo = enqueuedNsAgo; + this.startedNsAgo = startedNsAgo; + } + + private void assertWillSpeculate() + { + DatabaseDescriptor.setNativeTransportTimeout(nativeTimeoutMs, TimeUnit.MILLISECONDS); + DatabaseDescriptor.setReadRpcTimeout(rpcTimeoutMs); + DatabaseDescriptor.setCQLStartTime(cqlStartTime); + + ColumnFamilyStore cfs = Keyspace.open(KEYSPACE).getColumnFamilyStore("tbl"); + long speculatedBefore = cfs.metric.speculativeRetries.getCount(); + long before = System.nanoTime(); + cfs.sampleReadLatencyMicros = speculationTimeoutMicros; + + CoordinatorHelper.unsafeExecuteInternal("SELECT * FROM " + KEYSPACE + ".tbl WHERE pk = 1", + ConsistencyLevel.QUORUM, + ConsistencyLevel.QUORUM, + new Dispatcher.RequestTime(before - enqueuedNsAgo, + before - startedNsAgo)); + long after = System.nanoTime(); + long elapsed = after - before; + assertThat(elapsed).isGreaterThan(TimeUnit.MICROSECONDS.toNanos(speculationTimeoutMicros) - startedNsAgo); + long speculatedAfter = cfs.metric.speculativeRetries.getCount(); + Assert.assertEquals(speculatedAfter, speculatedBefore + 1); + } + + private void assertWillNotSpeculate() + { + DatabaseDescriptor.setNativeTransportIdleTimeout(nativeTimeoutMs); + DatabaseDescriptor.setReadRpcTimeout(rpcTimeoutMs); + DatabaseDescriptor.setCQLStartTime(cqlStartTime); + + ColumnFamilyStore cfs = Keyspace.open(KEYSPACE).getColumnFamilyStore("tbl"); + long speculatedBefore = cfs.metric.speculativeRetries.getCount(); + long before = System.nanoTime(); + cfs.sampleReadLatencyMicros = speculationTimeoutMicros; + + try + { + CoordinatorHelper.unsafeExecuteInternal("SELECT * FROM " + KEYSPACE + ".tbl WHERE pk = 1", + ConsistencyLevel.QUORUM, + ConsistencyLevel.QUORUM, + new Dispatcher.RequestTime(before - enqueuedNsAgo, + before - startedNsAgo)); + throw new AssertionError("Should have timed out"); + } + catch (ReadTimeoutException t) + { + // Expected + } + long after = System.nanoTime(); + long elapsed = after - before; + assertThat(TimeUnit.NANOSECONDS.toMicros(elapsed)).isLessThan(speculationTimeoutMicros); + long speculatedAfter = cfs.metric.speculativeRetries.getCount(); + Assert.assertEquals(speculatedAfter, speculatedBefore); + } + } + +} diff --git a/test/distributed/org/apache/cassandra/distributed/test/UnableToParseClientMessageTest.java b/test/distributed/org/apache/cassandra/distributed/test/UnableToParseClientMessageTest.java index 5c976b97c6..62969f03cf 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/UnableToParseClientMessageTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/UnableToParseClientMessageTest.java @@ -40,6 +40,7 @@ import org.apache.cassandra.distributed.api.Feature; import org.apache.cassandra.distributed.api.IInvokableInstance; import org.apache.cassandra.metrics.CassandraMetricsRegistry; import org.apache.cassandra.service.QueryState; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.transport.Envelope; import org.apache.cassandra.transport.Message; import org.apache.cassandra.transport.ProtocolVersion; @@ -262,7 +263,7 @@ public class UnableToParseClientMessageTest extends TestBaseImpl } @Override - protected Response execute(QueryState queryState, long queryStartNanoTime, boolean traceRequest) + protected Response execute(QueryState queryState, Dispatcher.RequestTime requestTime, boolean traceRequest) { throw new AssertionError("execute not supported"); } diff --git a/test/distributed/org/apache/cassandra/distributed/test/metrics/RequestTimeoutTest.java b/test/distributed/org/apache/cassandra/distributed/test/metrics/RequestTimeoutTest.java index 2799fca66a..8d519fd4ba 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/metrics/RequestTimeoutTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/metrics/RequestTimeoutTest.java @@ -23,7 +23,6 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Map; import java.util.concurrent.Callable; -import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; @@ -53,7 +52,6 @@ import org.apache.cassandra.exceptions.ReadTimeoutException; import org.apache.cassandra.exceptions.WriteTimeoutException; import org.apache.cassandra.net.Verb; import org.apache.cassandra.service.paxos.Paxos; -import org.apache.cassandra.utils.concurrent.Awaitable; import org.apache.cassandra.utils.concurrent.Condition; import org.assertj.core.api.Assertions; @@ -81,6 +79,11 @@ public class RequestTimeoutTest extends TestBaseImpl @AfterClass public static void cleanup() { + CLUSTER.forEach(i -> { + i.runOnInstance(() -> { + BB.ENABLED = false; + }); + }); if (CLUSTER != null) CLUSTER.close(); } @@ -191,12 +194,14 @@ public class RequestTimeoutTest extends TestBaseImpl public static class BB { + private static boolean ENABLED = true; + public static void install(ClassLoader cl, int num) { if (num != COORDINATOR) return; new ByteBuddy().rebase(Condition.Async.class) - .method(named("await").and(takesArguments(2))) + .method(named("awaitUntil").and(takesArguments(long.class))) .intercept(MethodDelegation.to(BB.class)) .make() .load(cl, ClassLoadingStrategy.Default.INJECTION); @@ -208,14 +213,21 @@ public class RequestTimeoutTest extends TestBaseImpl .load(cl, ClassLoadingStrategy.Default.INJECTION); } - public static boolean await(long time, TimeUnit units, @This Awaitable self, @SuperMethod Method method) throws InterruptedException, InvocationTargetException, IllegalAccessException + public static boolean awaitUntil(long deadlineNanos, @This Condition.Async self, @SuperMethod Method method) throws InterruptedException, InvocationTargetException, IllegalAccessException { + if (!ENABLED) + return (boolean) method.invoke(self, deadlineNanos); + + boolean res = false; // make sure that the underline condition is met before returnning true // this way its know that the timeouts triggered! - while (!((boolean) method.invoke(self, time, units))) + while (ENABLED) { + res = (boolean) method.invoke(self, deadlineNanos); + if (res) + return true; } - return true; + return res; } private static final AtomicInteger TIMEOUTS = new AtomicInteger(0); diff --git a/test/distributed/org/apache/cassandra/distributed/test/ring/ReadsDuringBootstrapTest.java b/test/distributed/org/apache/cassandra/distributed/test/ring/ReadsDuringBootstrapTest.java index ba2cdc520e..9f194f6805 100644 --- a/test/distributed/org/apache/cassandra/distributed/test/ring/ReadsDuringBootstrapTest.java +++ b/test/distributed/org/apache/cassandra/distributed/test/ring/ReadsDuringBootstrapTest.java @@ -62,6 +62,7 @@ public class ReadsDuringBootstrapTest extends TestBaseImpl .withNodeIdTopology(NetworkTopology.singleDcNetworkTopology(expandedNodeCount, "dc0", "rack0")) .withConfig(config -> config.with(NETWORK, GOSSIP) .set("read_request_timeout", String.format("%dms", Integer.MAX_VALUE)) + .set("native_transport_timeout", String.format("%dms", Integer.MAX_VALUE)) .set("request_timeout", String.format("%dms", Integer.MAX_VALUE))) .withInstanceInitializer(BB::install) .start()) diff --git a/test/microbench/org/apache/cassandra/test/microbench/BatchStatementBench.java b/test/microbench/org/apache/cassandra/test/microbench/BatchStatementBench.java index ebd663d50c..16dbbda72a 100644 --- a/test/microbench/org/apache/cassandra/test/microbench/BatchStatementBench.java +++ b/test/microbench/org/apache/cassandra/test/microbench/BatchStatementBench.java @@ -43,6 +43,7 @@ import org.apache.cassandra.schema.Schema; import org.apache.cassandra.schema.SchemaTestUtil; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.service.ClientState; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.utils.FBUtilities; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; @@ -64,8 +65,6 @@ import org.openjdk.jmh.runner.options.Options; import org.openjdk.jmh.runner.options.OptionsBuilder; import static org.apache.cassandra.utils.ByteBufferUtil.bytes; -import static org.apache.cassandra.utils.Clock.Global.nanoTime; - @BenchmarkMode(Mode.Throughput) @OutputTimeUnit(TimeUnit.MILLISECONDS) @@ -89,7 +88,7 @@ public class BatchStatementBench String table = "tbl"; long nowInSec = FBUtilities.nowInSeconds(); - long queryStartTime = nanoTime(); + Dispatcher.RequestTime queryStartTime = Dispatcher.RequestTime.forImmediateExecution(); BatchStatement bs; BatchQueryOptions bqo; diff --git a/test/unit/org/apache/cassandra/config/DatabaseDescriptorRefTest.java b/test/unit/org/apache/cassandra/config/DatabaseDescriptorRefTest.java index b4da790360..e763e738d9 100644 --- a/test/unit/org/apache/cassandra/config/DatabaseDescriptorRefTest.java +++ b/test/unit/org/apache/cassandra/config/DatabaseDescriptorRefTest.java @@ -82,6 +82,7 @@ public class DatabaseDescriptorRefTest "org.apache.cassandra.config.Config", "org.apache.cassandra.config.Config$1", "org.apache.cassandra.config.Config$CommitFailurePolicy", + "org.apache.cassandra.config.Config$CQLStartTime", "org.apache.cassandra.config.Config$CommitLogSync", "org.apache.cassandra.config.Config$CorruptedTombstoneStrategy", "org.apache.cassandra.config.Config$DiskAccessMode", diff --git a/test/unit/org/apache/cassandra/cql3/CustomNowInSecondsTest.java b/test/unit/org/apache/cassandra/cql3/CustomNowInSecondsTest.java index c24540970a..dd18ac7864 100644 --- a/test/unit/org/apache/cassandra/cql3/CustomNowInSecondsTest.java +++ b/test/unit/org/apache/cassandra/cql3/CustomNowInSecondsTest.java @@ -35,6 +35,7 @@ import org.apache.cassandra.db.rows.Cell; import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.QueryState; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.transport.messages.ResultMessage; import org.apache.cassandra.utils.FBUtilities; @@ -42,7 +43,6 @@ import org.assertj.core.api.Assertions; import static java.lang.String.format; import static java.util.Collections.emptyMap; -import static org.apache.cassandra.utils.Clock.Global.nanoTime; import static org.junit.Assert.assertEquals; public class CustomNowInSecondsTest extends CQLTester @@ -207,7 +207,7 @@ public class CustomNowInSecondsTest extends CQLTester new BatchStatement(BatchStatement.Type.UNLOGGED, VariableSpecifications.empty(), statements, Attributes.none()); // execute an BATCH message with now set to [now + 1 day], with ttl = 1, making its effective ttl = 1 day + 1. - QueryProcessor.instance.processBatch(batch, qs, batchQueryOptions(now + day), emptyMap(), nanoTime()); + QueryProcessor.instance.processBatch(batch, qs, batchQueryOptions(now + day), emptyMap(), Dispatcher.RequestTime.forImmediateExecution()); // verify that despite TTL having passed at now + 1 the rows are still there. assertEquals(2, executeSelect(format("SELECT * FROM %s.%s", ks, tbl), now + 1, false).size()); @@ -236,12 +236,12 @@ public class CustomNowInSecondsTest extends CQLTester if (prepared) { CQLStatement statement = QueryProcessor.parseStatement(query, cs); - return QueryProcessor.instance.processPrepared(statement, qs, queryOptions(nowInSeconds), emptyMap(), nanoTime()); + return QueryProcessor.instance.processPrepared(statement, qs, queryOptions(nowInSeconds), emptyMap(), Dispatcher.RequestTime.forImmediateExecution()); } else { CQLStatement statement = QueryProcessor.instance.parse(query, qs, queryOptions(nowInSeconds)); - return QueryProcessor.instance.process(statement, qs, queryOptions(nowInSeconds), emptyMap(), nanoTime()); + return QueryProcessor.instance.process(statement, qs, queryOptions(nowInSeconds), emptyMap(), Dispatcher.RequestTime.forImmediateExecution()); } } diff --git a/test/unit/org/apache/cassandra/cql3/PstmtPersistenceTest.java b/test/unit/org/apache/cassandra/cql3/PstmtPersistenceTest.java index 2d0ea3b1ea..c799a286ab 100644 --- a/test/unit/org/apache/cassandra/cql3/PstmtPersistenceTest.java +++ b/test/unit/org/apache/cassandra/cql3/PstmtPersistenceTest.java @@ -32,13 +32,14 @@ import org.apache.cassandra.db.marshal.UTF8Type; import org.apache.cassandra.schema.SchemaConstants; import org.apache.cassandra.schema.SchemaKeyspaceTables; import org.apache.cassandra.service.ClientState; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.utils.ByteBufferUtil; import org.apache.cassandra.utils.MD5Digest; import static java.util.Collections.emptyMap; import static org.apache.cassandra.service.QueryState.forInternalCalls; -import static org.apache.cassandra.utils.Clock.Global.nanoTime; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; public class PstmtPersistenceTest extends CQLTester { @@ -132,7 +133,7 @@ public class PstmtPersistenceTest extends CQLTester { QueryProcessor.Prepared prepared = handler.getPrepared(stmtId); Assert.assertNotNull(prepared); - handler.processPrepared(prepared.statement, forInternalCalls(), options, emptyMap(), nanoTime()); + handler.processPrepared(prepared.statement, forInternalCalls(), options, emptyMap(), Dispatcher.RequestTime.forImmediateExecution()); } @Test diff --git a/test/unit/org/apache/cassandra/cql3/statements/AddIdentityStatementTest.java b/test/unit/org/apache/cassandra/cql3/statements/AddIdentityStatementTest.java index f90685782c..ca91ea1c32 100644 --- a/test/unit/org/apache/cassandra/cql3/statements/AddIdentityStatementTest.java +++ b/test/unit/org/apache/cassandra/cql3/statements/AddIdentityStatementTest.java @@ -41,6 +41,7 @@ import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.exceptions.UnauthorizedException; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.QueryState; +import org.apache.cassandra.transport.Dispatcher; import static org.apache.cassandra.auth.AuthKeyspace.IDENTITY_TO_ROLES; import static org.apache.cassandra.schema.SchemaConstants.AUTH_KEYSPACE_NAME; @@ -97,17 +98,17 @@ public class AddIdentityStatementTest @Test public void testAddingValidIdentity() { - QueryProcessor.process(ADD_QUERY, ConsistencyLevel.QUORUM, getClientState(), 10L); + QueryProcessor.process(ADD_QUERY, ConsistencyLevel.QUORUM, getClientState(), Dispatcher.RequestTime.forImmediateExecution()); assertEquals(USER_ROLE, DatabaseDescriptor.getRoleManager().roleForIdentity(IDENTITY)); } @Test public void testAddingExistingIdentity() { - QueryProcessor.process(ADD_QUERY, ConsistencyLevel.QUORUM, getClientState(), 10L); + QueryProcessor.process(ADD_QUERY, ConsistencyLevel.QUORUM, getClientState(), Dispatcher.RequestTime.forImmediateExecution()); expectedException.expect(InvalidRequestException.class); expectedException.expectMessage(IDENTITY +" already exists"); - QueryProcessor.process(ADD_QUERY, ConsistencyLevel.QUORUM, getClientState(), 10L); + QueryProcessor.process(ADD_QUERY, ConsistencyLevel.QUORUM, getClientState(), Dispatcher.RequestTime.forImmediateExecution()); } @Test @@ -133,7 +134,7 @@ public class AddIdentityStatementTest String query = String.format("ADD IDENTITY '%s' TO ROLE 'non-existing-role';", IDENTITY); expectedException.expect(InvalidRequestException.class); expectedException.expectMessage("Can not add identity for non-existent role 'non-existing-role'"); - QueryProcessor.process(query, ConsistencyLevel.QUORUM, getClientState(), 10L); + QueryProcessor.process(query, ConsistencyLevel.QUORUM, getClientState(), Dispatcher.RequestTime.forImmediateExecution()); } @Test @@ -148,7 +149,7 @@ public class AddIdentityStatementTest String query = String.format("ADD IDENTITY '%s' TO ROLE 'readwrite_user';", IDENTITY); expectedException.expect(UnauthorizedException.class); expectedException.expectMessage("User readwrite_user does not have sufficient privileges to perform the requested operation"); - QueryProcessor.process(query, ConsistencyLevel.QUORUM, new QueryState(state), 10L); + QueryProcessor.process(query, ConsistencyLevel.QUORUM, new QueryState(state), Dispatcher.RequestTime.forImmediateExecution()); } @@ -171,23 +172,23 @@ public class AddIdentityStatementTest String addQueryWithIfNotExists = String.format("ADD IDENTITY IF NOT EXISTS '%s' TO ROLE '%s';", IDENTITY, USER_ROLE); // Identity not in the table & add identity query with IF NOT EXISTS should succeed - QueryProcessor.process(addQueryWithIfNotExists, ConsistencyLevel.QUORUM, getClientState(), 10L); + QueryProcessor.process(addQueryWithIfNotExists, ConsistencyLevel.QUORUM, getClientState(), Dispatcher.RequestTime.forImmediateExecution()); assertEquals(USER_ROLE, DatabaseDescriptor.getRoleManager().roleForIdentity(IDENTITY)); // Identity in the table & add identity query with IF NOT EXISTS should succeed - QueryProcessor.process(addQueryWithIfNotExists, ConsistencyLevel.QUORUM, getClientState(), 10L); + QueryProcessor.process(addQueryWithIfNotExists, ConsistencyLevel.QUORUM, getClientState(), Dispatcher.RequestTime.forImmediateExecution()); assertEquals(USER_ROLE, DatabaseDescriptor.getRoleManager().roleForIdentity(IDENTITY)); clear(); String addQueryWithOutIfNotExists = String.format("ADD IDENTITY '%s' TO ROLE '%s';", IDENTITY, USER_ROLE); // Identity not in the table & add identity query without IF NOT EXISTS should succeed - QueryProcessor.process(addQueryWithOutIfNotExists, ConsistencyLevel.QUORUM, getClientState(), 10L); + QueryProcessor.process(addQueryWithOutIfNotExists, ConsistencyLevel.QUORUM, getClientState(), Dispatcher.RequestTime.forImmediateExecution()); assertEquals(USER_ROLE, DatabaseDescriptor.getRoleManager().roleForIdentity(IDENTITY)); // Identity in the table & add identity query without IF NOT EXISTS should fail expectedException.expect(InvalidRequestException.class); expectedException.expectMessage(IDENTITY + " already exists"); - QueryProcessor.process(addQueryWithOutIfNotExists, ConsistencyLevel.QUORUM, getClientState(), 10L); + QueryProcessor.process(addQueryWithOutIfNotExists, ConsistencyLevel.QUORUM, getClientState(), Dispatcher.RequestTime.forImmediateExecution()); } static QueryState getClientState() diff --git a/test/unit/org/apache/cassandra/cql3/statements/DropIdentityStatementTest.java b/test/unit/org/apache/cassandra/cql3/statements/DropIdentityStatementTest.java index 7521c95e85..2a96c70c2e 100644 --- a/test/unit/org/apache/cassandra/cql3/statements/DropIdentityStatementTest.java +++ b/test/unit/org/apache/cassandra/cql3/statements/DropIdentityStatementTest.java @@ -39,6 +39,7 @@ import org.apache.cassandra.exceptions.InvalidRequestException; import org.apache.cassandra.exceptions.UnauthorizedException; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.QueryState; +import org.apache.cassandra.transport.Dispatcher; import static org.apache.cassandra.auth.AuthKeyspace.IDENTITY_TO_ROLES; import static org.apache.cassandra.cql3.statements.AddIdentityStatementTest.defineSchema; @@ -83,7 +84,7 @@ public class DropIdentityStatementTest public void testDroppingValidIdentity() { DatabaseDescriptor.getRoleManager().addIdentity(IDENTITY, "cassandra"); - QueryProcessor.process(DROP_QUERY, ConsistencyLevel.QUORUM, getClientState(), 10L); + QueryProcessor.process(DROP_QUERY, ConsistencyLevel.QUORUM, getClientState(), Dispatcher.RequestTime.forImmediateExecution()); assertFalse(DatabaseDescriptor.getRoleManager().isExistingIdentity(IDENTITY)); } @@ -107,7 +108,7 @@ public class DropIdentityStatementTest String query = String.format("DROP IDENTITY '%s';", IDENTITY); expectedException.expect(UnauthorizedException.class); expectedException.expectMessage("User readwrite_user does not have sufficient privileges to perform the requested operation"); - QueryProcessor.process(query, ConsistencyLevel.QUORUM, new QueryState(state), 10L); + QueryProcessor.process(query, ConsistencyLevel.QUORUM, new QueryState(state), Dispatcher.RequestTime.forImmediateExecution()); } @Test @@ -122,7 +123,7 @@ public class DropIdentityStatementTest // Dropping role should remove identities associated with it String query = "DROP ROLE readwrite_user"; - QueryProcessor.process(query, ConsistencyLevel.QUORUM, getClientState(), 10L); + QueryProcessor.process(query, ConsistencyLevel.QUORUM, getClientState(), Dispatcher.RequestTime.forImmediateExecution()); Map m = DatabaseDescriptor.getRoleManager().authorizedIdentities(); assertFalse(m.containsKey(IDENTITY)); @@ -139,19 +140,19 @@ public class DropIdentityStatementTest // Identity in the table & IF EXISTS in query should succeed DatabaseDescriptor.getRoleManager().addIdentity(IDENTITY, "cassandra"); - QueryProcessor.process(dropQueryWithIfExists, ConsistencyLevel.QUORUM, getClientState(), 10L); + QueryProcessor.process(dropQueryWithIfExists, ConsistencyLevel.QUORUM, getClientState(), Dispatcher.RequestTime.forImmediateExecution()); // Identity not in the table & IF EXISTS in query should succeed - QueryProcessor.process(dropQueryWithIfExists, ConsistencyLevel.QUORUM, getClientState(), 10L); + QueryProcessor.process(dropQueryWithIfExists, ConsistencyLevel.QUORUM, getClientState(), Dispatcher.RequestTime.forImmediateExecution()); String dropQueryWithOutIfExists = String.format("DROP IDENTITY '%s'", IDENTITY); // Identity in the table & no IF EXISTS in query should succeed DatabaseDescriptor.getRoleManager().addIdentity(IDENTITY, "cassandra"); - QueryProcessor.process(dropQueryWithOutIfExists, ConsistencyLevel.QUORUM, getClientState(), 10L); + QueryProcessor.process(dropQueryWithOutIfExists, ConsistencyLevel.QUORUM, getClientState(), Dispatcher.RequestTime.forImmediateExecution()); // Identity not in the table & no IF EXISTS in query should fail expectedException.expect(InvalidRequestException.class); expectedException.expectMessage(String.format("identity '%s' doesn't exist", IDENTITY)); - QueryProcessor.process(dropQueryWithOutIfExists, ConsistencyLevel.QUORUM, getClientState(), 10L); + QueryProcessor.process(dropQueryWithOutIfExists, ConsistencyLevel.QUORUM, getClientState(), Dispatcher.RequestTime.forImmediateExecution()); } } diff --git a/test/unit/org/apache/cassandra/db/guardrails/GuardrailTester.java b/test/unit/org/apache/cassandra/db/guardrails/GuardrailTester.java index f2744abf57..58860d828f 100644 --- a/test/unit/org/apache/cassandra/db/guardrails/GuardrailTester.java +++ b/test/unit/org/apache/cassandra/db/guardrails/GuardrailTester.java @@ -55,9 +55,9 @@ import org.apache.cassandra.index.sasi.SASIIndex; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.ClientWarn; import org.apache.cassandra.service.QueryState; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.transport.ProtocolVersion; import org.apache.cassandra.transport.messages.ResultMessage; -import org.apache.cassandra.utils.Clock; import org.assertj.core.api.Assertions; import static java.lang.String.format; @@ -529,7 +529,7 @@ public abstract class GuardrailTester extends CQLTester CQLStatement statement = QueryProcessor.parseStatement(formattedQuery, queryState.getClientState()); statement.validate(state); - return statement.execute(queryState, options, Clock.Global.nanoTime()); + return statement.execute(queryState, options, Dispatcher.RequestTime.forImmediateExecution()); } protected static String sortCSV(String csv) diff --git a/test/unit/org/apache/cassandra/index/sai/cql/VectorInvalidQueryTest.java b/test/unit/org/apache/cassandra/index/sai/cql/VectorInvalidQueryTest.java index e26f6d9f83..f74c72c897 100644 --- a/test/unit/org/apache/cassandra/index/sai/cql/VectorInvalidQueryTest.java +++ b/test/unit/org/apache/cassandra/index/sai/cql/VectorInvalidQueryTest.java @@ -35,6 +35,7 @@ import org.apache.cassandra.index.sai.disk.v1.IndexWriterConfig; import org.apache.cassandra.service.ClientState; import org.apache.cassandra.service.ClientWarn; import org.apache.cassandra.service.QueryState; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.transport.messages.ResultMessage; import org.junit.BeforeClass; @@ -422,6 +423,6 @@ public class VectorInvalidQueryTest extends SAITester QueryOptions options = QueryOptions.withConsistencyLevel(QueryOptions.forInternalCalls(Collections.emptyList()), consistencyLevel); options = QueryOptions.withPageSize(options, pageSize); - return ((ResultMessage.Rows)statement.execute(queryState, options, System.nanoTime())).result; + return ((ResultMessage.Rows)statement.execute(queryState, options, Dispatcher.RequestTime.forImmediateExecution())).result; } } diff --git a/test/unit/org/apache/cassandra/net/ConnectionTest.java b/test/unit/org/apache/cassandra/net/ConnectionTest.java index 778dc41fc8..1e24dd6375 100644 --- a/test/unit/org/apache/cassandra/net/ConnectionTest.java +++ b/test/unit/org/apache/cassandra/net/ConnectionTest.java @@ -751,7 +751,7 @@ public class ConnectionTest // The reserved capacity (pendingBytes) at the end of the round should equal to K - N * M, // which you can find in the assertion. test((inbound, outbound, endpoint) -> { - // max capacity equals to permit-free sendQueueCapcity + the minimun of endpoint and global reserve + // max capacity equals to permit-free sendQueueCapcaity + the minimun of endpoint and global reserve double maxSendQueueCapacity = outbound.settings().applicationSendQueueCapacityInBytes + Double.min(outbound.settings().applicationSendQueueReserveEndpointCapacityInBytes, outbound.settings().applicationSendQueueReserveGlobalCapacityInBytes.limit()); diff --git a/test/unit/org/apache/cassandra/service/WriteResponseHandlerTest.java b/test/unit/org/apache/cassandra/service/WriteResponseHandlerTest.java index 7a9bbf33c8..3d639ae29d 100644 --- a/test/unit/org/apache/cassandra/service/WriteResponseHandlerTest.java +++ b/test/unit/org/apache/cassandra/service/WriteResponseHandlerTest.java @@ -47,6 +47,7 @@ import org.apache.cassandra.locator.TokenMetadata; import org.apache.cassandra.net.Message; import org.apache.cassandra.net.Verb; import org.apache.cassandra.schema.KeyspaceParams; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.utils.ByteBufferUtil; import static java.util.concurrent.TimeUnit.DAYS; @@ -147,7 +148,7 @@ public class WriteResponseHandlerTest { long startingCount = ks.metric.idealCLWriteLatency.latency.getCount(); //Specify query start time in past to ensure minimum latency measurement - AbstractWriteResponseHandler awr = createWriteResponseHandler(ConsistencyLevel.LOCAL_QUORUM, ConsistencyLevel.EACH_QUORUM, nanoTime() - DAYS.toNanos(1)); + AbstractWriteResponseHandler awr = createWriteResponseHandler(ConsistencyLevel.LOCAL_QUORUM, ConsistencyLevel.EACH_QUORUM, new Dispatcher.RequestTime(nanoTime() - DAYS.toNanos(1))); //dc1 awr.onResponse(createDummyMessage(0)); @@ -262,13 +263,13 @@ public class WriteResponseHandlerTest private static AbstractWriteResponseHandler createWriteResponseHandler(ConsistencyLevel cl, ConsistencyLevel ideal) { - return createWriteResponseHandler(cl, ideal, nanoTime()); + return createWriteResponseHandler(cl, ideal, Dispatcher.RequestTime.forImmediateExecution()); } - private static AbstractWriteResponseHandler createWriteResponseHandler(ConsistencyLevel cl, ConsistencyLevel ideal, long queryStartTime) + private static AbstractWriteResponseHandler createWriteResponseHandler(ConsistencyLevel cl, ConsistencyLevel ideal, Dispatcher.RequestTime requestTime) { return ks.getReplicationStrategy().getWriteResponseHandler(ReplicaPlans.forWrite(ks, cl, targets, pending, Predicates.alwaysTrue(), ReplicaPlans.writeAll), - null, WriteType.SIMPLE, null, queryStartTime, ideal); + null, WriteType.SIMPLE, null, requestTime, ideal); } private static Message createDummyMessage(int target) diff --git a/test/unit/org/apache/cassandra/service/reads/DataResolverTest.java b/test/unit/org/apache/cassandra/service/reads/DataResolverTest.java index 24504296ad..c481a2cb2d 100644 --- a/test/unit/org/apache/cassandra/service/reads/DataResolverTest.java +++ b/test/unit/org/apache/cassandra/service/reads/DataResolverTest.java @@ -70,6 +70,7 @@ import org.apache.cassandra.service.reads.repair.ReadRepair; import org.apache.cassandra.service.reads.repair.RepairedDataTracker; import org.apache.cassandra.service.reads.repair.RepairedDataVerifier; import org.apache.cassandra.service.reads.repair.TestableReadRepair; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.utils.ByteBufferUtil; import static org.apache.cassandra.Util.assertClustering; @@ -77,7 +78,6 @@ import static org.apache.cassandra.Util.assertColumn; import static org.apache.cassandra.Util.assertColumns; import static org.apache.cassandra.db.ClusteringBound.Kind; import static org.apache.cassandra.db.ConsistencyLevel.ALL; -import static org.apache.cassandra.utils.Clock.Global.nanoTime; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -133,7 +133,7 @@ public class DataResolverTest extends AbstractReadResponseTest public void testResolveNewerSingleRow() { EndpointsForRange replicas = makeReplicas(2); - DataResolver resolver = new DataResolver(command, plan(replicas, ALL), readRepair, nanoTime()); + DataResolver resolver = new DataResolver(command, plan(replicas, ALL), readRepair, Dispatcher.RequestTime.forImmediateExecution()); InetAddressAndPort peer1 = replicas.get(0).endpoint(); resolver.preprocess(response(command, peer1, iter(new RowUpdateBuilder(cfm, nowInSec, 0L, dk).clustering("1") .add("c1", "v1") @@ -165,7 +165,7 @@ public class DataResolverTest extends AbstractReadResponseTest public void testResolveDisjointSingleRow() { EndpointsForRange replicas = makeReplicas(2); - DataResolver resolver = new DataResolver(command, plan(replicas, ALL), readRepair, nanoTime()); + DataResolver resolver = new DataResolver(command, plan(replicas, ALL), readRepair, Dispatcher.RequestTime.forImmediateExecution()); InetAddressAndPort peer1 = replicas.get(0).endpoint(); resolver.preprocess(response(command, peer1, iter(new RowUpdateBuilder(cfm, nowInSec, 0L, dk).clustering("1") .add("c1", "v1") @@ -202,7 +202,7 @@ public class DataResolverTest extends AbstractReadResponseTest public void testResolveDisjointMultipleRows() throws UnknownHostException { EndpointsForRange replicas = makeReplicas(2); - DataResolver resolver = new DataResolver(command, plan(replicas, ALL), readRepair, nanoTime()); + DataResolver resolver = new DataResolver(command, plan(replicas, ALL), readRepair, Dispatcher.RequestTime.forImmediateExecution()); InetAddressAndPort peer1 = replicas.get(0).endpoint(); resolver.preprocess(response(command, peer1, iter(new RowUpdateBuilder(cfm, nowInSec, 0L, dk).clustering("1") .add("c1", "v1") @@ -249,7 +249,7 @@ public class DataResolverTest extends AbstractReadResponseTest public void testResolveDisjointMultipleRowsWithRangeTombstones() { EndpointsForRange replicas = makeReplicas(4); - DataResolver resolver = new DataResolver(command, plan(replicas, ALL), readRepair, nanoTime()); + DataResolver resolver = new DataResolver(command, plan(replicas, ALL), readRepair, Dispatcher.RequestTime.forImmediateExecution()); RangeTombstone tombstone1 = tombstone("1", "11", 1, nowInSec); RangeTombstone tombstone2 = tombstone("3", "31", 1, nowInSec); @@ -330,7 +330,7 @@ public class DataResolverTest extends AbstractReadResponseTest public void testResolveWithOneEmpty() { EndpointsForRange replicas = makeReplicas(2); - DataResolver resolver = new DataResolver(command, plan(replicas, ALL), readRepair, nanoTime()); + DataResolver resolver = new DataResolver(command, plan(replicas, ALL), readRepair, Dispatcher.RequestTime.forImmediateExecution()); InetAddressAndPort peer1 = replicas.get(0).endpoint(); resolver.preprocess(response(command, peer1, iter(new RowUpdateBuilder(cfm, nowInSec, 1L, dk).clustering("1") .add("c2", "v2") @@ -361,7 +361,7 @@ public class DataResolverTest extends AbstractReadResponseTest { EndpointsForRange replicas = makeReplicas(2); TestableReadRepair readRepair = new TestableReadRepair(command); - DataResolver resolver = new DataResolver(command, plan(replicas, ALL), readRepair, nanoTime()); + DataResolver resolver = new DataResolver(command, plan(replicas, ALL), readRepair, Dispatcher.RequestTime.forImmediateExecution()); resolver.preprocess(response(command, replicas.get(0).endpoint(), EmptyIterators.unfilteredPartition(cfm))); resolver.preprocess(response(command, replicas.get(1).endpoint(), EmptyIterators.unfilteredPartition(cfm))); @@ -377,7 +377,7 @@ public class DataResolverTest extends AbstractReadResponseTest public void testResolveDeleted() { EndpointsForRange replicas = makeReplicas(2); - DataResolver resolver = new DataResolver(command, plan(replicas, ALL), readRepair, nanoTime()); + DataResolver resolver = new DataResolver(command, plan(replicas, ALL), readRepair, Dispatcher.RequestTime.forImmediateExecution()); // one response with columns timestamped before a delete in another response InetAddressAndPort peer1 = replicas.get(0).endpoint(); resolver.preprocess(response(command, peer1, iter(new RowUpdateBuilder(cfm, nowInSec, 0L, dk).clustering("1") @@ -403,7 +403,7 @@ public class DataResolverTest extends AbstractReadResponseTest public void testResolveMultipleDeleted() { EndpointsForRange replicas = makeReplicas(4); - DataResolver resolver = new DataResolver(command, plan(replicas, ALL), readRepair, nanoTime()); + DataResolver resolver = new DataResolver(command, plan(replicas, ALL), readRepair, Dispatcher.RequestTime.forImmediateExecution()); // deletes and columns with interleaved timestamp, with out of order return sequence InetAddressAndPort peer1 = replicas.get(0).endpoint(); resolver.preprocess(response(command, peer1, fullPartitionDelete(cfm, dk, 0, nowInSec))); @@ -488,7 +488,7 @@ public class DataResolverTest extends AbstractReadResponseTest private void resolveRangeTombstonesOnBoundary(long timestamp1, long timestamp2) { EndpointsForRange replicas = makeReplicas(2); - DataResolver resolver = new DataResolver(command, plan(replicas, ALL), readRepair, nanoTime()); + DataResolver resolver = new DataResolver(command, plan(replicas, ALL), readRepair, Dispatcher.RequestTime.forImmediateExecution()); InetAddressAndPort peer1 = replicas.get(0).endpoint(); InetAddressAndPort peer2 = replicas.get(1).endpoint(); @@ -562,7 +562,7 @@ public class DataResolverTest extends AbstractReadResponseTest private void testRepairRangeTombstoneBoundary(int timestamp1, int timestamp2, int timestamp3) throws UnknownHostException { EndpointsForRange replicas = makeReplicas(2); - DataResolver resolver = new DataResolver(command, plan(replicas, ALL), readRepair, nanoTime()); + DataResolver resolver = new DataResolver(command, plan(replicas, ALL), readRepair, Dispatcher.RequestTime.forImmediateExecution()); InetAddressAndPort peer1 = replicas.get(0).endpoint(); InetAddressAndPort peer2 = replicas.get(1).endpoint(); @@ -615,7 +615,7 @@ public class DataResolverTest extends AbstractReadResponseTest { EndpointsForRange replicas = makeReplicas(2); - DataResolver resolver = new DataResolver(command, plan(replicas, ALL), readRepair, nanoTime()); + DataResolver resolver = new DataResolver(command, plan(replicas, ALL), readRepair, Dispatcher.RequestTime.forImmediateExecution()); InetAddressAndPort peer1 = replicas.get(0).endpoint(); InetAddressAndPort peer2 = replicas.get(1).endpoint(); @@ -654,7 +654,7 @@ public class DataResolverTest extends AbstractReadResponseTest public void testRepairRangeTombstoneWithPartitionDeletion2() { EndpointsForRange replicas = makeReplicas(2); - DataResolver resolver = new DataResolver(command, plan(replicas, ALL), readRepair, nanoTime()); + DataResolver resolver = new DataResolver(command, plan(replicas, ALL), readRepair, Dispatcher.RequestTime.forImmediateExecution()); InetAddressAndPort peer1 = replicas.get(0).endpoint(); InetAddressAndPort peer2 = replicas.get(1).endpoint(); @@ -738,7 +738,7 @@ public class DataResolverTest extends AbstractReadResponseTest EndpointsForRange replicas = makeReplicas(2); ReadCommand cmd = Util.cmd(cfs2, dk).withNowInSeconds(nowInSec).build(); TestableReadRepair readRepair = new TestableReadRepair(cmd); - DataResolver resolver = new DataResolver(cmd, plan(replicas, ALL), readRepair, nanoTime()); + DataResolver resolver = new DataResolver(cmd, plan(replicas, ALL), readRepair, Dispatcher.RequestTime.forImmediateExecution()); long[] ts = {100, 200}; @@ -790,7 +790,7 @@ public class DataResolverTest extends AbstractReadResponseTest EndpointsForRange replicas = makeReplicas(2); ReadCommand cmd = Util.cmd(cfs2, dk).withNowInSeconds(nowInSec).build(); TestableReadRepair readRepair = new TestableReadRepair(cmd); - DataResolver resolver = new DataResolver(cmd, plan(replicas, ALL), readRepair, nanoTime()); + DataResolver resolver = new DataResolver(cmd, plan(replicas, ALL), readRepair, Dispatcher.RequestTime.forImmediateExecution()); long[] ts = {100, 200}; @@ -834,7 +834,7 @@ public class DataResolverTest extends AbstractReadResponseTest EndpointsForRange replicas = makeReplicas(2); ReadCommand cmd = Util.cmd(cfs2, dk).withNowInSeconds(nowInSec).build(); TestableReadRepair readRepair = new TestableReadRepair(cmd); - DataResolver resolver = new DataResolver(cmd, plan(replicas, ALL), readRepair, nanoTime()); + DataResolver resolver = new DataResolver(cmd, plan(replicas, ALL), readRepair, Dispatcher.RequestTime.forImmediateExecution()); long[] ts = {100, 200}; @@ -884,7 +884,7 @@ public class DataResolverTest extends AbstractReadResponseTest EndpointsForRange replicas = makeReplicas(2); ReadCommand cmd = Util.cmd(cfs2, dk).withNowInSeconds(nowInSec).build(); TestableReadRepair readRepair = new TestableReadRepair(cmd); - DataResolver resolver = new DataResolver(cmd, plan(replicas, ALL), readRepair, nanoTime()); + DataResolver resolver = new DataResolver(cmd, plan(replicas, ALL), readRepair, Dispatcher.RequestTime.forImmediateExecution()); long[] ts = {100, 200}; @@ -940,7 +940,7 @@ public class DataResolverTest extends AbstractReadResponseTest verifier.expectDigest(peer1, digest1, true); verifier.expectDigest(peer2, digest1, true); - DataResolver resolver = resolverWithVerifier(command, plan(replicas, ALL), readRepair, nanoTime(), verifier); + DataResolver resolver = resolverWithVerifier(command, plan(replicas, ALL), readRepair, Dispatcher.RequestTime.forImmediateExecution(), verifier); resolver.preprocess(response(peer1, iter(PartitionUpdate.emptyUpdate(cfm, dk)), digest1, true, command)); resolver.preprocess(response(peer2, iter(PartitionUpdate.emptyUpdate(cfm, dk)), digest1, true, command)); @@ -960,7 +960,7 @@ public class DataResolverTest extends AbstractReadResponseTest verifier.expectDigest(peer1, digest1, false); verifier.expectDigest(peer2, digest1, true); - DataResolver resolver = resolverWithVerifier(command, plan(replicas, ALL), readRepair, nanoTime(), verifier); + DataResolver resolver = resolverWithVerifier(command, plan(replicas, ALL), readRepair, Dispatcher.RequestTime.forImmediateExecution(), verifier); resolver.preprocess(response(peer1, iter(PartitionUpdate.emptyUpdate(cfm, dk)), digest1, false, command)); resolver.preprocess(response(peer2, iter(PartitionUpdate.emptyUpdate(cfm, dk)), digest1, true, command)); @@ -980,7 +980,7 @@ public class DataResolverTest extends AbstractReadResponseTest verifier.expectDigest(peer1, digest1, false); verifier.expectDigest(peer2, digest1, false); - DataResolver resolver = resolverWithVerifier(command, plan(replicas, ALL), readRepair, nanoTime(), verifier); + DataResolver resolver = resolverWithVerifier(command, plan(replicas, ALL), readRepair, Dispatcher.RequestTime.forImmediateExecution(), verifier); resolver.preprocess(response(peer1, iter(PartitionUpdate.emptyUpdate(cfm, dk)), digest1, false, command)); resolver.preprocess(response(peer2, iter(PartitionUpdate.emptyUpdate(cfm, dk)), digest1, false, command)); @@ -1000,7 +1000,7 @@ public class DataResolverTest extends AbstractReadResponseTest verifier.expectDigest(peer1, digest1, true); verifier.expectDigest(peer2, digest1, true); - DataResolver resolver = resolverWithVerifier(command, plan(replicas, ALL), readRepair, nanoTime(), verifier); + DataResolver resolver = resolverWithVerifier(command, plan(replicas, ALL), readRepair, Dispatcher.RequestTime.forImmediateExecution(), verifier); resolver.preprocess(response(peer1, iter(PartitionUpdate.emptyUpdate(cfm,dk)), digest1, true, command)); resolver.preprocess(response(peer2, iter(PartitionUpdate.emptyUpdate(cfm, dk)), digest1, true, command)); @@ -1020,7 +1020,7 @@ public class DataResolverTest extends AbstractReadResponseTest verifier.expectDigest(peer1, digest1, true); verifier.expectDigest(peer2, digest1, false); - DataResolver resolver = resolverWithVerifier(command, plan(replicas, ALL), readRepair, nanoTime(), verifier); + DataResolver resolver = resolverWithVerifier(command, plan(replicas, ALL), readRepair, Dispatcher.RequestTime.forImmediateExecution(), verifier); resolver.preprocess(response(peer1, iter(PartitionUpdate.emptyUpdate(cfm,dk)), digest1, true, command)); resolver.preprocess(response(peer2, iter(PartitionUpdate.emptyUpdate(cfm, dk)), digest1, false, command)); @@ -1040,7 +1040,7 @@ public class DataResolverTest extends AbstractReadResponseTest verifier.expectDigest(peer1, digest1, false); verifier.expectDigest(peer2, digest1, false); - DataResolver resolver = resolverWithVerifier(command, plan(replicas, ALL), readRepair, nanoTime(), verifier); + DataResolver resolver = resolverWithVerifier(command, plan(replicas, ALL), readRepair, Dispatcher.RequestTime.forImmediateExecution(), verifier); resolver.preprocess(response(peer1, iter(PartitionUpdate.emptyUpdate(cfm, dk)), digest1, false, command)); resolver.preprocess(response(peer2, iter(PartitionUpdate.emptyUpdate(cfm, dk)), digest1, false, command)); @@ -1061,7 +1061,7 @@ public class DataResolverTest extends AbstractReadResponseTest verifier.expectDigest(peer1, digest1, true); verifier.expectDigest(peer2, digest1, true); - DataResolver resolver = resolverWithVerifier(command, plan(replicas, ALL), readRepair, nanoTime(), verifier); + DataResolver resolver = resolverWithVerifier(command, plan(replicas, ALL), readRepair, Dispatcher.RequestTime.forImmediateExecution(), verifier); resolver.preprocess(response(peer1, iter(new RowUpdateBuilder(cfm, nowInSec, 1L, dk).clustering("1") .buildUpdate()), digest1, true, command)); resolver.preprocess(response(peer2, iter(PartitionUpdate.emptyUpdate(cfm, dk)), digest1, true, command)); @@ -1082,7 +1082,7 @@ public class DataResolverTest extends AbstractReadResponseTest verifier.expectDigest(peer1, digest1, true); verifier.expectDigest(peer2, digest2, true); - DataResolver resolver = resolverWithVerifier(command, plan(replicas, ALL), readRepair, nanoTime(), verifier); + DataResolver resolver = resolverWithVerifier(command, plan(replicas, ALL), readRepair, Dispatcher.RequestTime.forImmediateExecution(), verifier); resolver.preprocess(response(peer1, iter(PartitionUpdate.emptyUpdate(cfm, dk)), digest1, true, command)); resolver.preprocess(response(peer2, iter(PartitionUpdate.emptyUpdate(cfm, dk)), digest2, true, command)); @@ -1103,7 +1103,7 @@ public class DataResolverTest extends AbstractReadResponseTest verifier.expectDigest(peer1, digest1, false); verifier.expectDigest(peer2, digest2, true); - DataResolver resolver = resolverWithVerifier(command, plan(replicas, ALL), readRepair, nanoTime(), verifier); + DataResolver resolver = resolverWithVerifier(command, plan(replicas, ALL), readRepair, Dispatcher.RequestTime.forImmediateExecution(), verifier); resolver.preprocess(response(peer1, iter(PartitionUpdate.emptyUpdate(cfm, dk)), digest1, false, command)); resolver.preprocess(response(peer2, iter(PartitionUpdate.emptyUpdate(cfm, dk)), digest2, true, command)); @@ -1124,7 +1124,7 @@ public class DataResolverTest extends AbstractReadResponseTest verifier.expectDigest(peer1, digest1, false); verifier.expectDigest(peer2, digest2, false); - DataResolver resolver = resolverWithVerifier(command, plan(replicas, ALL), readRepair, nanoTime(), verifier); + DataResolver resolver = resolverWithVerifier(command, plan(replicas, ALL), readRepair, Dispatcher.RequestTime.forImmediateExecution(), verifier); resolver.preprocess(response(peer1, iter(PartitionUpdate.emptyUpdate(cfm, dk)), digest1, false, command)); resolver.preprocess(response(peer2, iter(PartitionUpdate.emptyUpdate(cfm, dk)), digest2, false, command)); @@ -1145,7 +1145,7 @@ public class DataResolverTest extends AbstractReadResponseTest verifier.expectDigest(peer1, digest1, true); verifier.expectDigest(peer2, digest2, true); - DataResolver resolver = resolverWithVerifier(command, plan(replicas, ALL), readRepair, nanoTime(), verifier); + DataResolver resolver = resolverWithVerifier(command, plan(replicas, ALL), readRepair, Dispatcher.RequestTime.forImmediateExecution(), verifier); resolver.preprocess(response(peer1, iter(new RowUpdateBuilder(cfm, nowInSec, 1L, dk).clustering("1") .buildUpdate()), digest1, true, command)); resolver.preprocess(response(peer2, iter(PartitionUpdate.emptyUpdate(cfm, dk)), digest2, true, command)); @@ -1165,7 +1165,7 @@ public class DataResolverTest extends AbstractReadResponseTest TestRepairedDataVerifier verifier = new TestRepairedDataVerifier(); verifier.expectDigest(peer1, digest1, true); - DataResolver resolver = resolverWithVerifier(command, plan(replicas, ALL), readRepair, nanoTime(), verifier); + DataResolver resolver = resolverWithVerifier(command, plan(replicas, ALL), readRepair, Dispatcher.RequestTime.forImmediateExecution(), verifier); resolver.preprocess(response(peer1, iter(PartitionUpdate.emptyUpdate(cfm,dk)), digest1, true, command)); @@ -1187,7 +1187,7 @@ public class DataResolverTest extends AbstractReadResponseTest InetAddressAndPort peer2 = replicas.get(1).endpoint(); verifier.expectDigest(peer1, digest1, true); - DataResolver resolver = resolverWithVerifier(command, plan(replicas, ALL), readRepair, nanoTime(), verifier); + DataResolver resolver = resolverWithVerifier(command, plan(replicas, ALL), readRepair, Dispatcher.RequestTime.forImmediateExecution(), verifier); resolver.preprocess(response(peer1, iter(PartitionUpdate.emptyUpdate(cfm,dk)), digest1, true, command)); // peer2 is advertising an older version, so when we deserialize its response there are two things to note: @@ -1219,7 +1219,7 @@ public class DataResolverTest extends AbstractReadResponseTest InetAddressAndPort peer2 = replicas.get(1).endpoint(); verifier.expectDigest(peer1, digest1, true); - DataResolver resolver = resolverWithVerifier(command, plan(replicas, ALL), readRepair, nanoTime(), verifier); + DataResolver resolver = resolverWithVerifier(command, plan(replicas, ALL), readRepair, Dispatcher.RequestTime.forImmediateExecution(), verifier); resolver.preprocess(response(peer1, iter(PartitionUpdate.emptyUpdate(cfm,dk)), digest1, true, command)); resolver.preprocess(response(peer2, iter(PartitionUpdate.emptyUpdate(cfm,dk)), digest2, true, command)); @@ -1248,15 +1248,15 @@ public class DataResolverTest extends AbstractReadResponseTest private DataResolver resolverWithVerifier(final ReadCommand command, final ReplicaPlan.SharedForRangeRead plan, final ReadRepair readRepair, - final long queryStartNanoTime, + final Dispatcher.RequestTime requestTime, final RepairedDataVerifier verifier) { class TestableDataResolver extends DataResolver { - public TestableDataResolver(ReadCommand command, ReplicaPlan.SharedForRangeRead plan, ReadRepair readRepair, long queryStartNanoTime) + public TestableDataResolver(ReadCommand command, ReplicaPlan.SharedForRangeRead plan, ReadRepair readRepair, Dispatcher.RequestTime requestTime) { - super(command, plan, readRepair, queryStartNanoTime, true); + super(command, plan, readRepair, requestTime, true); } protected RepairedDataVerifier getRepairedDataVerifier(ReadCommand command) @@ -1265,7 +1265,7 @@ public class DataResolverTest extends AbstractReadResponseTest } } - return new TestableDataResolver(command, plan, readRepair, queryStartNanoTime); + return new TestableDataResolver(command, plan, readRepair, requestTime); } private void assertRepairContainsDeletions(Mutation mutation, diff --git a/test/unit/org/apache/cassandra/service/reads/DigestResolverTest.java b/test/unit/org/apache/cassandra/service/reads/DigestResolverTest.java index 36dd6d9a5c..290049c2e2 100644 --- a/test/unit/org/apache/cassandra/service/reads/DigestResolverTest.java +++ b/test/unit/org/apache/cassandra/service/reads/DigestResolverTest.java @@ -34,6 +34,7 @@ import org.apache.cassandra.db.partitions.PartitionUpdate; import org.apache.cassandra.db.rows.Row; import org.apache.cassandra.locator.EndpointsForToken; import org.apache.cassandra.schema.TableMetadata; +import org.apache.cassandra.transport.Dispatcher; import static org.apache.cassandra.locator.ReplicaUtils.full; import static org.apache.cassandra.locator.ReplicaUtils.trans; @@ -67,7 +68,7 @@ public class DigestResolverTest extends AbstractReadResponseTest { SinglePartitionReadCommand command = SinglePartitionReadCommand.fullPartitionRead(cfm, nowInSec, dk); EndpointsForToken targetReplicas = EndpointsForToken.of(dk.getToken(), full(EP1), full(EP2)); - DigestResolver resolver = new DigestResolver(command, plan(ConsistencyLevel.QUORUM, targetReplicas), 0); + DigestResolver resolver = new DigestResolver(command, plan(ConsistencyLevel.QUORUM, targetReplicas), new Dispatcher.RequestTime(0L, 0L)); PartitionUpdate response = update(row(1000, 4, 4), row(1000, 5, 5)).build(); @@ -99,8 +100,9 @@ public class DigestResolverTest extends AbstractReadResponseTest while (System.nanoTime() < endTime) { final long startNanos = System.nanoTime(); - final DigestResolver resolver = new DigestResolver<>(command, plan, startNanos); - final ReadCallback callback = new ReadCallback<>(resolver, command, plan, startNanos); + final Dispatcher.RequestTime requestTime = new Dispatcher.RequestTime(startNanos, startNanos); + final DigestResolver resolver = new DigestResolver<>(command, plan, requestTime); + final ReadCallback callback = new ReadCallback<>(resolver, command, plan, requestTime); final CountDownLatch startlatch = new CountDownLatch(2); @@ -134,7 +136,7 @@ public class DigestResolverTest extends AbstractReadResponseTest { SinglePartitionReadCommand command = SinglePartitionReadCommand.fullPartitionRead(cfm, nowInSec, dk); EndpointsForToken targetReplicas = EndpointsForToken.of(dk.getToken(), full(EP1), full(EP2)); - DigestResolver resolver = new DigestResolver(command, plan(ConsistencyLevel.QUORUM, targetReplicas), 0); + DigestResolver resolver = new DigestResolver(command, plan(ConsistencyLevel.QUORUM, targetReplicas), new Dispatcher.RequestTime(0L, 0L)); PartitionUpdate response1 = update(row(1000, 4, 4), row(1000, 5, 5)).build(); PartitionUpdate response2 = update(row(2000, 4, 5)).build(); @@ -155,7 +157,7 @@ public class DigestResolverTest extends AbstractReadResponseTest { SinglePartitionReadCommand command = SinglePartitionReadCommand.fullPartitionRead(cfm, nowInSec, dk); EndpointsForToken targetReplicas = EndpointsForToken.of(dk.getToken(), full(EP1), trans(EP2)); - DigestResolver resolver = new DigestResolver<>(command, plan(ConsistencyLevel.QUORUM, targetReplicas), 0); + DigestResolver resolver = new DigestResolver<>(command, plan(ConsistencyLevel.QUORUM, targetReplicas), new Dispatcher.RequestTime(0L, 0L)); PartitionUpdate response1 = update(row(1000, 4, 4), row(1000, 5, 5)).build(); PartitionUpdate response2 = update(row(1000, 5, 5)).build(); @@ -176,7 +178,7 @@ public class DigestResolverTest extends AbstractReadResponseTest { SinglePartitionReadCommand command = SinglePartitionReadCommand.fullPartitionRead(cfm, nowInSec, dk); EndpointsForToken targetReplicas = EndpointsForToken.of(dk.getToken(), full(EP1), trans(EP2)); - DigestResolver resolver = new DigestResolver<>(command, plan(ConsistencyLevel.QUORUM, targetReplicas), 0); + DigestResolver resolver = new DigestResolver<>(command, plan(ConsistencyLevel.QUORUM, targetReplicas), new Dispatcher.RequestTime(0L, 0L)); PartitionUpdate response2 = update(row(1000, 5, 5)).build(); Assert.assertFalse(resolver.isDataPresent()); @@ -191,7 +193,7 @@ public class DigestResolverTest extends AbstractReadResponseTest { SinglePartitionReadCommand command = SinglePartitionReadCommand.fullPartitionRead(cfm, nowInSec, dk); EndpointsForToken targetReplicas = EndpointsForToken.of(dk.getToken(), full(EP1), full(EP2), trans(EP3)); - DigestResolver resolver = new DigestResolver<>(command, plan(ConsistencyLevel.QUORUM, targetReplicas), 0); + DigestResolver resolver = new DigestResolver<>(command, plan(ConsistencyLevel.QUORUM, targetReplicas), new Dispatcher.RequestTime(0L, 0L)); PartitionUpdate fullResponse = update(row(1000, 1, 1)).build(); PartitionUpdate digestResponse = update(row(1000, 1, 1)).build(); diff --git a/test/unit/org/apache/cassandra/service/reads/ReadExecutorTest.java b/test/unit/org/apache/cassandra/service/reads/ReadExecutorTest.java index 6f6bf36cbf..0c4b2f685e 100644 --- a/test/unit/org/apache/cassandra/service/reads/ReadExecutorTest.java +++ b/test/unit/org/apache/cassandra/service/reads/ReadExecutorTest.java @@ -44,12 +44,12 @@ import org.apache.cassandra.net.Message; import org.apache.cassandra.net.NoPayload; import org.apache.cassandra.net.Verb; import org.apache.cassandra.schema.KeyspaceParams; +import org.apache.cassandra.transport.Dispatcher; import static java.util.concurrent.TimeUnit.DAYS; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.apache.cassandra.db.ConsistencyLevel.LOCAL_QUORUM; import static org.apache.cassandra.locator.ReplicaUtils.full; -import static org.apache.cassandra.utils.Clock.Global.nanoTime; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; @@ -95,7 +95,7 @@ public class ReadExecutorTest { assertEquals(0, cfs.metric.speculativeInsufficientReplicas.getCount()); assertEquals(0, ks.metric.speculativeInsufficientReplicas.getCount()); - AbstractReadExecutor executor = new AbstractReadExecutor.NeverSpeculatingReadExecutor(cfs, new MockSinglePartitionReadCommand(), plan(targets, LOCAL_QUORUM), nanoTime(), true); + AbstractReadExecutor executor = new AbstractReadExecutor.NeverSpeculatingReadExecutor(cfs, new MockSinglePartitionReadCommand(), plan(targets, LOCAL_QUORUM), Dispatcher.RequestTime.forImmediateExecution(), true); executor.maybeTryAdditionalReplicas(); try { @@ -110,7 +110,7 @@ public class ReadExecutorTest assertEquals(1, ks.metric.speculativeInsufficientReplicas.getCount()); //Shouldn't increment - executor = new AbstractReadExecutor.NeverSpeculatingReadExecutor(cfs, new MockSinglePartitionReadCommand(), plan(targets, LOCAL_QUORUM), nanoTime(), false); + executor = new AbstractReadExecutor.NeverSpeculatingReadExecutor(cfs, new MockSinglePartitionReadCommand(), plan(targets, LOCAL_QUORUM), Dispatcher.RequestTime.forImmediateExecution(), false); executor.maybeTryAdditionalReplicas(); try { @@ -136,7 +136,7 @@ public class ReadExecutorTest assertEquals(0, cfs.metric.speculativeFailedRetries.getCount()); assertEquals(0, ks.metric.speculativeRetries.getCount()); assertEquals(0, ks.metric.speculativeFailedRetries.getCount()); - AbstractReadExecutor executor = new AbstractReadExecutor.SpeculatingReadExecutor(cfs, new MockSinglePartitionReadCommand(DAYS.toMillis(365)), plan(LOCAL_QUORUM, targets, targets.subList(0, 2)), nanoTime()); + AbstractReadExecutor executor = new AbstractReadExecutor.SpeculatingReadExecutor(cfs, new MockSinglePartitionReadCommand(DAYS.toMillis(365)), plan(LOCAL_QUORUM, targets, targets.subList(0, 2)), Dispatcher.RequestTime.forImmediateExecution()); executor.maybeTryAdditionalReplicas(); new Thread() { @@ -177,7 +177,7 @@ public class ReadExecutorTest assertEquals(0, cfs.metric.speculativeFailedRetries.getCount()); assertEquals(0, ks.metric.speculativeRetries.getCount()); assertEquals(0, ks.metric.speculativeFailedRetries.getCount()); - AbstractReadExecutor executor = new AbstractReadExecutor.SpeculatingReadExecutor(cfs, new MockSinglePartitionReadCommand(), plan(LOCAL_QUORUM, targets, targets.subList(0, 2)), nanoTime()); + AbstractReadExecutor executor = new AbstractReadExecutor.SpeculatingReadExecutor(cfs, new MockSinglePartitionReadCommand(), plan(LOCAL_QUORUM, targets, targets.subList(0, 2)), Dispatcher.RequestTime.forImmediateExecution()); executor.maybeTryAdditionalReplicas(); try { @@ -203,7 +203,7 @@ public class ReadExecutorTest { MockSinglePartitionReadCommand command = new MockSinglePartitionReadCommand(TimeUnit.DAYS.toMillis(365)); ReplicaPlan.ForTokenRead plan = plan(ConsistencyLevel.LOCAL_ONE, targets, targets.subList(0, 1)); - AbstractReadExecutor executor = new AbstractReadExecutor.SpeculatingReadExecutor(cfs, command, plan, nanoTime()); + AbstractReadExecutor executor = new AbstractReadExecutor.SpeculatingReadExecutor(cfs, command, plan, Dispatcher.RequestTime.forImmediateExecution()); // Issue an initial request against the first endpoint... executor.executeAsync(); @@ -260,7 +260,7 @@ public class ReadExecutorTest } @Override - public Message createMessage(boolean trackRepairedData) + public Message createMessage(boolean trackRepairedData, Dispatcher.RequestTime requestTime) { return Message.out(Verb.ECHO_REQ, NoPayload.noPayload); } diff --git a/test/unit/org/apache/cassandra/service/reads/range/RangeCommandIteratorTest.java b/test/unit/org/apache/cassandra/service/reads/range/RangeCommandIteratorTest.java index 27884a8a64..dfd1f7f88d 100644 --- a/test/unit/org/apache/cassandra/service/reads/range/RangeCommandIteratorTest.java +++ b/test/unit/org/apache/cassandra/service/reads/range/RangeCommandIteratorTest.java @@ -40,9 +40,9 @@ import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.locator.ReplicaPlan; import org.apache.cassandra.locator.ReplicaPlans; import org.apache.cassandra.schema.KeyspaceParams; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.utils.CloseableIterator; -import static org.apache.cassandra.utils.Clock.Global.nanoTime; import static org.junit.Assert.assertEquals; public class RangeCommandIteratorTest @@ -105,27 +105,27 @@ public class RangeCommandIteratorTest // without range merger, there will be 2 batches requested: 1st batch with 1 range and 2nd batch with remaining ranges CloseableIterator replicaPlans = replicaPlanIterator(keyRange, keyspace, false); - RangeCommandIterator data = new RangeCommandIterator(replicaPlans, command, 1, 1000, vnodeCount, nanoTime()); + RangeCommandIterator data = new RangeCommandIterator(replicaPlans, command, 1, 1000, vnodeCount, Dispatcher.RequestTime.forImmediateExecution()); verifyRangeCommandIterator(data, rows, 2, vnodeCount); // without range merger and initial cf=5, there will be 1 batches requested: 5 vnode ranges for 1st batch replicaPlans = replicaPlanIterator(keyRange, keyspace, false); - data = new RangeCommandIterator(replicaPlans, command, vnodeCount, 1000, vnodeCount, nanoTime()); + data = new RangeCommandIterator(replicaPlans, command, vnodeCount, 1000, vnodeCount, Dispatcher.RequestTime.forImmediateExecution()); verifyRangeCommandIterator(data, rows, 1, vnodeCount); // without range merger and max cf=1, there will be 5 batches requested: 1 vnode range per batch replicaPlans = replicaPlanIterator(keyRange, keyspace, false); - data = new RangeCommandIterator(replicaPlans, command, 1, 1, vnodeCount, nanoTime()); + data = new RangeCommandIterator(replicaPlans, command, 1, 1, vnodeCount, Dispatcher.RequestTime.forImmediateExecution()); verifyRangeCommandIterator(data, rows, vnodeCount, vnodeCount); // with range merger, there will be only 1 batch requested, as all ranges share the same replica - localhost replicaPlans = replicaPlanIterator(keyRange, keyspace, true); - data = new RangeCommandIterator(replicaPlans, command, 1, 1000, vnodeCount, nanoTime()); + data = new RangeCommandIterator(replicaPlans, command, 1, 1000, vnodeCount, Dispatcher.RequestTime.forImmediateExecution()); verifyRangeCommandIterator(data, rows, 1, vnodeCount); // with range merger and max cf=1, there will be only 1 batch requested, as all ranges share the same replica - localhost replicaPlans = replicaPlanIterator(keyRange, keyspace, true); - data = new RangeCommandIterator(replicaPlans, command, 1, 1, vnodeCount, nanoTime()); + data = new RangeCommandIterator(replicaPlans, command, 1, 1, vnodeCount, Dispatcher.RequestTime.forImmediateExecution()); verifyRangeCommandIterator(data, rows, 1, vnodeCount); } diff --git a/test/unit/org/apache/cassandra/service/reads/range/RangeCommandsTest.java b/test/unit/org/apache/cassandra/service/reads/range/RangeCommandsTest.java index 546fe5111f..ce04d5deab 100644 --- a/test/unit/org/apache/cassandra/service/reads/range/RangeCommandsTest.java +++ b/test/unit/org/apache/cassandra/service/reads/range/RangeCommandsTest.java @@ -38,10 +38,10 @@ import org.apache.cassandra.distributed.shared.WithProperties; import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.index.StubIndex; import org.apache.cassandra.schema.IndexMetadata; +import org.apache.cassandra.transport.Dispatcher; import static org.apache.cassandra.config.CassandraRelevantProperties.MAX_CONCURRENT_RANGE_REQUESTS; import static org.apache.cassandra.db.ConsistencyLevel.ONE; -import static org.apache.cassandra.utils.Clock.Global.nanoTime; import static org.junit.Assert.assertEquals; /** @@ -78,7 +78,7 @@ public class RangeCommandsTest extends CQLTester // verify that a low concurrency factor is not capped by the max concurrency factor PartitionRangeReadCommand command = command(cfs, 50, 50); - try (RangeCommandIterator partitions = RangeCommands.rangeCommandIterator(command, ONE, nanoTime()); + try (RangeCommandIterator partitions = RangeCommands.rangeCommandIterator(command, ONE, Dispatcher.RequestTime.forImmediateExecution()); ReplicaPlanIterator ranges = new ReplicaPlanIterator(command.dataRange().keyRange(), command.indexQueryPlan(), keyspace, ONE)) { assertEquals(2, partitions.concurrencyFactor()); @@ -88,7 +88,7 @@ public class RangeCommandsTest extends CQLTester // verify that a high concurrency factor is capped by the max concurrency factor command = command(cfs, 1000, 50); - try (RangeCommandIterator partitions = RangeCommands.rangeCommandIterator(command, ONE, nanoTime()); + try (RangeCommandIterator partitions = RangeCommands.rangeCommandIterator(command, ONE, Dispatcher.RequestTime.forImmediateExecution()); ReplicaPlanIterator ranges = new ReplicaPlanIterator(command.dataRange().keyRange(), command.indexQueryPlan(), keyspace, ONE)) { assertEquals(MAX_CONCURRENCY_FACTOR, partitions.concurrencyFactor()); @@ -98,7 +98,7 @@ public class RangeCommandsTest extends CQLTester // with 0 estimated results per range the concurrency factor should be 1 command = command(cfs, 1000, 0); - try (RangeCommandIterator partitions = RangeCommands.rangeCommandIterator(command, ONE, nanoTime()); + try (RangeCommandIterator partitions = RangeCommands.rangeCommandIterator(command, ONE, Dispatcher.RequestTime.forImmediateExecution()); ReplicaPlanIterator ranges = new ReplicaPlanIterator(command.dataRange().keyRange(), command.indexQueryPlan(), keyspace, ONE)) { assertEquals(1, partitions.concurrencyFactor()); diff --git a/test/unit/org/apache/cassandra/service/reads/repair/AbstractReadRepairTest.java b/test/unit/org/apache/cassandra/service/reads/repair/AbstractReadRepairTest.java index 3a64e3d1ab..51caa94801 100644 --- a/test/unit/org/apache/cassandra/service/reads/repair/AbstractReadRepairTest.java +++ b/test/unit/org/apache/cassandra/service/reads/repair/AbstractReadRepairTest.java @@ -76,6 +76,7 @@ import org.apache.cassandra.schema.SchemaTestUtil; import org.apache.cassandra.schema.TableMetadata; import org.apache.cassandra.schema.Tables; import org.apache.cassandra.service.StorageService; +import org.apache.cassandra.transport.Dispatcher; import org.apache.cassandra.utils.ByteBufferUtil; import static org.apache.cassandra.locator.Replica.fullReplica; @@ -366,11 +367,11 @@ public abstract class AbstractReadRepairTest return new ReplicaPlan.ForRangeRead(keyspace, keyspace.getReplicationStrategy(), consistencyLevel, ReplicaUtils.FULL_BOUNDS, replicas, targets, 1); } - public abstract InstrumentedReadRepair createInstrumentedReadRepair(ReadCommand command, ReplicaPlan.Shared replicaPlan, long queryStartNanoTime); + public abstract InstrumentedReadRepair createInstrumentedReadRepair(ReadCommand command, ReplicaPlan.Shared replicaPlan, Dispatcher.RequestTime requestTime); public InstrumentedReadRepair createInstrumentedReadRepair(ReplicaPlan.Shared replicaPlan) { - return createInstrumentedReadRepair(command, replicaPlan, nanoTime()); + return createInstrumentedReadRepair(command, replicaPlan, Dispatcher.RequestTime.forImmediateExecution()); } diff --git a/test/unit/org/apache/cassandra/service/reads/repair/BlockingReadRepairTest.java b/test/unit/org/apache/cassandra/service/reads/repair/BlockingReadRepairTest.java index dc93bf35c2..f76e15cd8f 100644 --- a/test/unit/org/apache/cassandra/service/reads/repair/BlockingReadRepairTest.java +++ b/test/unit/org/apache/cassandra/service/reads/repair/BlockingReadRepairTest.java @@ -41,6 +41,7 @@ import org.apache.cassandra.locator.Replica; import org.apache.cassandra.locator.ReplicaPlan; import org.apache.cassandra.net.Message; import org.apache.cassandra.service.reads.ReadCallback; +import org.apache.cassandra.transport.Dispatcher; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static org.apache.cassandra.utils.Clock.Global.nanoTime; @@ -83,9 +84,9 @@ public class BlockingReadRepairTest extends AbstractReadRepairTest private static class InstrumentedBlockingReadRepair, P extends ReplicaPlan.ForRead> extends BlockingReadRepair implements InstrumentedReadRepair { - public InstrumentedBlockingReadRepair(ReadCommand command, ReplicaPlan.Shared replicaPlan, long queryStartNanoTime) + public InstrumentedBlockingReadRepair(ReadCommand command, ReplicaPlan.Shared replicaPlan, Dispatcher.RequestTime requestTime) { - super(command, replicaPlan, queryStartNanoTime); + super(command, replicaPlan, requestTime); } Set readCommandRecipients = new HashSet<>(); @@ -113,9 +114,9 @@ public class BlockingReadRepairTest extends AbstractReadRepairTest } @Override - public InstrumentedReadRepair createInstrumentedReadRepair(ReadCommand command, ReplicaPlan.Shared replicaPlan, long queryStartNanoTime) + public InstrumentedReadRepair createInstrumentedReadRepair(ReadCommand command, ReplicaPlan.Shared replicaPlan, Dispatcher.RequestTime requestTime) { - return new InstrumentedBlockingReadRepair(command, replicaPlan, queryStartNanoTime); + return new InstrumentedBlockingReadRepair(command, replicaPlan, requestTime); } @Test diff --git a/test/unit/org/apache/cassandra/service/reads/repair/DiagEventsBlockingReadRepairTest.java b/test/unit/org/apache/cassandra/service/reads/repair/DiagEventsBlockingReadRepairTest.java index bc1a7ab070..749b225c71 100644 --- a/test/unit/org/apache/cassandra/service/reads/repair/DiagEventsBlockingReadRepairTest.java +++ b/test/unit/org/apache/cassandra/service/reads/repair/DiagEventsBlockingReadRepairTest.java @@ -48,6 +48,7 @@ import org.apache.cassandra.locator.Replica; import org.apache.cassandra.net.Message; import org.apache.cassandra.service.reads.ReadCallback; import org.apache.cassandra.service.reads.repair.ReadRepairEvent.ReadRepairEventType; +import org.apache.cassandra.transport.Dispatcher; import static java.util.concurrent.TimeUnit.NANOSECONDS; import static org.apache.cassandra.utils.Clock.Global.nanoTime; @@ -116,9 +117,10 @@ public class DiagEventsBlockingReadRepairTest extends AbstractReadRepairTest return handler.awaitRepairsUntil(nanoTime(), NANOSECONDS); } - public InstrumentedReadRepair createInstrumentedReadRepair(ReadCommand command, ReplicaPlan.Shared replicaPlan, long queryStartNanoTime) + @Override + public InstrumentedReadRepair createInstrumentedReadRepair(ReadCommand command, ReplicaPlan.Shared replicaPlan, Dispatcher.RequestTime requestTime) { - return new DiagnosticBlockingRepairHandler(command, replicaPlan, queryStartNanoTime); + return new DiagnosticBlockingRepairHandler(command, replicaPlan, requestTime); } private static DiagnosticPartitionReadRepairHandler createRepairHandler(Map repairs, ReplicaPlan.ForWrite writePlan) @@ -131,9 +133,9 @@ public class DiagEventsBlockingReadRepairTest extends AbstractReadRepairTest private Set recipients = Collections.emptySet(); private ReadCallback readCallback = null; - DiagnosticBlockingRepairHandler(ReadCommand command, ReplicaPlan.Shared replicaPlan, long queryStartNanoTime) + DiagnosticBlockingRepairHandler(ReadCommand command, ReplicaPlan.Shared replicaPlan, Dispatcher.RequestTime requestTime) { - super(command, replicaPlan, queryStartNanoTime); + super(command, replicaPlan, requestTime); DiagnosticEventService.instance().subscribe(ReadRepairEvent.class, this::onRepairEvent); } diff --git a/test/unit/org/apache/cassandra/service/reads/repair/ReadOnlyReadRepairTest.java b/test/unit/org/apache/cassandra/service/reads/repair/ReadOnlyReadRepairTest.java index 82bb8de14d..749e444425 100644 --- a/test/unit/org/apache/cassandra/service/reads/repair/ReadOnlyReadRepairTest.java +++ b/test/unit/org/apache/cassandra/service/reads/repair/ReadOnlyReadRepairTest.java @@ -33,15 +33,16 @@ import org.apache.cassandra.locator.Endpoints; import org.apache.cassandra.locator.InetAddressAndPort; import org.apache.cassandra.locator.Replica; import org.apache.cassandra.service.reads.ReadCallback; +import org.apache.cassandra.transport.Dispatcher; public class ReadOnlyReadRepairTest extends AbstractReadRepairTest { private static class InstrumentedReadOnlyReadRepair, P extends ReplicaPlan.ForRead> extends ReadOnlyReadRepair implements InstrumentedReadRepair { - public InstrumentedReadOnlyReadRepair(ReadCommand command, ReplicaPlan.Shared replicaPlan, long queryStartNanoTime) + public InstrumentedReadOnlyReadRepair(ReadCommand command, ReplicaPlan.Shared replicaPlan, Dispatcher.RequestTime requestTime) { - super(command, replicaPlan, queryStartNanoTime); + super(command, replicaPlan, requestTime); } Set readCommandRecipients = new HashSet<>(); @@ -75,9 +76,9 @@ public class ReadOnlyReadRepairTest extends AbstractReadRepairTest } @Override - public InstrumentedReadRepair createInstrumentedReadRepair(ReadCommand command, ReplicaPlan.Shared replicaPlan, long queryStartNanoTime) + public InstrumentedReadRepair createInstrumentedReadRepair(ReadCommand command, ReplicaPlan.Shared replicaPlan, Dispatcher.RequestTime requestTime) { - return new InstrumentedReadOnlyReadRepair(command, replicaPlan, queryStartNanoTime); + return new InstrumentedReadOnlyReadRepair(command, replicaPlan, requestTime); } @Test diff --git a/test/unit/org/apache/cassandra/transport/CQLConnectionTest.java b/test/unit/org/apache/cassandra/transport/CQLConnectionTest.java index 0ef61a5bdd..a7afae1b8b 100644 --- a/test/unit/org/apache/cassandra/transport/CQLConnectionTest.java +++ b/test/unit/org/apache/cassandra/transport/CQLConnectionTest.java @@ -633,7 +633,7 @@ public class CQLConnectionTest this.frameEncoder = frameEncoder; } - public void accept(Channel channel, Message.Request message, Dispatcher.FlushItemConverter toFlushItem, Overload backpressure) + public void dispatch(Channel channel, Message.Request message, Dispatcher.FlushItemConverter toFlushItem, Overload backpressure) { if (flusher == null) flusher = new SimpleClient.SimpleFlusher(frameEncoder); @@ -651,6 +651,12 @@ public class CQLConnectionTest Flusher.FlushItem.Framed item = (Flusher.FlushItem.Framed)toFlushItem.toFlushItem(channel, message, fixedResponse); item.release(); } + + @Override + public boolean hasQueueCapacity() + { + return true; + } } static class ServerConfigurator extends PipelineConfigurator @@ -1084,7 +1090,7 @@ public class CQLConnectionTest options.put(StartupMessage.CQL_VERSION, QueryProcessor.CQL_VERSION.toString()); if (codec.encoder instanceof FrameEncoderLZ4) options.put(StartupMessage.COMPRESSION, "LZ4"); - Connection connection = new Connection(channel, ProtocolVersion.V5, (ch, connection1) -> {}); + Connection connection = new Connection(channel, ProtocolVersion.V5, new NoOpTracker()); channel.attr(Connection.attributeKey).set(connection); channel.writeAndFlush(new StartupMessage(options)).sync(); @@ -1158,4 +1164,14 @@ public class CQLConnectionTest f.release(); } } + + private static class NoOpTracker implements Connection.Tracker + { + public void addConnection(Channel ch, Connection connection) {} + + public boolean isRunning() + { + return true; + } + } } diff --git a/test/unit/org/apache/cassandra/transport/MessageDispatcherTest.java b/test/unit/org/apache/cassandra/transport/MessageDispatcherTest.java index 319b362071..819e75c4b7 100644 --- a/test/unit/org/apache/cassandra/transport/MessageDispatcherTest.java +++ b/test/unit/org/apache/cassandra/transport/MessageDispatcherTest.java @@ -33,12 +33,20 @@ import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.metrics.ClientMetrics; import org.apache.cassandra.service.QueryState; import org.apache.cassandra.transport.messages.AuthResponse; +import org.mockito.Mockito; public class MessageDispatcherTest { static final Message.Request AUTH_RESPONSE_REQUEST = new AuthResponse(new byte[0]) { - public Response execute(QueryState queryState, long queryStartNanoTime, boolean traceRequest) + @Override + public Connection connection() + { + return connectionMock(); + } + + @Override + public Response execute(QueryState queryState, Dispatcher.RequestTime requestTime, boolean traceRequest) { return null; } @@ -91,7 +99,14 @@ public class MessageDispatcherTest long auths = completedAuth(); long requests = tryAuth(this::completedRequests, new Message.Request(type) { - public Response execute(QueryState queryState, long queryStartNanoTime, boolean traceRequest) + @Override + public Connection connection() + { + return connectionMock(); + } + + @Override + public Response execute(QueryState queryState, Dispatcher.RequestTime requestTime, boolean traceRequest) { return null; } @@ -164,9 +179,18 @@ public class MessageDispatcherTest Message.Request request, FlushItemConverter forFlusher, ClientResourceLimits.Overload backpressure, - long startTimeNanos) + RequestTime requestTime) { // noop } } + + private static Connection connectionMock() + { + Connection.Tracker tracker = Mockito.mock(Connection.Tracker.class); + Mockito.when(tracker.isRunning()).thenAnswer(invocation -> true); + Connection c = Mockito.mock(Connection.class); + Mockito.when(c.getTracker()).thenAnswer(invocation -> tracker); + return c; + } } diff --git a/test/unit/org/apache/cassandra/transport/MessagePayloadTest.java b/test/unit/org/apache/cassandra/transport/MessagePayloadTest.java index 1bd1a8f7f2..a50174bd87 100644 --- a/test/unit/org/apache/cassandra/transport/MessagePayloadTest.java +++ b/test/unit/org/apache/cassandra/transport/MessagePayloadTest.java @@ -405,16 +405,17 @@ public class MessagePayloadTest extends CQLTester return result; } + @Override public ResultMessage process(CQLStatement statement, QueryState state, QueryOptions options, Map customPayload, - long queryStartNanoTime) + Dispatcher.RequestTime requestTime) throws RequestExecutionException, RequestValidationException { if (customPayload != null) requestPayload = customPayload; - ResultMessage result = QueryProcessor.instance.process(statement, state, options, customPayload, queryStartNanoTime); + ResultMessage result = QueryProcessor.instance.process(statement, state, options, customPayload, requestTime); if (customPayload != null) { result.setCustomPayload(responsePayload); @@ -423,16 +424,17 @@ public class MessagePayloadTest extends CQLTester return result; } + @Override public ResultMessage processBatch(BatchStatement statement, QueryState state, BatchQueryOptions options, Map customPayload, - long queryStartNanoTime) + Dispatcher.RequestTime requestTime) throws RequestExecutionException, RequestValidationException { if (customPayload != null) requestPayload = customPayload; - ResultMessage result = QueryProcessor.instance.processBatch(statement, state, options, customPayload, queryStartNanoTime); + ResultMessage result = QueryProcessor.instance.processBatch(statement, state, options, customPayload, requestTime); if (customPayload != null) { result.setCustomPayload(responsePayload); @@ -445,12 +447,12 @@ public class MessagePayloadTest extends CQLTester QueryState state, QueryOptions options, Map customPayload, - long queryStartNanoTime) + Dispatcher.RequestTime requestTime) throws RequestExecutionException, RequestValidationException { if (customPayload != null) requestPayload = customPayload; - ResultMessage result = QueryProcessor.instance.processPrepared(statement, state, options, customPayload, queryStartNanoTime); + ResultMessage result = QueryProcessor.instance.processPrepared(statement, state, options, customPayload, requestTime); if (customPayload != null) { result.setCustomPayload(responsePayload); diff --git a/test/unit/org/apache/cassandra/transport/QueueBackpressureTest.java b/test/unit/org/apache/cassandra/transport/QueueBackpressureTest.java new file mode 100644 index 0000000000..b76739f1a5 --- /dev/null +++ b/test/unit/org/apache/cassandra/transport/QueueBackpressureTest.java @@ -0,0 +1,60 @@ +/* + * 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.transport; + +import java.util.concurrent.TimeUnit; + +import org.junit.Assert; +import org.junit.Test; + +public class QueueBackpressureTest +{ + @Test + public void testQueueBackpressure() + { + QueueBackpressure.Incident backpressure = new QueueBackpressure.Incident(() -> TimeUnit.MILLISECONDS.toNanos(10), + () -> TimeUnit.MILLISECONDS.toNanos(100), + -1, 0, 0); + + Assert.assertEquals(0, backpressure.delay(TimeUnit.NANOSECONDS)); + backpressure = backpressure.mark(backpressure.appliedAt() + TimeUnit.MILLISECONDS.toNanos(100)); + Assert.assertEquals(backpressure.minDelayNanos(), backpressure.delay(TimeUnit.NANOSECONDS)); + // up to 9 times + for (int i = 1; i < 10; i++) + backpressure = backpressure.mark(backpressure.appliedAt() + TimeUnit.MILLISECONDS.toNanos(100)); + Assert.assertEquals(backpressure.minDelayNanos(), backpressure.delay(TimeUnit.NANOSECONDS)); + + // 10th time + backpressure = backpressure.mark(backpressure.appliedAt() + TimeUnit.MILLISECONDS.toNanos(100)); + Assert.assertEquals(backpressure.minDelayNanos() * 2, backpressure.delay(TimeUnit.NANOSECONDS)); + + for (int i = 0; i < 40; i++) + backpressure = backpressure.mark(backpressure.appliedAt() + TimeUnit.MILLISECONDS.toNanos(100)); + + Assert.assertEquals(backpressure.minDelayNanos() * 6, backpressure.delay(TimeUnit.NANOSECONDS)); + + for (int i = 0; i < 1000; i++) + backpressure = backpressure.mark(backpressure.appliedAt() + TimeUnit.MILLISECONDS.toNanos(100)); + + Assert.assertEquals(backpressure.maxDelayNanos(), backpressure.delay(TimeUnit.NANOSECONDS)); + + backpressure = backpressure.mark(backpressure.appliedAt() + TimeUnit.MILLISECONDS.toNanos(1001)); + Assert.assertEquals(backpressure.minDelayNanos(), backpressure.delay(TimeUnit.NANOSECONDS)); + } +}