diff --git a/src/main/java/com/jagrosh/discordipc/IPCClient.java b/src/main/java/com/jagrosh/discordipc/IPCClient.java index bca56c3..631bea3 100644 --- a/src/main/java/com/jagrosh/discordipc/IPCClient.java +++ b/src/main/java/com/jagrosh/discordipc/IPCClient.java @@ -23,6 +23,7 @@ import com.jagrosh.discordipc.entities.pipe.Pipe; import com.jagrosh.discordipc.entities.pipe.PipeStatus; import com.jagrosh.discordipc.exceptions.NoDiscordClientException; +import com.jagrosh.discordipc.impl.Backoff; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -56,15 +57,17 @@ */ public final class IPCClient implements Closeable { private static final Logger LOGGER = LoggerFactory.getLogger(IPCClient.class); - private Logger FORCED_LOGGER = null; + private final Backoff RECONNECT_TIME_MS = new Backoff(500, 60 * 1000); private final long clientId; private final boolean autoRegister; private final HashMap callbacks = new HashMap<>(); private final String applicationId, optionalSteamId; private volatile Pipe pipe; + private Logger forcedLogger = null; private IPCListener listener = null; private Thread readThread = null; private String encoding = "UTF-8"; + private long nextDelay = 0L; private boolean debugMode; private boolean verboseLogging; @@ -213,7 +216,7 @@ private static int getPID() { * @return the current logger to use */ public Logger getCurrentLogger(final Logger instance) { - return FORCED_LOGGER != null ? FORCED_LOGGER : instance; + return forcedLogger != null ? forcedLogger : instance; } /** @@ -222,7 +225,7 @@ public Logger getCurrentLogger(final Logger instance) { * @param forcedLogger The logger instance to be used */ public void setForcedLogger(Logger forcedLogger) { - FORCED_LOGGER = forcedLogger; + this.forcedLogger = forcedLogger; } /** @@ -356,12 +359,24 @@ public void setVerboseLogging(boolean verboseLogging) { * @throws IllegalStateException There is an open connection on this IPCClient. * @throws NoDiscordClientException No client of the provided {@link DiscordBuild build type}(s) was found. */ - public void connect(DiscordBuild... preferredOrder) throws NoDiscordClientException { + public void connect(DiscordBuild... preferredOrder) throws NoDiscordClientException, InterruptedException { checkConnected(false); + long timeToConnect; + while ((timeToConnect = nextDelay - System.currentTimeMillis()) > 0) { + if (debugMode) { + getCurrentLogger(LOGGER).info("[DEBUG] Attempting connection in: " + timeToConnect + "ms"); + } + Thread.sleep(timeToConnect); + } callbacks.clear(); pipe = null; - pipe = Pipe.openPipe(this, clientId, callbacks, preferredOrder); + try { + pipe = Pipe.openPipe(this, clientId, callbacks, preferredOrder); + } catch (Exception ex) { + updateReconnectTime(); + throw ex; + } if (isAutoRegister()) { try { @@ -743,12 +758,20 @@ private void readPipe(final IPCClient instance) { getCurrentLogger(LOGGER).error(String.format("Reading thread encountered an Exception: %s", ex)); pipe.setStatus(PipeStatus.DISCONNECTED); - if (listener != null) + if (listener != null) { + RECONNECT_TIME_MS.reset(); + updateReconnectTime(); listener.onDisconnect(instance, ex); + } } } - // Private static methods + /** + * Sets the next delay before re-attempting connection. + */ + private void updateReconnectTime() { + nextDelay = System.currentTimeMillis() + RECONNECT_TIME_MS.nextDelay(); + } /** * Constants representing a Response to an Ask to Join or Spectate Request diff --git a/src/main/java/com/jagrosh/discordipc/impl/Backoff.java b/src/main/java/com/jagrosh/discordipc/impl/Backoff.java new file mode 100644 index 0000000..16b23c5 --- /dev/null +++ b/src/main/java/com/jagrosh/discordipc/impl/Backoff.java @@ -0,0 +1,36 @@ +package com.jagrosh.discordipc.impl; + +import java.util.Random; +import java.util.concurrent.TimeUnit; + +public class Backoff { + private final long minAmount; + private final long maxAmount; + private long current; + private int fails; + private final Random randGenerator; + + public Backoff(long min, long max) { + this.minAmount = min; + this.maxAmount = max; + this.current = min; + this.fails = 0; + this.randGenerator = new Random(); + } + + public void reset() { + fails = 0; + current = minAmount; + } + + public long nextDelay() { + fails++; + double delay = current * 2.0 * rand01(); + current = Math.min(current + (long) delay, maxAmount); + return current; + } + + private double rand01() { + return randGenerator.nextDouble(); + } +}