All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.couchbase.client.core.retry.RetryOrchestratorProtostellar Maven / Gradle / Ivy

There is a newer version: 3.7.2
Show newest version
/*
 * Copyright (c) 2023 Couchbase, Inc.
 *
 * Licensed 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.couchbase.client.core.retry;

import com.couchbase.client.core.CoreProtostellar;
import com.couchbase.client.core.annotation.Stability;
import com.couchbase.client.core.cnc.Event;
import com.couchbase.client.core.cnc.events.request.RequestNotRetriedEvent;
import com.couchbase.client.core.cnc.events.request.RequestRetryScheduledEvent;
import com.couchbase.client.core.error.AmbiguousTimeoutException;
import com.couchbase.client.core.error.UnambiguousTimeoutException;
import com.couchbase.client.core.error.context.CancellationErrorContext;
import com.couchbase.client.core.msg.CancellationReason;
import com.couchbase.client.core.protostellar.ProtostellarBaseRequest;
import com.couchbase.client.core.protostellar.ProtostellarContext;
import com.couchbase.client.core.protostellar.ProtostellarRequest;

import java.time.Duration;
import java.util.Optional;

import static com.couchbase.client.core.retry.RetryOrchestrator.controlledBackoff;

@Stability.Internal
public class RetryOrchestratorProtostellar {

  public static ProtostellarRequestBehaviour shouldRetry(CoreProtostellar core, ProtostellarRequest request, RetryReason reason) {
    ProtostellarContext ctx = core.context();

    // Note there is intentionally no timeout handling here - that is left to GRPC to raise.

    if (reason.alwaysRetry()) {
      return retryWithDuration(ctx, request, controlledBackoff(request.retryAttempts()), reason);
    }

    try {
      RetryAction retryAction;

      // The 99% case is that the retry strategy is the default BestEffortRetryStrategy.INSTANCE.  We can avoid turning the ProtostellarRequest into a Request in this case (Request is required by
      // the RetryStrategy public API).
      if (request.retryStrategy() == BestEffortRetryStrategy.INSTANCE) {
        retryAction = RetryAction.withDuration(Duration.ofMillis(50));
      }
      else {
        ProtostellarBaseRequest wrapper = new ProtostellarBaseRequest(core, request);

        retryAction = request.retryStrategy().shouldRetry(wrapper, reason).get();
      }

      Optional duration = retryAction.duration();
      if (duration.isPresent()) {
        Duration cappedDuration = capDuration(duration.get(), request);
        return retryWithDuration(ctx, request, cappedDuration, reason);
      } else {
        ctx.environment().eventBus().publish(new RequestNotRetriedEvent(Event.Severity.DEBUG, request.getClass(), request.context(), reason, null));
        return request.cancel(CancellationReason.noMoreRetries(reason));
      }
    }
    catch (Throwable throwable) {
      ctx.environment().eventBus().publish(
        new RequestNotRetriedEvent(Event.Severity.INFO, request.getClass(), request.context(), reason, throwable)
      );
    }

    // If we're retrying we're either going to do that or fail the request due to timeout.
    throw new IllegalStateException("Internal bug - should not reach here");
  }

  private static ProtostellarRequestBehaviour retryWithDuration(final ProtostellarContext ctx, final ProtostellarRequest request,
                                        final Duration duration, final RetryReason reason) {
    Duration cappedDuration = capDuration(duration, request);
    ctx.environment().eventBus().publish(
      new RequestRetryScheduledEvent(cappedDuration, request.context(), request.getClass(), reason)
    );
    request.incrementRetryAttempts(cappedDuration, reason);

    return ProtostellarRequestBehaviour.retry(cappedDuration);
  }

  @Stability.Internal
  public static Duration capDuration(final Duration uncappedDuration, final ProtostellarRequest request) {
    long theoreticalTimeout = System.nanoTime() + uncappedDuration.toNanos();
    long absoluteTimeout = request.absoluteTimeout();
    long timeoutDelta = theoreticalTimeout - absoluteTimeout;
    if (timeoutDelta > 0) {
      Duration cappedDuration = uncappedDuration.minus(Duration.ofNanos(timeoutDelta));
      if (cappedDuration.isNegative()) {
        return uncappedDuration; // something went wrong, return the uncapped one as a safety net
      }
      return cappedDuration;

    }
    return uncappedDuration;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy