Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* 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 com.datastax.dse.driver.internal.core.cql.continuous;
import com.datastax.dse.driver.api.core.DseProtocolVersion;
import com.datastax.dse.driver.api.core.cql.continuous.ContinuousAsyncResultSet;
import com.datastax.dse.driver.internal.core.DseProtocolFeature;
import com.datastax.dse.driver.internal.core.cql.DseConversions;
import com.datastax.dse.protocol.internal.request.Revise;
import com.datastax.dse.protocol.internal.response.result.DseRowsMetadata;
import com.datastax.oss.driver.api.core.AllNodesFailedException;
import com.datastax.oss.driver.api.core.CqlIdentifier;
import com.datastax.oss.driver.api.core.DriverTimeoutException;
import com.datastax.oss.driver.api.core.NodeUnavailableException;
import com.datastax.oss.driver.api.core.ProtocolVersion;
import com.datastax.oss.driver.api.core.RequestThrottlingException;
import com.datastax.oss.driver.api.core.config.DefaultDriverOption;
import com.datastax.oss.driver.api.core.config.DriverExecutionProfile;
import com.datastax.oss.driver.api.core.connection.FrameTooLongException;
import com.datastax.oss.driver.api.core.cql.ColumnDefinitions;
import com.datastax.oss.driver.api.core.cql.ExecutionInfo;
import com.datastax.oss.driver.api.core.metadata.Node;
import com.datastax.oss.driver.api.core.metrics.DefaultNodeMetric;
import com.datastax.oss.driver.api.core.metrics.DefaultSessionMetric;
import com.datastax.oss.driver.api.core.metrics.NodeMetric;
import com.datastax.oss.driver.api.core.metrics.SessionMetric;
import com.datastax.oss.driver.api.core.retry.RetryPolicy;
import com.datastax.oss.driver.api.core.retry.RetryVerdict;
import com.datastax.oss.driver.api.core.servererrors.BootstrappingException;
import com.datastax.oss.driver.api.core.servererrors.CoordinatorException;
import com.datastax.oss.driver.api.core.servererrors.FunctionFailureException;
import com.datastax.oss.driver.api.core.servererrors.ProtocolError;
import com.datastax.oss.driver.api.core.servererrors.QueryValidationException;
import com.datastax.oss.driver.api.core.servererrors.ReadTimeoutException;
import com.datastax.oss.driver.api.core.servererrors.UnavailableException;
import com.datastax.oss.driver.api.core.servererrors.WriteTimeoutException;
import com.datastax.oss.driver.api.core.session.Request;
import com.datastax.oss.driver.api.core.session.throttling.RequestThrottler;
import com.datastax.oss.driver.api.core.session.throttling.Throttled;
import com.datastax.oss.driver.internal.core.adminrequest.ThrottledAdminRequestHandler;
import com.datastax.oss.driver.internal.core.adminrequest.UnexpectedResponseException;
import com.datastax.oss.driver.internal.core.channel.DriverChannel;
import com.datastax.oss.driver.internal.core.channel.ResponseCallback;
import com.datastax.oss.driver.internal.core.context.InternalDriverContext;
import com.datastax.oss.driver.internal.core.cql.Conversions;
import com.datastax.oss.driver.internal.core.cql.DefaultExecutionInfo;
import com.datastax.oss.driver.internal.core.metadata.DefaultNode;
import com.datastax.oss.driver.internal.core.metrics.NodeMetricUpdater;
import com.datastax.oss.driver.internal.core.metrics.SessionMetricUpdater;
import com.datastax.oss.driver.internal.core.session.DefaultSession;
import com.datastax.oss.driver.internal.core.session.RepreparePayload;
import com.datastax.oss.driver.internal.core.util.Loggers;
import com.datastax.oss.driver.internal.core.util.collection.SimpleQueryPlan;
import com.datastax.oss.driver.shaded.guava.common.annotations.VisibleForTesting;
import com.datastax.oss.protocol.internal.Frame;
import com.datastax.oss.protocol.internal.Message;
import com.datastax.oss.protocol.internal.ProtocolConstants;
import com.datastax.oss.protocol.internal.request.Prepare;
import com.datastax.oss.protocol.internal.response.Error;
import com.datastax.oss.protocol.internal.response.Result;
import com.datastax.oss.protocol.internal.response.error.Unprepared;
import com.datastax.oss.protocol.internal.response.result.Rows;
import com.datastax.oss.protocol.internal.response.result.Void;
import com.datastax.oss.protocol.internal.util.Bytes;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import com.datastax.oss.driver.shaded.netty.handler.codec.EncoderException;
import com.datastax.oss.driver.shaded.netty.util.Timeout;
import com.datastax.oss.driver.shaded.netty.util.Timer;
import com.datastax.oss.driver.shaded.netty.util.concurrent.Future;
import com.datastax.oss.driver.shaded.netty.util.concurrent.GenericFutureListener;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.time.Duration;
import java.util.AbstractMap;
import java.util.ArrayDeque;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import net.jcip.annotations.GuardedBy;
import net.jcip.annotations.ThreadSafe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Handles a request that supports multiple response messages (a.k.a. continuous paging request).
*/
@ThreadSafe
public abstract class ContinuousRequestHandlerBase
implements Throttled {
private static final Logger LOG = LoggerFactory.getLogger(ContinuousRequestHandlerBase.class);
protected final String logPrefix;
protected final StatementT initialStatement;
protected final DefaultSession session;
private final CqlIdentifier keyspace;
protected final InternalDriverContext context;
private final Queue queryPlan;
protected final RequestThrottler throttler;
private final boolean protocolBackpressureAvailable;
private final Timer timer;
private final SessionMetricUpdater sessionMetricUpdater;
private final boolean specExecEnabled;
private final SessionMetric clientTimeoutsMetric;
private final SessionMetric continuousRequestsMetric;
private final NodeMetric messagesMetric;
private final List scheduledExecutions;
// The errors on the nodes that were already tried.
// We don't use a map because nodes can appear multiple times.
protected final List> errors = new CopyOnWriteArrayList<>();
/**
* The list of in-flight executions, one per node. Executions may be triggered by speculative
* executions or retries. An execution is added to this list when the write operation completes.
* It is removed from this list when the callback has done reading responses.
*/
private final List inFlightCallbacks = new CopyOnWriteArrayList<>();
/** The callback selected to stream results back to the client. */
private final CompletableFuture chosenCallback = new CompletableFuture<>();
/**
* How many speculative executions are currently running (including the initial execution). We
* track this in order to know when to fail the request if all executions have reached the end of
* the query plan.
*/
private final AtomicInteger activeExecutionsCount = new AtomicInteger(0);
/**
* How many speculative executions have started (excluding the initial execution), whether they
* have completed or not. We track this in order to fill execution info objects with this
* information.
*/
protected final AtomicInteger startedSpeculativeExecutionsCount = new AtomicInteger(0);
// Set when the execution starts, and is never modified after.
private final long startTimeNanos;
private volatile Timeout globalTimeout;
private final Class resultSetClass;
public ContinuousRequestHandlerBase(
@NonNull StatementT statement,
@NonNull DefaultSession session,
@NonNull InternalDriverContext context,
@NonNull String sessionLogPrefix,
@NonNull Class resultSetClass,
boolean specExecEnabled,
SessionMetric clientTimeoutsMetric,
SessionMetric continuousRequestsMetric,
NodeMetric messagesMetric) {
this.resultSetClass = resultSetClass;
ProtocolVersion protocolVersion = context.getProtocolVersion();
if (!context
.getProtocolVersionRegistry()
.supports(protocolVersion, DseProtocolFeature.CONTINUOUS_PAGING)) {
throw new IllegalStateException(
"Cannot execute continuous paging requests with protocol version " + protocolVersion);
}
this.clientTimeoutsMetric = clientTimeoutsMetric;
this.continuousRequestsMetric = continuousRequestsMetric;
this.messagesMetric = messagesMetric;
this.logPrefix = sessionLogPrefix + "|" + this.hashCode();
LOG.trace("[{}] Creating new continuous handler for request {}", logPrefix, statement);
this.initialStatement = statement;
this.session = session;
this.keyspace = session.getKeyspace().orElse(null);
this.context = context;
DriverExecutionProfile executionProfile =
Conversions.resolveExecutionProfile(statement, context);
this.queryPlan =
statement.getNode() != null
? new SimpleQueryPlan(statement.getNode())
: context
.getLoadBalancingPolicyWrapper()
.newQueryPlan(statement, executionProfile.getName(), session);
this.timer = context.getNettyOptions().getTimer();
this.protocolBackpressureAvailable =
protocolVersion.getCode() >= DseProtocolVersion.DSE_V2.getCode();
this.throttler = context.getRequestThrottler();
this.sessionMetricUpdater = session.getMetricUpdater();
this.startTimeNanos = System.nanoTime();
this.specExecEnabled = specExecEnabled;
this.scheduledExecutions = this.specExecEnabled ? new CopyOnWriteArrayList<>() : null;
}
@NonNull
protected abstract Duration getGlobalTimeout();
@NonNull
protected abstract Duration getPageTimeout(@NonNull StatementT statement, int pageNumber);
@NonNull
protected abstract Duration getReviseRequestTimeout(@NonNull StatementT statement);
protected abstract int getMaxEnqueuedPages(@NonNull StatementT statement);
protected abstract int getMaxPages(@NonNull StatementT statement);
@NonNull
protected abstract Message getMessage(@NonNull StatementT statement);
protected abstract boolean isTracingEnabled(@NonNull StatementT statement);
@NonNull
protected abstract Map createPayload(@NonNull StatementT statement);
@NonNull
protected abstract ResultSetT createEmptyResultSet(@NonNull ExecutionInfo executionInfo);
protected abstract int pageNumber(@NonNull ResultSetT resultSet);
@NonNull
protected abstract ResultSetT createResultSet(
@NonNull StatementT statement,
@NonNull Rows rows,
@NonNull ExecutionInfo executionInfo,
@NonNull ColumnDefinitions columnDefinitions)
throws IOException;
// MAIN LIFECYCLE
@Override
public void onThrottleReady(boolean wasDelayed) {
DriverExecutionProfile executionProfile =
Conversions.resolveExecutionProfile(initialStatement, context);
if (wasDelayed
// avoid call to nanoTime() if metric is disabled:
&& sessionMetricUpdater.isEnabled(
DefaultSessionMetric.THROTTLING_DELAY, executionProfile.getName())) {
session
.getMetricUpdater()
.updateTimer(
DefaultSessionMetric.THROTTLING_DELAY,
executionProfile.getName(),
System.nanoTime() - startTimeNanos,
TimeUnit.NANOSECONDS);
}
activeExecutionsCount.incrementAndGet();
sendRequest(initialStatement, null, 0, 0, specExecEnabled);
}
@Override
public void onThrottleFailure(@NonNull RequestThrottlingException error) {
DriverExecutionProfile executionProfile =
Conversions.resolveExecutionProfile(initialStatement, context);
session
.getMetricUpdater()
.incrementCounter(DefaultSessionMetric.THROTTLING_ERRORS, executionProfile.getName());
abortGlobalRequestOrChosenCallback(error);
}
private void abortGlobalRequestOrChosenCallback(@NonNull Throwable error) {
if (!chosenCallback.completeExceptionally(error)) {
chosenCallback.thenAccept(callback -> callback.abort(error, false));
}
}
public CompletionStage handle() {
globalTimeout = scheduleGlobalTimeout();
return fetchNextPage();
}
/**
* Builds the future that will get returned to the user from the initial execute call or a
* fetchNextPage() on the async API.
*/
public CompletionStage fetchNextPage() {
CompletableFuture result = new CompletableFuture<>();
// This is equivalent to
// `chosenCallback.thenCompose(NodeResponseCallback::dequeueOrCreatePending)`, except
// that we need to cancel `result` if `resultSetError` is a CancellationException.
chosenCallback.whenComplete(
(callback, callbackError) -> {
if (callbackError != null) {
result.completeExceptionally(callbackError);
} else {
callback
.dequeueOrCreatePending()
.whenComplete(
(resultSet, resultSetError) -> {
if (resultSetError != null) {
result.completeExceptionally(resultSetError);
} else {
result.complete(resultSet);
}
});
}
});
// If the user cancels the future, propagate to our internal components
result.whenComplete(
(rs, t) -> {
if (t instanceof CancellationException) {
cancel();
}
});
return result;
}
/**
* Sends the initial request to the next available node.
*
* @param node if not null, it will be attempted first before the rest of the query plan. It
* happens only when we retry on the same host.
* @param currentExecutionIndex 0 for the initial execution, 1 for the first speculative one, etc.
* @param retryCount the number of times that the retry policy was invoked for this execution
* already (note that some internal retries don't go through the policy, and therefore don't
* increment this counter)
* @param scheduleSpeculativeExecution whether to schedule the next speculative execution
*/
private void sendRequest(
StatementT statement,
@Nullable Node node,
int currentExecutionIndex,
int retryCount,
boolean scheduleSpeculativeExecution) {
DriverChannel channel = null;
if (node == null || (channel = session.getChannel(node, logPrefix)) == null) {
while ((node = queryPlan.poll()) != null) {
channel = session.getChannel(node, logPrefix);
if (channel != null) {
break;
} else {
recordError(node, new NodeUnavailableException(node));
}
}
}
if (channel == null) {
// We've reached the end of the query plan without finding any node to write to; abort the
// continuous paging session.
if (activeExecutionsCount.decrementAndGet() == 0) {
abortGlobalRequestOrChosenCallback(AllNodesFailedException.fromErrors(errors));
}
} else if (!chosenCallback.isDone()) {
NodeResponseCallback nodeResponseCallback =
new NodeResponseCallback(
statement,
node,
channel,
currentExecutionIndex,
retryCount,
scheduleSpeculativeExecution,
logPrefix);
inFlightCallbacks.add(nodeResponseCallback);
channel
.write(
getMessage(statement),
isTracingEnabled(statement),
createPayload(statement),
nodeResponseCallback)
.addListener(nodeResponseCallback);
}
}
private Timeout scheduleGlobalTimeout() {
Duration globalTimeout = getGlobalTimeout();
if (globalTimeout.toNanos() <= 0) {
return null;
}
LOG.trace("[{}] Scheduling global timeout for pages in {}", logPrefix, globalTimeout);
return timer.newTimeout(
timeout ->
abortGlobalRequestOrChosenCallback(
new DriverTimeoutException("Query timed out after " + globalTimeout)),
globalTimeout.toNanos(),
TimeUnit.NANOSECONDS);
}
/**
* Cancels the continuous paging request.
*
*
Called from user code, see {@link DefaultContinuousAsyncResultSet#cancel()}, or from a
* driver I/O thread.
*/
public void cancel() {
// If chosenCallback is already set, this is a no-op and the chosen callback will be handled by
// cancelScheduledTasks
chosenCallback.cancel(true);
cancelScheduledTasks(null);
cancelGlobalTimeout();
}
private void cancelGlobalTimeout() {
if (globalTimeout != null) {
globalTimeout.cancel();
}
}
/**
* Cancel all pending and scheduled executions, except the one passed as an argument to the
* method.
*
* @param toIgnore An optional execution to ignore (will not be cancelled).
*/
private void cancelScheduledTasks(@Nullable NodeResponseCallback toIgnore) {
if (scheduledExecutions != null) {
for (Timeout scheduledExecution : scheduledExecutions) {
scheduledExecution.cancel();
}
}
for (NodeResponseCallback callback : inFlightCallbacks) {
if (toIgnore == null || toIgnore != callback) {
callback.cancel();
}
}
}
@VisibleForTesting
int getState() {
try {
return chosenCallback.get().getState();
} catch (CancellationException e) {
// Happens if the test cancels before the callback was chosen
return NodeResponseCallback.STATE_FAILED;
} catch (InterruptedException | ExecutionException e) {
// We never interrupt or fail chosenCallback (other than canceling)
throw new AssertionError("Unexpected error", e);
}
}
@VisibleForTesting
CompletableFuture getPendingResult() {
try {
return chosenCallback.get().getPendingResult();
} catch (Exception e) {
// chosenCallback should always be complete by the time tests call this
throw new AssertionError("Expected callback to be chosen at this point");
}
}
private void recordError(@NonNull Node node, @NonNull Throwable error) {
errors.add(new AbstractMap.SimpleEntry<>(node, error));
}
/**
* Handles the interaction with a single node in the query plan.
*
*
An instance of this class is created each time we (re)try a node. The first callback that
* has something ready to enqueue will be allowed to stream results back to the client; the others
* will be cancelled.
*/
private class NodeResponseCallback
implements ResponseCallback, GenericFutureListener> {
private final long messageStartTimeNanos = System.nanoTime();
private final StatementT statement;
private final Node node;
private final DriverChannel channel;
// The identifier of the current execution (0 for the initial execution, 1 for the first
// speculative execution, etc.)
private final int executionIndex;
// How many times we've invoked the retry policy and it has returned a "retry" decision (0 for
// the first attempt of each execution).
private final String logPrefix;
private final boolean scheduleSpeculativeExecution;
private final DriverExecutionProfile executionProfile;
// Coordinates concurrent accesses between the client and I/O threads
private final ReentrantLock lock = new ReentrantLock();
// The page queue, storing responses that we have received and have not been consumed by the
// client yet. We instantiate it lazily to avoid unnecessary allocation; this is also used to
// check if the callback ever tried to enqueue something.
@GuardedBy("lock")
private Queue