Update ts3j: resend like the official client; clean up failed connects
- ts3j: native resend timing, INIT1 resends, one clientinitiv per handshake, non-blocking disconnect. - Ignore errors from a connect attempt that has been superseded or cancelled. - Show the whole cause chain in network error messages. - -Dts3j.debug=true enables ts3j's protocol logging. - Tests run with a throwaway user.home instead of the real profile. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -105,6 +105,11 @@ public final class TeamspeakConnection implements TS3Listener {
|
|||||||
private ExecutorService eventExecutor;
|
private ExecutorService eventExecutor;
|
||||||
|
|
||||||
private volatile boolean connected;
|
private volatile boolean connected;
|
||||||
|
/**
|
||||||
|
* The connect in progress. A disconnect clears it and a newer connect replaces it, which
|
||||||
|
* tells a superseded attempt that is still failing not to clean up after itself.
|
||||||
|
*/
|
||||||
|
private volatile Object connectAttempt;
|
||||||
private volatile int selfClientId = -1;
|
private volatile int selfClientId = -1;
|
||||||
/** Package-private: read directly by {@link ConnectionStatsCollector}. */
|
/** Package-private: read directly by {@link ConnectionStatsCollector}. */
|
||||||
volatile long connectedAtMs;
|
volatile long connectedAtMs;
|
||||||
@@ -240,6 +245,8 @@ public final class TeamspeakConnection implements TS3Listener {
|
|||||||
|
|
||||||
private void doConnect(String address, int port, String nickname, String password, LocalIdentity withIdentity,
|
private void doConnect(String address, int port, String nickname, String password, LocalIdentity withIdentity,
|
||||||
String channel, String channelPassword) {
|
String channel, String channelPassword) {
|
||||||
|
Object attempt = new Object();
|
||||||
|
connectAttempt = attempt;
|
||||||
try {
|
try {
|
||||||
identity = withIdentity;
|
identity = withIdentity;
|
||||||
serverHost = address;
|
serverHost = address;
|
||||||
@@ -290,7 +297,7 @@ public final class TeamspeakConnection implements TS3Listener {
|
|||||||
// A late packet dispatched after close() rejects on the shut-down
|
// A late packet dispatched after close() rejects on the shut-down
|
||||||
// command executor; harmless during teardown.
|
// command executor; harmless during teardown.
|
||||||
if (t instanceof java.util.concurrent.RejectedExecutionException) return;
|
if (t instanceof java.util.concurrent.RejectedExecutionException) return;
|
||||||
ui.onError("Network error: " + rootMessage(t));
|
ui.onError("Network error: " + messageChain(t));
|
||||||
});
|
});
|
||||||
|
|
||||||
applyClientVersion();
|
applyClientVersion();
|
||||||
@@ -331,6 +338,9 @@ public final class TeamspeakConnection implements TS3Listener {
|
|||||||
// Only captures if this connection holds the microphone.
|
// Only captures if this connection holds the microphone.
|
||||||
applyMicrophoneState();
|
applyMicrophoneState();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
// Whoever took over reports on their own; cleaning up here would tear down what a newer
|
||||||
|
// connect has set up in the meantime.
|
||||||
|
if (connectAttempt != attempt) return;
|
||||||
connected = false;
|
connected = false;
|
||||||
ui.onError("Connection failed: " + rootMessage(e) + versionHint(e));
|
ui.onError("Connection failed: " + rootMessage(e) + versionHint(e));
|
||||||
safeCleanup();
|
safeCleanup();
|
||||||
@@ -505,6 +515,7 @@ public final class TeamspeakConnection implements TS3Listener {
|
|||||||
* close) where the JVM must not exit before the leave notification is sent.
|
* close) where the JVM must not exit before the leave notification is sent.
|
||||||
*/
|
*/
|
||||||
public void disconnectBlocking(String reason) {
|
public void disconnectBlocking(String reason) {
|
||||||
|
connectAttempt = null;
|
||||||
leaving = true;
|
leaving = true;
|
||||||
try {
|
try {
|
||||||
if (client != null) client.disconnect(reason);
|
if (client != null) client.disconnect(reason);
|
||||||
@@ -1667,6 +1678,18 @@ public final class TeamspeakConnection implements TS3Listener {
|
|||||||
return s == null ? "" : s;
|
return s == null ? "" : s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Every message along the cause chain, outermost first: network errors are vague without context. */
|
||||||
|
private static String messageChain(Throwable t) {
|
||||||
|
StringBuilder chain = new StringBuilder();
|
||||||
|
for (Throwable c = t; c != null; c = c.getCause() == c ? null : c.getCause()) {
|
||||||
|
String m = c.getMessage();
|
||||||
|
if (m == null || chain.indexOf(m) >= 0) continue;
|
||||||
|
if (chain.length() > 0) chain.append(": ");
|
||||||
|
chain.append(m);
|
||||||
|
}
|
||||||
|
return chain.length() > 0 ? chain.toString() : t.getClass().getSimpleName();
|
||||||
|
}
|
||||||
|
|
||||||
private static String rootMessage(Throwable t) {
|
private static String rootMessage(Throwable t) {
|
||||||
Throwable r = t;
|
Throwable r = t;
|
||||||
while (r.getCause() != null && r.getCause() != r) r = r.getCause();
|
while (r.getCause() != null && r.getCause() != r) r = r.getCause();
|
||||||
|
|||||||
@@ -34,6 +34,12 @@
|
|||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-surefire-plugin</artifactId>
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
<version>${surefire.version}</version>
|
<version>${surefire.version}</version>
|
||||||
|
<configuration>
|
||||||
|
<!-- The stores resolve ~/.ts3jclient once per JVM; keep every test out of the real profile. -->
|
||||||
|
<systemPropertyVariables>
|
||||||
|
<user.home>${project.build.directory}/test-home</user.home>
|
||||||
|
</systemPropertyVariables>
|
||||||
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</pluginManagement>
|
</pluginManagement>
|
||||||
|
|||||||
@@ -15,9 +15,10 @@ import javax.swing.SwingUtilities;
|
|||||||
public final class Main {
|
public final class Main {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
// ts3j is verbose by default; keep the console quiet unless debugging.
|
// ts3j is verbose by default; keep the console quiet unless asked for its protocol log
|
||||||
|
// (-Dts3j.debug=true, written to stderr).
|
||||||
try {
|
try {
|
||||||
com.github.manevolent.ts3j.util.Ts3Debugging.setEnabled(false);
|
com.github.manevolent.ts3j.util.Ts3Debugging.setEnabled(Boolean.getBoolean("ts3j.debug"));
|
||||||
} catch (Throwable ignored) {
|
} catch (Throwable ignored) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
2
ts3j
2
ts3j
Submodule ts3j updated: 45cb8fd559...2018c1c593
Reference in New Issue
Block a user