Add backoff to reconnects.
[xudocci.git] / src / main / java / net / pterodactylus / xdcc / core / ConnectionBackoff.java
diff --git a/src/main/java/net/pterodactylus/xdcc/core/ConnectionBackoff.java b/src/main/java/net/pterodactylus/xdcc/core/ConnectionBackoff.java
new file mode 100644 (file)
index 0000000..5ba44a6
--- /dev/null
@@ -0,0 +1,105 @@
+package net.pterodactylus.xdcc.core;
+
+import java.time.Clock;
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import net.pterodactylus.xdcc.data.Network;
+
+/**
+ * Manages backoff times for connections.
+ *
+ * @author <a href="mailto:bombe@pterodactylus.net">David ‘Bombe’ Roden</a>
+ */
+public class ConnectionBackoff {
+
+       private static final long MILLIS_PER_SECOND = TimeUnit.MINUTES.toMillis(1);
+       private static final long MILLIS_PER_HOUR = TimeUnit.HOURS.toMillis(1);
+
+       private final Clock clock;
+       private final Map<Network, ConnectionFailureCounter> connectionFailures = new HashMap<>();
+
+       public ConnectionBackoff() {
+               this(Clock.systemDefaultZone());
+       }
+
+       public ConnectionBackoff(Clock clock) {
+               this.clock = clock;
+       }
+
+       public void connectionFailed(Network network) {
+               ConnectionFailureCounter connectionFailureCounter = getConnectionFailureCounter(network);
+               connectionFailureCounter.countFailure();
+       }
+
+       public void connectionSuccessful(Network network) {
+               ConnectionFailureCounter connectionFailureCounter = getConnectionFailureCounter(network);
+               connectionFailureCounter.reset();
+       }
+
+       public long getBackoff(Network network) {
+               ConnectionFailureCounter connectionFailureCounter = getConnectionFailureCounter(network);
+               if (!connectionFailureCounter.hasFailure()) {
+                       return 0;
+               }
+               long delay = (long) (MILLIS_PER_SECOND *
+                               Math.pow(1.2, connectionFailureCounter.getFailureCount() - 1));
+               return Math.min(MILLIS_PER_HOUR, Math.max(0,
+                               (connectionFailureCounter.getLastFailureTime() + delay) - Instant.now(clock)
+                                               .toEpochMilli()));
+       }
+
+       private ConnectionFailureCounter getConnectionFailureCounter(Network network) {
+               ConnectionFailureCounter connectionFailureCounter;
+               synchronized (connectionFailures) {
+                       if (!connectionFailures.containsKey(network)) {
+                               connectionFailures.put(network, new ConnectionFailureCounter());
+                       }
+                       connectionFailureCounter = connectionFailures.get(network);
+               }
+               return connectionFailureCounter;
+       }
+
+       private class ConnectionFailureCounter {
+
+               private final Object lock = new Object();
+               private int count;
+               private long lastFailureTime;
+
+               public void reset() {
+                       synchronized (lock) {
+                               count = 0;
+                               lastFailureTime = 0;
+                       }
+               }
+
+               public void countFailure() {
+                       synchronized (lock) {
+                               count++;
+                               lastFailureTime = Instant.now(clock).toEpochMilli();
+                       }
+               }
+
+               public boolean hasFailure() {
+                       synchronized (lock) {
+                               return (count > 0);
+                       }
+               }
+
+               public int getFailureCount() {
+                       synchronized (lock) {
+                               return count;
+                       }
+               }
+
+               public long getLastFailureTime() {
+                       synchronized (lock) {
+                               return lastFailureTime;
+                       }
+               }
+
+       }
+
+}