org.opendaylight.controller.cluster.access.client.AveragingProgressTracker Maven / Gradle / Ivy
/*
* Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v10.html
*/
package org.opendaylight.controller.cluster.access.client;
import java.util.concurrent.TimeUnit;
/**
* A ProgressTracker subclass which uses {@code ticksWorkedPerClosedTask} to compute delays.
*
* This class has {@code tasksOpenLimit} used as a (weak) limit,
* as number of open tasks approaches that value, delays computed are increasing.
*
*
In order to keep {@code estimateIsolatedDelay} values from raising unreasonably high,
* {@code defaultTicksPerTask} acts as a maximal value. {@code openTask} may return
* higher value if there are tasks above the limit.
*
*
On the other hand, there is no delay when number of open tasks is half the limit or less,
* in order to prevent backend from running out of tasks while there may be waiting frontend threads.
*
*
* This class is NOT thread-safe.
*
* @author Vratko Polak
*/
final class AveragingProgressTracker extends ProgressTracker {
private static final long DEFAULT_TICKS_PER_TASK = TimeUnit.MILLISECONDS.toNanos(500);
/**
* The implementation will avoid having more that this number of tasks open.
*/
private final long tasksOpenLimit;
/**
* We do not delay tasks until their count hits this threshold.
*/
private final long noDelayThreshold;
/**
* Create an idle tracker with limit and specified ticks per task value to use as default.
*
* @param limit of open tasks to avoid exceeding
* @param ticksPerTask value to use as default
*/
private AveragingProgressTracker(final long limit, final long ticksPerTask) {
super(ticksPerTask);
tasksOpenLimit = limit;
noDelayThreshold = limit / 2;
}
/**
* Create a default idle tracker with given limit.
*
* @param limit of open tasks to avoid exceeding
*/
AveragingProgressTracker(final long limit) {
this(limit, DEFAULT_TICKS_PER_TASK);
}
/**
* Construct a new tracker suitable for a new task queue related to a "reconnect".
*
*
The limit is set independently of the old tracker.
*
* @param oldTracker the tracker used for the previously used backend
* @param limit of open tasks to avoid exceeding
* @param now tick number corresponding to caller's present
*/
AveragingProgressTracker(final ProgressTracker oldTracker, final long limit, final long now) {
super(oldTracker, now);
tasksOpenLimit = limit;
noDelayThreshold = limit / 2;
}
/**
* Construct a new tracker suitable for a new task queue related to a "reconnect".
*
*
The limit is copied from the old tracker.
*
* @param oldTracker the tracker used for the previously used backend
* @param now tick number corresponding to caller's present
*/
AveragingProgressTracker(final AveragingProgressTracker oldTracker, final long now) {
this(oldTracker, oldTracker.tasksOpenLimit, now);
}
// Protected read-only methods
/**
* Give an estimate of a fair delay, assuming delays caused by other opened tasks are ignored.
*
*
This implementation returns zero delay if number of open tasks is half of limit or less.
* Else the delay is computed, aiming to keep number of open tasks at 3/4 of limit,
* assuming backend throughput remains constant.
*
*
As the number of open tasks approaches the limit,
* the computed delay increases, but it never exceeds defaultTicksPerTask.
* That means the actual number of open tasks can exceed the limit.
*
* @param now tick number corresponding to caller's present
* @return delay (in ticks) after which another openTask() would be fair to be called by the same thread again
*/
@Override
protected long estimateIsolatedDelay(final long now) {
final long open = tasksOpen();
if (open <= noDelayThreshold) {
return 0L;
}
if (open >= tasksOpenLimit) {
return defaultTicksPerTask();
}
/*
* Calculate the task capacity relative to the limit on open tasks. In real terms this value can be
* in the open interval (0.0, 0.5).
*/
final double relativeRemainingCapacity = 1.0 - (double) open / tasksOpenLimit;
/*
* Calculate delay coefficient. It increases in inverse proportion to relative remaining capacity, approaching
* infinity as remaining capacity approaches 0.0.
*/
final double delayCoefficient = (0.5 - relativeRemainingCapacity) / relativeRemainingCapacity;
final long delay = (long) (ticksWorkedPerClosedTask(now) * delayCoefficient);
/*
* Cap the result to defaultTicksPerTask, since the calculated delay may overstep it.
*/
return Math.min(delay, defaultTicksPerTask());
}
}