/* * Copyright © 2014 Cask Data, 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 co.cask.cdap.internal.app.runtime.flow; import com.google.common.math.LongMath; import com.google.common.primitives.Longs; import java.util.concurrent.TimeUnit; /** * Data structure for tracking retry and backing off calls to process method in flowlet. */ final class FlowletProcessEntry<T> implements Comparable<FlowletProcessEntry> { // Minimum back-off time in nanoseconds, 1ms. private static final long BACKOFF_MIN = TimeUnit.MILLISECONDS.toNanos(1); // Maximum back-off time in nanoseconds when increasing exponentially, 100ms. private static final long BACKOFF_MAX = TimeUnit.MILLISECONDS.toNanos(100); // Start time for switching from constant to exponentially increasing back-off time, 20ms. private static final long BACKOFF_EXP_START = TimeUnit.MILLISECONDS.toNanos(20); // Incrementing back-off time by this until reaching exponential increase range. private static final long BACKOFF_CONSTANT_INCREMENT = TimeUnit.MILLISECONDS.toNanos(1); // Doubling back-off time during exponential increase, up to maximum back-off time. private static final int BACKOFF_EXP = 2; private final ProcessSpecification<T> processSpec; private final ProcessSpecification<T> retrySpec; private final boolean isTick; /** * {@code System.nanoTime} when the next deque should happen. */ private long nextDeque; private long currentBackOff = BACKOFF_MIN; static <T> FlowletProcessEntry<T> create(ProcessSpecification<T> processSpec) { long nextDeque; try { nextDeque = LongMath.checkedAdd(System.nanoTime(), processSpec.getInitialCallDelay()); } catch (ArithmeticException e) { // in case System.nanoTime + initialCallDelay > MAX_VALUE, we simply set nextDeque to MAX_VALUE // so that the flowlet never performs a deque instead of dequing immediately. nextDeque = Long.MAX_VALUE; } return new FlowletProcessEntry<>(processSpec, null, nextDeque); } static <T> FlowletProcessEntry<T> create(ProcessSpecification<T> processSpec, ProcessSpecification<T> retrySpec) { return new FlowletProcessEntry<>(processSpec, retrySpec, 0); } private FlowletProcessEntry(ProcessSpecification<T> processSpec, ProcessSpecification<T> retrySpec, long nextDeque) { this.processSpec = processSpec; this.retrySpec = retrySpec; this.nextDeque = nextDeque; this.isTick = processSpec.isTick(); } long getNextDeque() { return nextDeque; } public boolean isRetry() { return retrySpec != null; } public void await() throws InterruptedException { long waitTime = nextDeque - System.nanoTime(); if (waitTime > 0) { TimeUnit.NANOSECONDS.sleep(waitTime); } } public boolean shouldProcess() { return nextDeque - System.nanoTime() <= 0; } @Override public int compareTo(FlowletProcessEntry o) { return Longs.compare(nextDeque, o.nextDeque); } public void resetBackOff() { nextDeque = System.nanoTime() + processSpec.getCallDelay(); currentBackOff = BACKOFF_MIN; } public void backOff() { nextDeque = System.nanoTime() + currentBackOff; if (currentBackOff < BACKOFF_EXP_START) { currentBackOff += BACKOFF_CONSTANT_INCREMENT; } else { currentBackOff = Math.min(currentBackOff * BACKOFF_EXP, BACKOFF_MAX); } } public ProcessSpecification<T> getProcessSpec() { return retrySpec == null ? processSpec : retrySpec; } public FlowletProcessEntry<T> resetRetry() { return retrySpec == null ? this : new FlowletProcessEntry<>(processSpec, null, processSpec.getCallDelay()); } public boolean isTick() { return isTick; } }