import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import io.netty.util.Timeout;
-import io.netty.util.Timer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CancellationException;
-import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import org.checkerframework.checker.lock.qual.GuardedBy;
import org.checkerframework.checker.lock.qual.Holding;
import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceServices;
import org.opendaylight.netconf.client.mdsal.api.SchemaResourceManager;
import org.opendaylight.netconf.client.mdsal.spi.KeepaliveSalFacade;
+import org.opendaylight.netconf.common.NetconfTimer;
import org.opendaylight.netconf.transport.api.UnsupportedConfigurationException;
import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Uri;
import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.optional.rev221225.NetconfNodeAugmentedOptional;
private final @NonNull NetconfClientConfiguration clientConfig;
private final @NonNull NetconfDeviceCommunicator communicator;
private final @NonNull RemoteDeviceHandler delegate;
- private final @NonNull Timer timer;
+ private final @NonNull NetconfTimer timer;
private final @NonNull RemoteDeviceId deviceId;
- private final long maxSleep;
+ private final long maxBackoff;
private final long maxAttempts;
- private final int minSleep;
- private final double sleepFactor;
+ private final int minBackoff;
+ private final double backoffMultiplier;
+ private final double jitter;
@GuardedBy("this")
private long attempts;
@GuardedBy("this")
- private long lastSleep;
+ private long lastBackoff;
@GuardedBy("this")
private Task currentTask;
- public NetconfNodeHandler(final NetconfClientFactory clientFactory, final Timer timer,
+ public NetconfNodeHandler(final NetconfClientFactory clientFactory, final NetconfTimer timer,
final BaseNetconfSchemas baseSchemas, final SchemaResourceManager schemaManager,
- final Executor processingExecutor, final NetconfClientConfigurationBuilderFactory builderFactory,
+ final NetconfTopologySchemaAssembler schemaAssembler,
+ final NetconfClientConfigurationBuilderFactory builderFactory,
final DeviceActionFactory deviceActionFactory, final RemoteDeviceHandler delegate,
final RemoteDeviceId deviceId, final NodeId nodeId, final NetconfNode node,
final NetconfNodeAugmentedOptional nodeOptional) {
this.deviceId = requireNonNull(deviceId);
maxAttempts = node.requireMaxConnectionAttempts().toJava();
- minSleep = node.requireBetweenAttemptsTimeoutMillis().toJava();
- sleepFactor = node.requireSleepFactor().doubleValue();
- final long potentialMaxSleep = node.requireMaxTimeoutBetweenAttemptsMillis().toJava();
- maxSleep = potentialMaxSleep >= minSleep ? potentialMaxSleep : minSleep;
+ minBackoff = node.requireMinBackoffMillis().toJava();
+ backoffMultiplier = node.requireBackoffMultiplier().doubleValue();
+ final long potentialMaxBackoff = node.requireMaxBackoffMillis().toJava();
+ maxBackoff = potentialMaxBackoff >= minBackoff ? potentialMaxBackoff : minBackoff;
+ jitter = node.getBackoffJitter().doubleValue();
// Setup reconnection on empty context, if so configured
// FIXME: NETCONF-925: implement this
device = new NetconfDeviceBuilder()
.setReconnectOnSchemasChange(node.requireReconnectOnChangedSchema())
.setSchemaResourcesDTO(resources)
- .setGlobalProcessingExecutor(processingExecutor)
+ .setGlobalProcessingExecutor(schemaAssembler.executor())
.setId(deviceId)
.setSalFacade(salFacade)
.setDeviceActionFactory(deviceActionFactory)
public synchronized void connect() {
attempts = 1;
- lastSleep = minSleep;
+ lastBackoff = minBackoff;
lockedConnect();
}
return null;
}
- final long delayMillis;
+ final long backoffMillis;
// We have exceeded the number of connection attempts
if (maxAttempts > 0 && attempts >= maxAttempts) {
return new ConnectGivenUpException("Given up connecting " + deviceId + " after " + attempts + " attempts");
}
- // First connection attempt gets initialized to minimum sleep, each subsequent is exponentially backed off
- // by sleepFactor.
+ // First connection attempt gets initialized to minimum backoff, each subsequent is exponentially backed off
+ // by backoffMultiplier (default 1.5) until reach max sleep and randomized by +/- jitter (default 0.1).
if (attempts != 0) {
- final long nextSleep = (long) (lastSleep * sleepFactor);
- if (nextSleep <= maxSleep) {
- // check for overflow
- delayMillis = nextSleep >= 0 ? nextSleep : maxSleep;
- } else {
- delayMillis = maxSleep;
- }
+ final var currentBackoff = Math.min(lastBackoff * backoffMultiplier, maxBackoff);
+ backoffMillis = (long) (currentBackoff * (Math.random() * (jitter * 2) + (1 - jitter)));
} else {
- delayMillis = minSleep;
+ backoffMillis = minBackoff;
}
attempts++;
- lastSleep = delayMillis;
- LOG.debug("Retrying {} connection attempt {} after {} milliseconds", deviceId, attempts, delayMillis);
+ lastBackoff = backoffMillis;
+ LOG.debug("Retrying {} connection attempt {} after {} milliseconds", deviceId, attempts, backoffMillis);
// Schedule a task for the right time. We always go through the executor to eliminate the special case of
// immediate reconnect. While we could check and got to lockedConnect(), it makes for a rare special case.
// That special case makes for more code paths to test and introduces additional uncertainty as to whether
// the attempt was executed on this thread or not.
- currentTask = new SleepingTask(timer.newTimeout(this::reconnect, delayMillis, TimeUnit.MILLISECONDS));
+ currentTask = new SleepingTask(timer.newTimeout(this::reconnect, backoffMillis, TimeUnit.MILLISECONDS));
return null;
}