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.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.cosmos.implementation.query;
import com.azure.cosmos.BridgeInternal;
import com.azure.cosmos.implementation.DocumentClientRetryPolicy;
import com.azure.cosmos.implementation.GlobalEndpointManager;
import com.azure.cosmos.implementation.ImplementationBridgeHelpers;
import com.azure.cosmos.implementation.circuitBreaker.GlobalPartitionEndpointManagerForCircuitBreaker;
import com.azure.cosmos.implementation.GoneException;
import com.azure.cosmos.implementation.InvalidPartitionExceptionRetryPolicy;
import com.azure.cosmos.implementation.MetadataDiagnosticsContext;
import com.azure.cosmos.implementation.ObservableHelper;
import com.azure.cosmos.implementation.PartitionKeyRangeGoneRetryPolicy;
import com.azure.cosmos.implementation.PathsHelper;
import com.azure.cosmos.implementation.ResourceType;
import com.azure.cosmos.implementation.RetryContext;
import com.azure.cosmos.implementation.RxDocumentClientImpl;
import com.azure.cosmos.implementation.RxDocumentServiceRequest;
import com.azure.cosmos.implementation.ShouldRetryResult;
import com.azure.cosmos.implementation.changefeed.common.ChangeFeedState;
import com.azure.cosmos.implementation.feedranges.FeedRangeContinuation;
import com.azure.cosmos.implementation.feedranges.FeedRangeInternal;
import com.azure.cosmos.implementation.routing.Range;
import com.azure.cosmos.implementation.spark.OperationContextAndListenerTuple;
import com.azure.cosmos.models.FeedResponse;
import com.azure.cosmos.models.ModelBridgeInternal;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Mono;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;
import static com.azure.cosmos.implementation.guava25.base.Preconditions.checkNotNull;
class ChangeFeedFetcher extends Fetcher {
private final static ImplementationBridgeHelpers.FeedResponseHelper.FeedResponseAccessor feedResponseAccessor =
ImplementationBridgeHelpers.FeedResponseHelper.getFeedResponseAccessor();
private final ChangeFeedState changeFeedState;
private final Supplier createRequestFunc;
private final Supplier feedRangeContinuationRetryPolicySupplier;
public ChangeFeedFetcher(
RxDocumentClientImpl client,
Supplier createRequestFunc,
Function>> executeFunc,
ChangeFeedState changeFeedState,
Map requestOptionProperties,
int top,
int maxItemCount,
boolean isSplitHandlingDisabled,
OperationContextAndListenerTuple operationContext,
GlobalEndpointManager globalEndpointManager,
GlobalPartitionEndpointManagerForCircuitBreaker globalPartitionEndpointManagerForCircuitBreaker) {
super(executeFunc, true, top, maxItemCount, operationContext, null, globalEndpointManager, globalPartitionEndpointManagerForCircuitBreaker);
checkNotNull(client, "Argument 'client' must not be null.");
checkNotNull(createRequestFunc, "Argument 'createRequestFunc' must not be null.");
checkNotNull(changeFeedState, "Argument 'changeFeedState' must not be null.");
this.changeFeedState = changeFeedState;
// For changeFeedProcessor with pkRange version, ChangeFeedState.containerRid will be name based rather than resouceId,
// due to the inconsistency of the ChangeFeedState.containerRid format, so in order to generate the correct path,
// we use a RxDocumentServiceRequest here
RxDocumentServiceRequest documentServiceRequest = createRequestFunc.get();
String collectionLink = PathsHelper.generatePath(
ResourceType.DocumentCollection, documentServiceRequest, false);
this.feedRangeContinuationRetryPolicySupplier =
() -> this.getFeedRangeContinuationRetryPolicy(
client,
requestOptionProperties,
collectionLink,
isSplitHandlingDisabled);
this.createRequestFunc = createRequestFunc;
}
@Override
public Mono> nextPage() {
DocumentClientRetryPolicy retryPolicy = this.feedRangeContinuationRetryPolicySupplier.get();
if (retryPolicy == null) {
return this.nextPageInternal(null);
}
// There are two conditions that require retries
// in the change feed pipeline
// 1) On 410 the FeedRangeContinuation needs to evaluate
// whether continuations need to be split (in the case that any continuation
// exists that would span more than one physical partition now
// 2) On 304 a retry is needed if at least one continuation has not been drained yet.
// This prevents returning a 304 before we received a 304 for all continuations
//
// 410 handling: this is triggered by an exception - using an
// IRetryPolicy (FeedRangeContinuationSplitRetryPolicy)
// 304 handling: this is not triggered by an exception (304 doesn't result in throwing)
// so using Reactor's built-in option of repeating the chain on an empty result
// so nextPageInternal below has the logic to return empty result
// if not all continuations have been drained yet.
return ObservableHelper.inlineIfPossible(
() -> nextPageInternal(retryPolicy),
retryPolicy);
}
private Mono> nextPageInternal(DocumentClientRetryPolicy retryPolicy) {
return Mono.fromSupplier(() -> nextPageCore(retryPolicy))
.flatMap(Function.identity())
.flatMap((r) -> {
FeedRangeContinuation continuationSnapshot =
this.changeFeedState.getContinuation();
if (continuationSnapshot != null &&
continuationSnapshot.handleChangeFeedNotModified(r) == ShouldRetryResult.RETRY_NOW) {
// not all continuations have been drained yet
// repeat with the next continuation
this.reEnableShouldFetchMoreForRetry();
return Mono.empty();
}
return Mono.just(r);
})
.repeatWhenEmpty(o -> o);
}
@Override
protected String applyServerResponseContinuation(
String serverContinuationToken,
RxDocumentServiceRequest request,
FeedResponse response) {
boolean isNoChanges = feedResponseAccessor.getNoChanges(response);
boolean shouldMoveToNextTokenOnETagReplace = !isNoChanges;
return this.changeFeedState.applyServerResponseContinuation(
serverContinuationToken, request, shouldMoveToNextTokenOnETagReplace);
}
@Override
protected boolean isFullyDrained(boolean isChangeFeed, FeedResponse response) {
if (ModelBridgeInternal.noChanges(response)) {
return true;
}
FeedRangeContinuation continuation = this.changeFeedState.getContinuation();
return continuation != null && continuation.isDone();
}
@Override
protected String getContinuationForLogging() {
return this.changeFeedState.toJson();
}
@Override
protected RxDocumentServiceRequest createRequest(
int maxItemCount,
DocumentClientRetryPolicy documentClientRetryPolicy) {
RxDocumentServiceRequest request = this.createRequestFunc.get();
if (documentClientRetryPolicy != null) {
request.requestContext.setClientRetryPolicySupplier(() -> documentClientRetryPolicy);
documentClientRetryPolicy.onBeforeSendRequest(request);
}
this.changeFeedState.populateRequest(request, maxItemCount);
return request;
}
private static final class FeedRangeContinuationFeedRangeGoneRetryPolicy extends DocumentClientRetryPolicy {
private final static Logger LOGGER = LoggerFactory.getLogger(FeedRangeContinuationFeedRangeGoneRetryPolicy.class);
private final ChangeFeedState state;
private final RxDocumentClientImpl client;
private final DocumentClientRetryPolicy nextRetryPolicy;
private final Map requestOptionProperties;
private MetadataDiagnosticsContext diagnosticsContext;
private final RetryContext retryContext;
private final Supplier operationContextTextProvider;
public FeedRangeContinuationFeedRangeGoneRetryPolicy(
RxDocumentClientImpl client,
ChangeFeedState state,
DocumentClientRetryPolicy nextRetryPolicy,
Map requestOptionProperties,
RetryContext retryContext,
Supplier operationContextTextProvider) {
checkNotNull(
operationContextTextProvider,
"Argument 'operationContextTextProvider' must not be null.");
this.client = client;
this.state = state;
this.nextRetryPolicy = nextRetryPolicy;
this.requestOptionProperties = requestOptionProperties;
this.diagnosticsContext = null;
this.retryContext = retryContext;
this.operationContextTextProvider = operationContextTextProvider;
}
@Override
public void onBeforeSendRequest(RxDocumentServiceRequest request) {
this.diagnosticsContext =
BridgeInternal.getMetaDataDiagnosticContext(request.requestContext.cosmosDiagnostics);
this.nextRetryPolicy.onBeforeSendRequest(request);
}
@Override
public Mono shouldRetry(Exception e) {
return this.nextRetryPolicy.shouldRetry(e).flatMap(shouldRetryResult -> {
if (!shouldRetryResult.shouldRetry) {
if (!(e instanceof GoneException)) {
LOGGER.warn(
"Exception not applicable - will fail the request. Context: {}",
this.operationContextTextProvider.get(),
e);
return Mono.just(ShouldRetryResult.noRetry());
}
if (this.state.getContinuation() == null) {
final FeedRangeInternal feedRange = this.state.getFeedRange();
final Mono> effectiveRangeMono = feedRange.getNormalizedEffectiveRange(
this.client.getPartitionKeyRangeCache(),
this.diagnosticsContext,
this.client.getCollectionCache().resolveByRidAsync(
this.diagnosticsContext,
this.state.getContainerRid(),
this.requestOptionProperties)
);
return effectiveRangeMono
.map(effectiveRange -> this.state.setContinuation(
FeedRangeContinuation.create(
this.state.getContainerRid(),
this.state.getFeedRange(),
effectiveRange)))
.flatMap(state -> state.getContinuation().handleFeedRangeGone(client, (GoneException)e));
}
return this
.state
.getContinuation()
.handleFeedRangeGone(client, (GoneException)e)
.flatMap(feedRangeGoneShouldRetryResult -> {
if (!feedRangeGoneShouldRetryResult.shouldRetry) {
LOGGER.warn(
"No partition split or merge error - will fail the request. Context: {}",
this.operationContextTextProvider.get(),
e);
} else {
LOGGER.debug(
"HandleFeedRangeGone will retry. Context: {}",
this.operationContextTextProvider.get(),
e);
}
return Mono.just(feedRangeGoneShouldRetryResult);
});
}
LOGGER.trace("Retrying due to inner retry policy");
return Mono.just(shouldRetryResult);
});
}
@Override
public RetryContext getRetryContext() {
return this.retryContext;
}
}
private DocumentClientRetryPolicy getFeedRangeContinuationRetryPolicy(
RxDocumentClientImpl client,
Map requestOptionProperties,
String collectionLink,
boolean isSplitHandlingDisabled) {
DocumentClientRetryPolicy feedRangeContinuationRetryPolicy;
// constructing retry policies for changeFeed requests
DocumentClientRetryPolicy retryPolicyInstance =
client.getResetSessionTokenRetryPolicy().getRequestPolicy(null);
retryPolicyInstance = new InvalidPartitionExceptionRetryPolicy(
client.getCollectionCache(),
retryPolicyInstance,
collectionLink,
requestOptionProperties);
if (isSplitHandlingDisabled) {
// True for ChangeFeedProcessor - where all retry-logic is handled
feedRangeContinuationRetryPolicy = retryPolicyInstance;
} else {
// TODO @fabianm wire up clientContext - for now no availability strategy is wired up for ChangeFeed
// requests - and this is expected/by design for now. But it is certainly worth discussing/checking whether
// we should include change feed requests as well - there are a few challenges especially for multi master
// accounts depending on the consistency level - and usually change feed is not processed in OLTP
// scenarios, so, keeping it out of scope for now is a reasonable decision. But probably worth
// double checking this decision in a few months.
retryPolicyInstance = new PartitionKeyRangeGoneRetryPolicy(client,
client.getCollectionCache(),
client.getPartitionKeyRangeCache(),
collectionLink,
retryPolicyInstance,
requestOptionProperties);
feedRangeContinuationRetryPolicy = new FeedRangeContinuationFeedRangeGoneRetryPolicy(
client,
this.changeFeedState,
retryPolicyInstance,
requestOptionProperties,
retryPolicyInstance.getRetryContext(),
this::getOperationContextText);
}
return feedRangeContinuationRetryPolicy;
}
}