Skip to content

Commit a5190d3

Browse files
committed
Partially rewrote the reconnect system.
Should be much more intuitive to use now. Also brought the examples up-to-date.
1 parent 313a8c7 commit a5190d3

8 files changed

Lines changed: 214 additions & 36 deletions

File tree

example/ClientInfoExample.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
* <li>maps channel ID -> channel</li>
4444
* <li>gets a list of all clients</li>
4545
* <li>prints out the name of each client and the name of the channel they are in</li>
46-
* <li>and finally, disconnects</li>
46+
* <li>and finally, disconnects.</li>
4747
* </ul>
4848
*/
4949
public class ClientInfoExample {
@@ -75,7 +75,7 @@ public static void main(String[] args) {
7575
Channel channel = channelMap.get(c.getChannelId());
7676

7777
// Write the client and channel name into the console
78-
System.out.println(c.getNickname() + " in channel: " + channel.getName());
78+
System.out.println(c.getNickname() + " in channel " + channel.getName());
7979
}
8080

8181
// We're done, disconnect

example/ReconnectExample.java

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@
3434
import com.github.theholywaffle.teamspeak3.api.event.TS3EventAdapter;
3535
import com.github.theholywaffle.teamspeak3.api.event.TS3EventType;
3636
import com.github.theholywaffle.teamspeak3.api.event.TextMessageEvent;
37-
import com.github.theholywaffle.teamspeak3.api.reconnect.ReconnectingConnectionHandler;
37+
import com.github.theholywaffle.teamspeak3.api.reconnect.ConnectionHandler;
38+
import com.github.theholywaffle.teamspeak3.api.reconnect.ReconnectStrategy;
3839

3940
import java.util.ArrayList;
4041
import java.util.Collection;
@@ -48,19 +49,30 @@
4849
public class ReconnectExample {
4950

5051
// Since this ID changes when we reconnect, we need to keep track of it!
51-
private static int clientId;
52+
// It also needs to be volatile so that any changes are immediately visible
53+
// in other threads, like the one executing our event handler.
54+
private static volatile int clientId;
5255

5356
public static void main(String[] args) {
5457
final TS3Config config = new TS3Config();
5558
config.setHost("77.77.77.77");
5659
config.setDebugLevel(Level.ALL);
5760

61+
// Use default exponential backoff reconnect strategy
62+
config.setReconnectStrategy(ReconnectStrategy.exponentialBackoff());
63+
5864
// Make stuff run every time the query (re)connects
59-
config.setConnectionHandler(new ReconnectingConnectionHandler() {
65+
config.setConnectionHandler(new ConnectionHandler() {
66+
6067
@Override
61-
public void setUpQuery(TS3Query ts3Query) {
68+
public void onConnect(TS3Query ts3Query) {
6269
stuffThatNeedsToRunEveryTimeTheQueryConnects(ts3Query.getApi());
6370
}
71+
72+
@Override
73+
public void onDisconnect(TS3Query ts3Query) {
74+
// Nothing
75+
}
6476
});
6577

6678
final TS3Query query = new TS3Query(config);
@@ -72,15 +84,18 @@ public void setUpQuery(TS3Query ts3Query) {
7284
stuffThatOnlyEverNeedsToBeRunOnce(query.getApi());
7385

7486
doSomethingThatTakesAReallyLongTime(query.getAsyncApi());
87+
88+
// Disconnect once we're done
89+
query.exit();
7590
}
7691

7792
private static void stuffThatNeedsToRunEveryTimeTheQueryConnects(TS3Api api) {
7893
// Logging in, selecting the virtual server, selecting a channel
7994
// and setting a nickname needs to be done every time we reconnect
8095
api.login("serveradmin", "serveradminpassword");
8196
api.selectVirtualServerById(1);
82-
api.setNickname("PutPutBot");
8397
// api.moveQuery(x);
98+
api.setNickname("PutPutBot");
8499

85100
// What events we listen to also resets
86101
api.registerEvent(TS3EventType.TEXT_CHANNEL, 0);
@@ -155,5 +170,7 @@ public void handleSuccess(Integer result) {
155170
for (Integer channelId : createdChannelIds) {
156171
api.deleteChannel(channelId, true);
157172
}
173+
174+
api.whoAmI().getUninterruptibly(); // Wait for all previous commands to complete
158175
}
159176
}

src/main/java/com/github/theholywaffle/teamspeak3/TS3Config.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
import com.github.theholywaffle.teamspeak3.TS3Query.FloodRate;
3030
import com.github.theholywaffle.teamspeak3.api.reconnect.ConnectionHandler;
31+
import com.github.theholywaffle.teamspeak3.api.reconnect.ReconnectStrategy;
3132

3233
import java.util.logging.Level;
3334

@@ -39,7 +40,8 @@ public class TS3Config {
3940
private Level level = Level.WARNING;
4041
private boolean debugToFile = false;
4142
private int commandTimeout = 4000;
42-
private ConnectionHandler connectionHandler = ConnectionHandler.DISCONNECT;
43+
private ReconnectStrategy reconnectStrategy = ReconnectStrategy.disconnect();
44+
private ConnectionHandler connectionHandler = null;
4345

4446
public TS3Config setHost(String host) {
4547
this.host = host;
@@ -113,8 +115,17 @@ int getCommandTimeout() {
113115
return commandTimeout;
114116
}
115117

118+
public TS3Config setReconnectStrategy(ReconnectStrategy reconnectStrategy) {
119+
if (reconnectStrategy == null) throw new NullPointerException("reconnectStrategy cannot be null!");
120+
this.reconnectStrategy = reconnectStrategy;
121+
return this;
122+
}
123+
124+
ReconnectStrategy getReconnectStrategy() {
125+
return reconnectStrategy;
126+
}
127+
116128
public TS3Config setConnectionHandler(ConnectionHandler connectionHandler) {
117-
if (connectionHandler == null) throw new NullPointerException("connectionHandler cannot be null!");
118129
this.connectionHandler = connectionHandler;
119130
return this;
120131
}

src/main/java/com/github/theholywaffle/teamspeak3/TS3Query.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
import com.github.theholywaffle.teamspeak3.api.Callback;
3030
import com.github.theholywaffle.teamspeak3.api.exception.TS3ConnectionFailedException;
31+
import com.github.theholywaffle.teamspeak3.api.reconnect.ConnectionHandler;
3132
import com.github.theholywaffle.teamspeak3.commands.CQuit;
3233
import com.github.theholywaffle.teamspeak3.commands.Command;
3334
import com.github.theholywaffle.teamspeak3.log.LogHandler;
@@ -60,6 +61,7 @@ public int getMs() {
6061
private final ExecutorService userThreadPool = Executors.newCachedThreadPool();
6162
private final EventManager eventManager = new EventManager();
6263
private final TS3Config config;
64+
private final ConnectionHandler connectionHandler;
6365

6466
private QueryIO io;
6567
private TS3Api api;
@@ -85,6 +87,7 @@ public TS3Query(TS3Config config) {
8587
log.addHandler(new LogHandler(config.getDebugToFile()));
8688
log.setLevel(config.getDebugLevel());
8789
this.config = config;
90+
this.connectionHandler = config.getReconnectStrategy().create(config.getConnectionHandler());
8891
}
8992

9093
// PUBLIC
@@ -103,7 +106,7 @@ public TS3Query connect() {
103106
}
104107

105108
try {
106-
config.getConnectionHandler().onConnect(this);
109+
connectionHandler.onConnect(this);
107110
} catch (Throwable t) {
108111
log.log(Level.SEVERE, "ConnectionHandler threw exception in connect handler", t);
109112
}
@@ -201,7 +204,7 @@ void fireDisconnect() {
201204
@Override
202205
public void run() {
203206
try {
204-
config.getConnectionHandler().onDisconnect(TS3Query.this);
207+
connectionHandler.onDisconnect(TS3Query.this);
205208
} catch (Throwable t) {
206209
log.log(Level.SEVERE, "ConnectionHandler threw exception in disconnect handler", t);
207210
}

src/main/java/com/github/theholywaffle/teamspeak3/api/reconnect/ConnectionHandler.java

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,4 @@ public interface ConnectionHandler {
3333
void onConnect(TS3Query ts3Query);
3434

3535
void onDisconnect(TS3Query ts3Query);
36-
37-
ConnectionHandler DISCONNECT = new DisconnectingConnectionHandler();
38-
39-
ConnectionHandler DEFAULT_RESTART = new ReconnectingConnectionHandler() {
40-
@Override
41-
public void setUpQuery(TS3Query ts3Query) {
42-
// Extend ReconnectingConnectionHandler and set it in config.setConnectionHandler!
43-
}
44-
};
4536
}

src/main/java/com/github/theholywaffle/teamspeak3/api/reconnect/DisconnectingConnectionHandler.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,26 @@
3030

3131
public class DisconnectingConnectionHandler implements ConnectionHandler {
3232

33+
private final ConnectionHandler userConnectionHandler;
34+
35+
public DisconnectingConnectionHandler(ConnectionHandler userConnectionHandler) {
36+
this.userConnectionHandler = userConnectionHandler;
37+
}
38+
3339
@Override
3440
public void onConnect(TS3Query ts3Query) {
35-
// Nothing
41+
if (userConnectionHandler != null) {
42+
userConnectionHandler.onConnect(ts3Query);
43+
}
3644
}
3745

3846
@Override
3947
public void onDisconnect(TS3Query ts3Query) {
4048
TS3Query.log.severe("[Connection] Disconnected from TS3 server");
4149
ts3Query.exit();
50+
51+
if (userConnectionHandler != null) {
52+
userConnectionHandler.onDisconnect(ts3Query);
53+
}
4254
}
4355
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package com.github.theholywaffle.teamspeak3.api.reconnect;
2+
3+
public abstract class ReconnectStrategy {
4+
5+
private static final int CONSTANT_BACKOFF = 10000;
6+
private static final int START_TIMEOUT = 1000;
7+
private static final int TIMEOUT_CAP = 60000;
8+
private static final int ADDEND = 2000;
9+
private static final double MULTIPLIER = 1.5;
10+
11+
private ReconnectStrategy() {}
12+
13+
public abstract ConnectionHandler create(ConnectionHandler userConnectionHandler);
14+
15+
public static ReconnectStrategy userControlled() {
16+
return new UserControlled();
17+
}
18+
19+
public static ReconnectStrategy disconnect() {
20+
return new Disconnect();
21+
}
22+
23+
public static ReconnectStrategy constantBackoff() {
24+
return constantBackoff(CONSTANT_BACKOFF);
25+
}
26+
27+
public static ReconnectStrategy constantBackoff(int timeout) {
28+
return new Constant(timeout);
29+
}
30+
31+
public static ReconnectStrategy linearBackoff() {
32+
return linearBackoff(START_TIMEOUT, ADDEND, TIMEOUT_CAP);
33+
}
34+
35+
public static ReconnectStrategy linearBackoff(int startTimeout, int addend) {
36+
return linearBackoff(startTimeout, addend, TIMEOUT_CAP);
37+
}
38+
39+
public static ReconnectStrategy linearBackoff(int startTimeout, int addend, int timeoutCap) {
40+
return new Linear(startTimeout, addend, timeoutCap);
41+
}
42+
43+
public static ReconnectStrategy exponentialBackoff() {
44+
return exponentialBackoff(START_TIMEOUT, MULTIPLIER, TIMEOUT_CAP);
45+
}
46+
47+
public static ReconnectStrategy exponentialBackoff(int startTimeout, double multiplier) {
48+
return exponentialBackoff(startTimeout, multiplier, TIMEOUT_CAP);
49+
}
50+
51+
public static ReconnectStrategy exponentialBackoff(int startTimeout, double multiplier, int timeoutCap) {
52+
return new Exponential(startTimeout, multiplier, timeoutCap);
53+
}
54+
55+
private static class UserControlled extends ReconnectStrategy {
56+
57+
@Override
58+
public ConnectionHandler create(ConnectionHandler userConnectionHandler) {
59+
String message = "userConnectionHandler cannot be null when using strategy UserControlled!";
60+
if (userConnectionHandler == null) throw new NullPointerException(message);
61+
return userConnectionHandler;
62+
}
63+
}
64+
65+
private static class Disconnect extends ReconnectStrategy {
66+
67+
@Override
68+
public ConnectionHandler create(ConnectionHandler userConnectionHandler) {
69+
return new DisconnectingConnectionHandler(userConnectionHandler);
70+
}
71+
}
72+
73+
private static class Constant extends ReconnectStrategy {
74+
75+
private final int timeout;
76+
77+
public Constant(int timeout) {
78+
if (timeout <= 0) throw new IllegalArgumentException("Timeout must be greater than 0");
79+
80+
this.timeout = timeout;
81+
}
82+
83+
@Override
84+
public ConnectionHandler create(ConnectionHandler userConnectionHandler) {
85+
return new ReconnectingConnectionHandler(userConnectionHandler, timeout, timeout, 0, 1.0);
86+
}
87+
}
88+
89+
private static class Linear extends ReconnectStrategy {
90+
91+
private final int startTimeout;
92+
private final int addend;
93+
private final int timeoutCap;
94+
95+
private Linear(int startTimeout, int addend, int timeoutCap) {
96+
if (startTimeout <= 0) throw new IllegalArgumentException("Starting timeout must be greater than 0");
97+
if (addend <= 0) throw new IllegalArgumentException("Addend must be greater than 0");
98+
99+
this.startTimeout = startTimeout;
100+
this.addend = addend;
101+
this.timeoutCap = timeoutCap;
102+
}
103+
104+
@Override
105+
public ConnectionHandler create(ConnectionHandler userConnectionHandler) {
106+
return new ReconnectingConnectionHandler(userConnectionHandler, startTimeout, timeoutCap, addend, 1.0);
107+
}
108+
}
109+
110+
private static class Exponential extends ReconnectStrategy {
111+
112+
private final int startTimeout;
113+
private final double multiplier;
114+
private final int timeoutCap;
115+
116+
private Exponential(int startTimeout, double multiplier, int timeoutCap) {
117+
if (startTimeout <= 0) throw new IllegalArgumentException("Starting timeout must be greater than 0");
118+
if (multiplier <= 1.0) throw new IllegalArgumentException("Multiplier must be greater than 1");
119+
120+
this.startTimeout = startTimeout;
121+
this.multiplier = multiplier;
122+
this.timeoutCap = timeoutCap;
123+
}
124+
125+
@Override
126+
public ConnectionHandler create(ConnectionHandler userConnectionHandler) {
127+
return new ReconnectingConnectionHandler(userConnectionHandler, startTimeout, timeoutCap, 0, multiplier);
128+
}
129+
}
130+
}

0 commit comments

Comments
 (0)