io.trino.server.remotetask.Backoff Maven / Gradle / Ivy
/*
* 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 io.trino.server.remotetask;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Ticker;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.annotations.ThreadSafe;
import io.airlift.units.Duration;
import java.util.List;
import static com.google.common.base.Preconditions.checkArgument;
import static java.lang.Math.max;
import static java.lang.Math.min;
import static java.util.Objects.requireNonNull;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
@ThreadSafe
public class Backoff
{
private static final int MIN_RETRIES = 3;
private static final List DEFAULT_BACKOFF_DELAY_INTERVALS = ImmutableList.builder()
.add(new Duration(0, MILLISECONDS))
.add(new Duration(50, MILLISECONDS))
.add(new Duration(100, MILLISECONDS))
.add(new Duration(200, MILLISECONDS))
.add(new Duration(500, MILLISECONDS))
.build();
private final int minTries;
private final long maxFailureIntervalNanos;
private final Ticker ticker;
private final long[] backoffDelayIntervalsNanos;
private long firstFailureTime;
private long lastFailureTime;
private long failureCount;
private long failureRequestTimeTotal;
private long lastRequestStart;
public Backoff(Duration maxFailureInterval)
{
this(maxFailureInterval, Ticker.systemTicker());
}
public Backoff(Duration maxFailureInterval, Ticker ticker)
{
this(MIN_RETRIES, maxFailureInterval, ticker, DEFAULT_BACKOFF_DELAY_INTERVALS);
}
@VisibleForTesting
public Backoff(int minTries, Duration maxFailureInterval, Ticker ticker, List backoffDelayIntervals)
{
checkArgument(minTries > 0, "minTries must be at least 1");
requireNonNull(maxFailureInterval, "maxFailureInterval is null");
requireNonNull(ticker, "ticker is null");
requireNonNull(backoffDelayIntervals, "backoffDelayIntervals is null");
checkArgument(!backoffDelayIntervals.isEmpty(), "backoffDelayIntervals must contain at least one entry");
this.minTries = minTries;
this.maxFailureIntervalNanos = maxFailureInterval.roundTo(NANOSECONDS);
this.ticker = ticker;
this.backoffDelayIntervalsNanos = backoffDelayIntervals.stream()
.mapToLong(duration -> duration.roundTo(NANOSECONDS))
.toArray();
}
public synchronized long getFailureCount()
{
return failureCount;
}
public synchronized Duration getFailureDuration()
{
if (firstFailureTime == 0) {
return new Duration(0, MILLISECONDS);
}
long value = ticker.read() - firstFailureTime;
return new Duration(value, NANOSECONDS);
}
public synchronized Duration getFailureRequestTimeTotal()
{
return new Duration(max(0, failureRequestTimeTotal), NANOSECONDS);
}
public synchronized void startRequest()
{
lastRequestStart = ticker.read();
}
public synchronized void success()
{
lastRequestStart = 0;
firstFailureTime = 0;
failureCount = 0;
lastFailureTime = 0;
}
/**
* @return true if the failure is considered permanent
*/
public synchronized boolean failure()
{
long now = ticker.read();
lastFailureTime = now;
failureCount++;
if (lastRequestStart != 0) {
failureRequestTimeTotal += now - lastRequestStart;
lastRequestStart = 0;
}
if (firstFailureTime == 0) {
firstFailureTime = now;
// cannot fail on first failure
return false;
}
if (failureCount < minTries) {
return false;
}
long failureDuration = now - firstFailureTime;
return failureDuration >= maxFailureIntervalNanos;
}
public synchronized long getBackoffDelayNanos()
{
int failureCount = (int) min(backoffDelayIntervalsNanos.length, this.failureCount);
if (failureCount == 0) {
return 0;
}
// expected amount of time to delay from the last failure time
long currentDelay = backoffDelayIntervalsNanos[failureCount - 1];
// calculate expected delay from now
long nanosSinceLastFailure = ticker.read() - lastFailureTime;
return max(0, currentDelay - nanosSinceLastFailure);
}
}