From d28bb57598b2500fc7baa14ce425246a993d3f9c Mon Sep 17 00:00:00 2001 From: ericek111 Date: Fri, 25 Sep 2026 10:30:34 +0000 Subject: [PATCH] Release a previous connection's resources on connect A connect on an instance that was still connected, or still connecting, replaced the socket and audio pipelines without closing them. The fields shared between the connect, event, action and UI threads are now volatile, and read once where they are used. Co-Authored-By: Claude Opus 5.5 --- .../ts3client/net/TeamspeakConnection.java | 56 ++++++++++++------- 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/ts3-client/core/src/main/java/com/ts3client/net/TeamspeakConnection.java b/ts3-client/core/src/main/java/com/ts3client/net/TeamspeakConnection.java index eec84bc..26875ec 100644 --- a/ts3-client/core/src/main/java/com/ts3client/net/TeamspeakConnection.java +++ b/ts3-client/core/src/main/java/com/ts3client/net/TeamspeakConnection.java @@ -90,19 +90,23 @@ public final class TeamspeakConnection implements TS3Listener { final BanAdmin bans = new BanAdmin(this); private final AvatarAdmin avatarAdmin = new AvatarAdmin(this); + /* + * Set up by the connect thread and torn down by whichever thread notices the end; read from + * the UI, event and action threads, so each reader copies the field before using it. + */ /** Package-private: read directly by {@link ConnectionStatsCollector}. */ - LocalTeamspeakClientSocket client; - private VoiceInput microphone; - private VoiceOutput playback; - private LocalIdentity identity; - private FileTransferManager fileTransfers; - private ChatLogStore chatLogs; + volatile LocalTeamspeakClientSocket client; + private volatile VoiceInput microphone; + private volatile VoiceOutput playback; + private volatile LocalIdentity identity; + private volatile FileTransferManager fileTransfers; + private volatile ChatLogStore chatLogs; /** * Runs ts3j's event callbacks. Supplying it ourselves (rather than leaving ts3j to make * its own) is what lets {@link #awaitEventsProcessed()} tell when everything the server * has already sent has been handled. Closed together with the socket. */ - private ExecutorService eventExecutor; + private volatile ExecutorService eventExecutor; /** Runs this connection's requests to the server; created on connect, shut down with the socket. */ private volatile ExecutorService actions; @@ -248,6 +252,9 @@ public final class TeamspeakConnection implements TS3Listener { String channel, String channelPassword) { Object attempt = new Object(); connectAttempt = attempt; + // A socket or audio line an earlier connect left behind must not outlive it. + connected = false; + safeCleanup(); try { identity = withIdentity; serverHost = address; @@ -570,7 +577,8 @@ public final class TeamspeakConnection implements TS3Listener { // ---- self actions ---- public void setMicMuted(boolean muted) { - if (microphone != null) microphone.setMuted(muted); + VoiceInput mic = microphone; + if (mic != null) mic.setMuted(muted); sound(muted ? SoundEvent.SOUND_CAPTURE_MUTED : SoundEvent.SOUND_CAPTURE_UNMUTED); pushSelfFlags(); } @@ -580,22 +588,26 @@ public final class TeamspeakConnection implements TS3Listener { * status the server (and other clients) see, and without a mute/unmute sound. */ public void setMicLocalMuted(boolean muted) { - if (microphone != null) microphone.setLocalMuted(muted); + VoiceInput mic = microphone; + if (mic != null) mic.setLocalMuted(muted); } public void setDeafened(boolean deaf) { // Announce muting while we can still be heard, and unmuting once we can again. if (deaf) sound(SoundEvent.SOUND_PLAYBACK_MUTED); deafened = deaf; - if (playback != null) playback.setDeafened(deaf); + VoiceOutput out = playback; + if (out != null) out.setDeafened(deaf); if (!deaf) sound(SoundEvent.SOUND_PLAYBACK_UNMUTED); pushSelfFlags(); } private void pushSelfFlags() { - if (microphone == null || playback == null) return; - boolean micMuted = microphone.isMuted(); - boolean deaf = playback.isDeafened(); + VoiceInput mic = microphone; + VoiceOutput out = playback; + if (mic == null || out == null) return; + boolean micMuted = mic.isMuted(); + boolean deaf = out.isDeafened(); updateSelf(self -> { self.inputMuted = micMuted; self.outputMuted = deaf; @@ -815,7 +827,8 @@ public final class TeamspeakConnection implements TS3Listener { public double getPingMillis() { try { - return client != null ? client.getPing().getKey() * 1000.0 : -1; + LocalTeamspeakClientSocket sock = client; + return sock != null ? sock.getPing().getKey() * 1000.0 : -1; } catch (Exception e) { return -1; } @@ -972,9 +985,10 @@ public final class TeamspeakConnection implements TS3Listener { private Contact applyContactPlayback(ClientEntry client) { if (client == null || client.self) return null; Contact contact = contacts.find(client.uniqueId); - if (contact != null && playback != null) { - if (contact.automute) playback.setClientMuted(client.id, true); - playback.setClientVolume(client.id, contact.volumeGain()); + VoiceOutput out = playback; + if (contact != null && out != null) { + if (contact.automute) out.setClientMuted(client.id, true); + out.setClientVolume(client.id, contact.volumeGain()); } return contact; } @@ -991,7 +1005,8 @@ public final class TeamspeakConnection implements TS3Listener { /** Remembers a per-client volume, in the contact entry when the client has one. */ public void setClientVolumeModifier(ClientEntry client, double decibels) { - if (playback != null) playback.setClientVolume(client.id, Math.pow(10.0, decibels / 20.0)); + VoiceOutput out = playback; + if (out != null) out.setClientVolume(client.id, Math.pow(10.0, decibels / 20.0)); Contact contact = contacts.find(client.uniqueId); if (contact == null) return; contact.hasVolumeModifier = decibels != 0; @@ -1000,8 +1015,9 @@ public final class TeamspeakConnection implements TS3Listener { } public double getClientVolumeModifier(ClientEntry client) { - if (playback == null) return 0; - double gain = playback.getClientVolume(client.id); + VoiceOutput out = playback; + if (out == null) return 0; + double gain = out.getClientVolume(client.id); return gain <= 0 ? -60 : 20.0 * Math.log10(gain); }