ReconnectPolicy.java
package com.shengzhiai.yugu;
import java.util.Random;
/**
* Reconnect policy of stream sessions (requirement A-03). Delay before reconnect attempt {@code n}:
* {@code min(maxDelayMs, initialDelayMs * multiplier^(n-1)) * (1 + U(-jitter, +jitter))}.
*
* <p>Defaults: enabled, 8 attempts, 500 ms, factor 2, at most 4000 ms, jitter 0.3. The waits are about
* 0.5, 1, 2, 4, 4, 4, 4 and 4 s, about 23 s in total, so the default settings ride out a network drop of
* 10 s. {@code maxAttempts} counts consecutive failed attempts; the counter starts again after each
* successful reconnect, so it bounds one outage.
*/
public final class ReconnectPolicy {
private static final ReconnectPolicy DEFAULTS = builder().build();
private static final ReconnectPolicy DISABLED = builder().enabled(false).build();
private final boolean enabled;
private final int maxAttempts;
private final long initialDelayMs;
private final double multiplier;
private final long maxDelayMs;
private final double jitter;
private ReconnectPolicy(Builder b) {
if (b.maxAttempts < 0) {
throw new IllegalArgumentException("maxAttempts must be >= 0");
}
if (b.initialDelayMs < 0 || b.maxDelayMs < 0) {
throw new IllegalArgumentException("delays must be >= 0");
}
if (b.multiplier < 1.0) {
throw new IllegalArgumentException("multiplier must be >= 1");
}
if (b.jitter < 0 || b.jitter > 1) {
throw new IllegalArgumentException("jitter must be in [0, 1]");
}
this.enabled = b.enabled;
this.maxAttempts = b.maxAttempts;
this.initialDelayMs = b.initialDelayMs;
this.multiplier = b.multiplier;
this.maxDelayMs = b.maxDelayMs;
this.jitter = b.jitter;
}
/** @return the default policy */
public static ReconnectPolicy defaults() {
return DEFAULTS;
}
/** @return a policy that never reconnects */
public static ReconnectPolicy disabled() {
return DISABLED;
}
/** @return a builder with the defaults */
public static Builder builder() {
return new Builder();
}
/** @return a builder initialised with this policy */
public Builder toBuilder() {
Builder b = new Builder();
b.enabled = enabled;
b.maxAttempts = maxAttempts;
b.initialDelayMs = initialDelayMs;
b.multiplier = multiplier;
b.maxDelayMs = maxDelayMs;
b.jitter = jitter;
return b;
}
/**
* @param n reconnect attempt, from 1
* @return delay without jitter
*/
public long baseDelayMs(int n) {
double d = initialDelayMs * Math.pow(multiplier, Math.max(0, n - 1));
return (long) Math.min((double) maxDelayMs, d);
}
/**
* @param n reconnect attempt, from 1
* @param unitRandom uniform random value in [0, 1)
* @return delay in milliseconds
*/
public long delayMs(int n, double unitRandom) {
double u = (unitRandom * 2 - 1) * jitter;
return Math.max(0, Math.round(baseDelayMs(n) * (1 + u)));
}
/**
* @param n reconnect attempt, from 1
* @param random random source
* @return delay in milliseconds
*/
public long delayMs(int n, Random random) {
return delayMs(n, random.nextDouble());
}
/** @return whether sessions reconnect */
public boolean isEnabled() {
return enabled;
}
/** @return attempts per outage */
public int getMaxAttempts() {
return maxAttempts;
}
/** @return first delay */
public long getInitialDelayMs() {
return initialDelayMs;
}
/** @return factor */
public double getMultiplier() {
return multiplier;
}
/** @return cap before jitter */
public long getMaxDelayMs() {
return maxDelayMs;
}
/** @return relative jitter */
public double getJitter() {
return jitter;
}
@Override
public String toString() {
return "ReconnectPolicy{enabled=" + enabled + ", maxAttempts=" + maxAttempts + ", initialDelayMs=" + initialDelayMs
+ ", multiplier=" + multiplier + ", maxDelayMs=" + maxDelayMs + ", jitter=" + jitter + '}';
}
/** Builder for {@link ReconnectPolicy}. */
public static final class Builder {
private boolean enabled = true;
private int maxAttempts = 8;
private long initialDelayMs = 500;
private double multiplier = 2.0;
private long maxDelayMs = 4000;
private double jitter = 0.3;
private Builder() {
}
/** @param v reconnect at all, default true @return this */
public Builder enabled(boolean v) {
this.enabled = v;
return this;
}
/** @param v consecutive failed attempts allowed per outage, default 8 @return this */
public Builder maxAttempts(int v) {
this.maxAttempts = v;
return this;
}
/** @param v first delay, default 500 ms @return this */
public Builder initialDelayMs(long v) {
this.initialDelayMs = v;
return this;
}
/** @param v factor, default 2 @return this */
public Builder multiplier(double v) {
this.multiplier = v;
return this;
}
/** @param v cap before jitter, default 4000 ms @return this */
public Builder maxDelayMs(long v) {
this.maxDelayMs = v;
return this;
}
/** @param v relative jitter, default 0.3 @return this */
public Builder jitter(double v) {
this.jitter = v;
return this;
}
/** @return the policy */
public ReconnectPolicy build() {
return new ReconnectPolicy(this);
}
}
}