- There was a bug that could cause TS3J to stop firing certain events to all event listeners if the command execution codes were generated quickly, sequentially by those event listener threads. This is now fixed.

- There is now more detailed output for command futures when reconnecting: "client reconnecting" instead of "client disconnected"
 - The MusicPlayer example now returns exit code -1 when it is kicked/disconnected prematurely.
This commit is contained in:
Manevolent
2019-01-22 21:07:51 -05:00
parent 0fed4c68ce
commit 7c33cfa97a
3 changed files with 82 additions and 32 deletions
@@ -4,6 +4,9 @@ import com.github.manevolent.ffmpeg4j.*;
import com.github.manevolent.ffmpeg4j.filter.audio.FFmpegAudioResampleFilter; import com.github.manevolent.ffmpeg4j.filter.audio.FFmpegAudioResampleFilter;
import com.github.manevolent.ffmpeg4j.source.FFmpegAudioSourceSubstream; import com.github.manevolent.ffmpeg4j.source.FFmpegAudioSourceSubstream;
import com.github.manevolent.ffmpeg4j.stream.source.FFmpegSourceStream; import com.github.manevolent.ffmpeg4j.stream.source.FFmpegSourceStream;
import com.github.manevolent.ts3j.event.ClientUpdatedEvent;
import com.github.manevolent.ts3j.event.DisconnectedEvent;
import com.github.manevolent.ts3j.event.TS3Listener;
import com.github.manevolent.ts3j.identity.LocalIdentity; import com.github.manevolent.ts3j.identity.LocalIdentity;
import com.github.manevolent.ts3j.protocol.socket.client.LocalTeamspeakClientSocket; import com.github.manevolent.ts3j.protocol.socket.client.LocalTeamspeakClientSocket;
import com.github.manevolent.ts3j.util.Ts3Debugging; import com.github.manevolent.ts3j.util.Ts3Debugging;
@@ -51,6 +54,13 @@ public class MusicPlayer {
client.setNickname(MusicPlayer.class.getSimpleName()); client.setNickname(MusicPlayer.class.getSimpleName());
client.setHWID("TestTestTest"); client.setHWID("TestTestTest");
client.addListener(new TS3Listener() {
@Override
public void onDisconnected(DisconnectedEvent e) {
System.exit(-1);
}
});
// Create a sink // Create a sink
TeamspeakFastMixerSink sink = new TeamspeakFastMixerSink( TeamspeakFastMixerSink sink = new TeamspeakFastMixerSink(
AUDIO_FORMAT, AUDIO_FORMAT,
@@ -149,10 +149,12 @@ public abstract class AbstractTeamspeakClientSocket
protected void start() { protected void start() {
Thread networkThread = new Thread(networkReader); Thread networkThread = new Thread(networkReader);
networkThread.setName("TS3J/NetworkReader/" + networkThread.getId());
networkThread.setDaemon(true); networkThread.setDaemon(true);
networkThread.start(); networkThread.start();
Thread handlerThread = new Thread(networkHandler); Thread handlerThread = new Thread(networkHandler);
handlerThread.setName("TS3J/NetworkHandler/" + handlerThread.getId());
handlerThread.setDaemon(true); handlerThread.setDaemon(true);
handlerThread.start(); handlerThread.start();
} }
@@ -922,6 +924,7 @@ public abstract class AbstractTeamspeakClientSocket
private class NetworkHandler implements Runnable { private class NetworkHandler implements Runnable {
@Override @Override
public void run() { public void run() {
try {
while (isReading()) { while (isReading()) {
try { try {
Packet packet = readQueue.take(); Packet packet = readQueue.take();
@@ -930,12 +933,13 @@ public abstract class AbstractTeamspeakClientSocket
// on us // on us
getHandler().handlePacket(packet); getHandler().handlePacket(packet);
} catch (Throwable e) { } catch (InterruptedException e) {
if (e instanceof InterruptedException) continue; break;
getExceptionHandler().accept(e);
} }
} }
} catch (Throwable e) {
getExceptionHandler().accept(e);
}
} }
} }
@@ -55,10 +55,15 @@ public class LocalTeamspeakClientSocket
private List<TS3Listener> listeners = new LinkedList<>(); private List<TS3Listener> listeners = new LinkedList<>();
private int acceptingReturnCode = Integer.MAX_VALUE;
private Integer serverId = null; private Integer serverId = null;
/**
* The internal command buffer
*
* This buffer ascends through specific command codes, which are calculated using:
*
* get
*/
private Map<Integer, ClientCommandResponse> awaitingCommands = new ConcurrentHashMap<>(); private Map<Integer, ClientCommandResponse> awaitingCommands = new ConcurrentHashMap<>();
public LocalTeamspeakClientSocket() { public LocalTeamspeakClientSocket() {
@@ -292,8 +297,6 @@ public class LocalTeamspeakClientSocket
} }
awaitingCommands.clear(); awaitingCommands.clear();
calculateAcceptingReturnCode();
} }
serverId = null; serverId = null;
@@ -372,10 +375,10 @@ public class LocalTeamspeakClientSocket
synchronized (commandSendLock) { synchronized (commandSendLock) {
for (Integer id : new ArrayList<>(awaitingCommands.keySet())) { for (Integer id : new ArrayList<>(awaitingCommands.keySet())) {
ClientCommandResponse response = awaitingCommands.get(id); ClientCommandResponse response = awaitingCommands.get(id);
response.completeFailure(new IOException("client disconnected")); response.completeFailure(new IOException("client reconnecting"));
} }
awaitingCommands.clear(); awaitingCommands.clear();
calculateAcceptingReturnCode();
} }
setOption("client.hostname", remote.getHostString()); setOption("client.hostname", remote.getHostString());
@@ -436,7 +439,7 @@ public class LocalTeamspeakClientSocket
CommandProcessor awaitingCommandProcessor; CommandProcessor awaitingCommandProcessor;
synchronized (commandSendLock) { synchronized (commandSendLock) {
awaitingCommandProcessor = awaitingCommands.get(acceptingReturnCode); awaitingCommandProcessor = awaitingCommands.get(getAwaitingCommandCode());
} }
if (awaitingCommandProcessor != null) if (awaitingCommandProcessor != null)
@@ -468,14 +471,14 @@ public class LocalTeamspeakClientSocket
else if (getState() == ClientConnectionState.CONNECTING) else if (getState() == ClientConnectionState.CONNECTING)
throw new IOException("connecting"); throw new IOException("connecting");
int maxReturnCode = calculateAcceptingReturnCode(); // Calculate the next command send code we should use
if (maxReturnCode == Integer.MAX_VALUE) maxReturnCode = 0; // This is so we can sequentially send commands and expect responses in order from the server
else maxReturnCode = maxReturnCode + 1; int sendCode = getNextCommandCode();
int returnCode = maxReturnCode; if (sendCode >= Integer.MAX_VALUE) throw new ArrayIndexOutOfBoundsException("sendCode");
command.add(new CommandSingleParameter("return_code", Integer.toString(returnCode))); command.add(new CommandSingleParameter("return_code", Integer.toString(sendCode)));
awaitingCommands.put(returnCode, response = new ClientCommandResponse<>(returnCode, multiCommands -> { awaitingCommands.put(sendCode, response = new ClientCommandResponse<>(sendCode, multiCommands -> {
List<SingleCommand> commands = new LinkedList<>(); List<SingleCommand> commands = new LinkedList<>();
for (MultiCommand multiCommand : multiCommands) commands.addAll(multiCommand.simplify()); for (MultiCommand multiCommand : multiCommands) commands.addAll(multiCommand.simplify());
@@ -484,10 +487,14 @@ public class LocalTeamspeakClientSocket
.collect(Collectors.toCollection(LinkedList::new)); .collect(Collectors.toCollection(LinkedList::new));
}, command)); }, command));
acceptingReturnCode = calculateAcceptingReturnCode(); // Ensure that the next command we expect to receive a response for is sent to the network
// but, only send that command if the accepting receive code matches the code we picked above
if (acceptingReturnCode == returnCode) // this ensures we don't double-send a command
sendNextCommand(); int nextCommandCode = getAwaitingCommandCode();
if (nextCommandCode < 0) // This should never happen
throw new IllegalStateException("nextCommandCode <= 0: " + nextCommandCode);
else if (nextCommandCode == sendCode)
sendCommand(nextCommandCode);
} }
return response; return response;
@@ -508,17 +515,42 @@ public class LocalTeamspeakClientSocket
} }
} }
private int calculateAcceptingReturnCode() { /**
* Gets the next command return_code we will create and buffer for sending.
*
* @return Next command send code
*/
private int getNextCommandCode() {
int highest = 0;
for (Integer integer : awaitingCommands.keySet()) highest = Math.max(highest, integer);
return highest + 1;
}
/**
* Gets the next return_code we expect to receive from the server.
*
* @return command receive code
*/
private int getAwaitingCommandCode() {
if (awaitingCommands.size() <= 0) return -1;
int lowest = Integer.MAX_VALUE; int lowest = Integer.MAX_VALUE;
for (Integer integer : awaitingCommands.keySet()) lowest = Math.min(lowest, integer); for (Integer integer : awaitingCommands.keySet()) lowest = Math.min(lowest, integer);
return lowest; return lowest;
} }
private void sendCommand(int code)
throws IOException, TimeoutException {
synchronized (commandSendLock) {
ClientCommandResponse currentResponse = awaitingCommands.get(code);
if (currentResponse != null) currentResponse.ensureSent();
}
}
private void sendNextCommand() private void sendNextCommand()
throws IOException, TimeoutException { throws IOException, TimeoutException {
synchronized (commandSendLock) { synchronized (commandSendLock) {
ClientCommandResponse currentResponse = awaitingCommands.get(acceptingReturnCode); ClientCommandResponse currentResponse = awaitingCommands.get(getAwaitingCommandCode());
if (currentResponse != null) currentResponse.ensureSent(); if (currentResponse != null) currentResponse.ensureSent();
} }
} }
@@ -649,9 +681,13 @@ public class LocalTeamspeakClientSocket
} catch (Throwable e) { } catch (Throwable e) {
completeFailure(e); completeFailure(e);
} finally { } finally {
synchronized (commandSendLock) { synchronized (commandSendLock) { // (this is likely already locked)
// Remove the current "awaiting" command. This is removing this instance, effectively.
awaitingCommands.remove(getReturnCode()); awaitingCommands.remove(getReturnCode());
acceptingReturnCode = calculateAcceptingReturnCode();
// Ensure we send the next command
// This is essential to keep the command chain flowing when there are commands buffered in the
// command send (awaitingCommands) queue
sendNextCommand(); sendNextCommand();
} }
} }