Introduce NetconfTimer
[netconf.git] / apps / netconf-topology / src / main / java / org / opendaylight / netconf / topology / spi / NetconfNodeHandler.java
index 3f54900a2f04ce3690f8e588d58dc57fe9273b61..9084c4a158ddd41058281059b13ae3fda9c32363 100644 (file)
@@ -15,11 +15,9 @@ import com.google.common.util.concurrent.Futures;
 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;
@@ -44,6 +42,7 @@ import org.opendaylight.netconf.client.mdsal.api.RemoteDeviceId;
 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;
@@ -112,24 +111,26 @@ public final class NetconfNodeHandler extends AbstractRegistration implements Re
     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) {
@@ -139,10 +140,11 @@ public final class NetconfNodeHandler extends AbstractRegistration implements Re
         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
@@ -172,7 +174,7 @@ public final class NetconfNodeHandler extends AbstractRegistration implements Re
             device = new NetconfDeviceBuilder()
                 .setReconnectOnSchemasChange(node.requireReconnectOnChangedSchema())
                 .setSchemaResourcesDTO(resources)
-                .setGlobalProcessingExecutor(processingExecutor)
+                .setGlobalProcessingExecutor(schemaAssembler.executor())
                 .setId(deviceId)
                 .setSalFacade(salFacade)
                 .setDeviceActionFactory(deviceActionFactory)
@@ -200,7 +202,7 @@ public final class NetconfNodeHandler extends AbstractRegistration implements Re
 
     public synchronized void connect() {
         attempts = 1;
-        lastSleep = minSleep;
+        lastBackoff = minBackoff;
         lockedConnect();
     }
 
@@ -299,7 +301,7 @@ public final class NetconfNodeHandler extends AbstractRegistration implements Re
             return null;
         }
 
-        final long delayMillis;
+        final long backoffMillis;
 
         // We have exceeded the number of connection attempts
         if (maxAttempts > 0 && attempts >= maxAttempts) {
@@ -307,29 +309,24 @@ public final class NetconfNodeHandler extends AbstractRegistration implements Re
             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;
     }