Make the manual asynchronous worker closable.
Added a good deal of synchronization to deal with closing a specific worker. This overhead can be avoided by simply closing any given worker.
Dieser Commit ist enthalten in:
Ursprung
8a5e5e849b
Commit
e729583d74
@ -1,6 +1,9 @@
|
|||||||
package com.comphenix.protocol.async;
|
package com.comphenix.protocol.async;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.ArrayBlockingQueue;
|
import java.util.concurrent.ArrayBlockingQueue;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
@ -18,18 +21,26 @@ import com.comphenix.protocol.events.PacketListener;
|
|||||||
public class AsyncListenerHandler {
|
public class AsyncListenerHandler {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signal an end to the packet processing.
|
* Signal an end to packet processing.
|
||||||
*/
|
*/
|
||||||
private static final PacketEvent INTERUPT_PACKET = new PacketEvent(new Object());
|
private static final PacketEvent INTERUPT_PACKET = new PacketEvent(new Object());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the threads have to wake up for something important.
|
||||||
|
*/
|
||||||
|
private static final PacketEvent WAKEUP_PACKET = new PacketEvent(new Object());
|
||||||
|
|
||||||
// Default queue capacity
|
// Default queue capacity
|
||||||
private static int DEFAULT_CAPACITY = 1024;
|
private static int DEFAULT_CAPACITY = 1024;
|
||||||
|
|
||||||
// Cancel the async handler
|
// Cancel the async handler
|
||||||
private volatile boolean cancelled;
|
private volatile boolean cancelled;
|
||||||
|
|
||||||
// If we've started the listener loop before
|
// Number of worker threads
|
||||||
private AtomicInteger started = new AtomicInteger();
|
private final AtomicInteger started = new AtomicInteger();
|
||||||
|
|
||||||
|
// Unique local worker ID
|
||||||
|
private final AtomicInteger nextID = new AtomicInteger();
|
||||||
|
|
||||||
// The packet listener
|
// The packet listener
|
||||||
private PacketListener listener;
|
private PacketListener listener;
|
||||||
@ -41,6 +52,10 @@ public class AsyncListenerHandler {
|
|||||||
// List of queued packets
|
// List of queued packets
|
||||||
private ArrayBlockingQueue<PacketEvent> queuedPackets = new ArrayBlockingQueue<PacketEvent>(DEFAULT_CAPACITY);
|
private ArrayBlockingQueue<PacketEvent> queuedPackets = new ArrayBlockingQueue<PacketEvent>(DEFAULT_CAPACITY);
|
||||||
|
|
||||||
|
// List of cancelled tasks
|
||||||
|
private final Set<Integer> stoppedTasks = new HashSet<Integer>();
|
||||||
|
private final Object stopLock = new Object();
|
||||||
|
|
||||||
// Minecraft main thread
|
// Minecraft main thread
|
||||||
private Thread mainThread;
|
private Thread mainThread;
|
||||||
|
|
||||||
@ -112,15 +127,57 @@ public class AsyncListenerHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a runnable that will initiate the listener loop.
|
* Create a worker that will initiate the listener loop. Note that using stop() to
|
||||||
|
* close a specific worker is less efficient than stopping an arbitrary worker.
|
||||||
* <p>
|
* <p>
|
||||||
* <b>Warning</b>: Never call the run() method in the main thread.
|
* <b>Warning</b>: Never call the run() method in the main thread.
|
||||||
*/
|
*/
|
||||||
public Runnable getListenerLoop() {
|
public Runnable getListenerLoop() {
|
||||||
return new Runnable() {
|
return new AsyncRunnable() {
|
||||||
|
|
||||||
|
private final AtomicBoolean running = new AtomicBoolean();
|
||||||
|
private volatile int id;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
listenerLoop();
|
// Careful now
|
||||||
|
if (running.compareAndSet(false, true)) {
|
||||||
|
id = nextID.incrementAndGet();
|
||||||
|
listenerLoop(id);
|
||||||
|
|
||||||
|
synchronized (stopLock) {
|
||||||
|
stoppedTasks.remove(id);
|
||||||
|
notifyAll();
|
||||||
|
running.set(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"This listener loop has already been started. Create a new instead.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean stop() throws InterruptedException {
|
||||||
|
synchronized (stopLock) {
|
||||||
|
if (!running.get())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
stoppedTasks.add(id);
|
||||||
|
|
||||||
|
// Wake up threads - we have a listener to stop
|
||||||
|
for (int i = 0; i < getWorkers(); i++) {
|
||||||
|
queuedPackets.offer(WAKEUP_PACKET);
|
||||||
|
}
|
||||||
|
|
||||||
|
waitForStops();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isRunning() {
|
||||||
|
return running.get();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -128,9 +185,11 @@ public class AsyncListenerHandler {
|
|||||||
/**
|
/**
|
||||||
* Start a singler worker thread handling the asynchronous.
|
* Start a singler worker thread handling the asynchronous.
|
||||||
*/
|
*/
|
||||||
public void start() {
|
public synchronized void start() {
|
||||||
if (listener.getPlugin() == null)
|
if (listener.getPlugin() == null)
|
||||||
throw new IllegalArgumentException("Cannot start task without a valid plugin.");
|
throw new IllegalArgumentException("Cannot start task without a valid plugin.");
|
||||||
|
if (cancelled)
|
||||||
|
throw new IllegalStateException("Cannot start a worker when the listener is closing.");
|
||||||
|
|
||||||
filterManager.scheduleAsyncTask(listener.getPlugin(), getListenerLoop());
|
filterManager.scheduleAsyncTask(listener.getPlugin(), getListenerLoop());
|
||||||
}
|
}
|
||||||
@ -139,13 +198,80 @@ public class AsyncListenerHandler {
|
|||||||
* Start multiple worker threads for this listener.
|
* Start multiple worker threads for this listener.
|
||||||
* @param count - number of worker threads to start.
|
* @param count - number of worker threads to start.
|
||||||
*/
|
*/
|
||||||
public void start(int count) {
|
public synchronized void start(int count) {
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++)
|
||||||
start();
|
start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop a worker thread.
|
||||||
|
*/
|
||||||
|
public synchronized void stop() {
|
||||||
|
queuedPackets.add(INTERUPT_PACKET);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop the given amount of worker threads.
|
||||||
|
* @param count - number of threads to stop.
|
||||||
|
*/
|
||||||
|
public synchronized void stop(int count) {
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the current number of workers.
|
||||||
|
* <p>
|
||||||
|
* This method can only be called with a count of zero when the listener is closing.
|
||||||
|
* @param count - new number of workers.
|
||||||
|
*/
|
||||||
|
public synchronized void setWorkers(int count) {
|
||||||
|
if (count < 0)
|
||||||
|
throw new IllegalArgumentException("Number of workers cannot be less than zero.");
|
||||||
|
if (count > DEFAULT_CAPACITY)
|
||||||
|
throw new IllegalArgumentException("Cannot initiate more than " + DEFAULT_CAPACITY + " workers");
|
||||||
|
if (cancelled && count > 0)
|
||||||
|
throw new IllegalArgumentException("Cannot add workers when the listener is closing.");
|
||||||
|
|
||||||
|
long time = System.currentTimeMillis();
|
||||||
|
|
||||||
|
// Try to get to the correct count
|
||||||
|
while (started.get() != count) {
|
||||||
|
if (started.get() < count)
|
||||||
|
start();
|
||||||
|
else
|
||||||
|
stop();
|
||||||
|
|
||||||
|
// May happen if another thread is doing something similar to "setWorkers"
|
||||||
|
if ((System.currentTimeMillis() - time) > 1000)
|
||||||
|
throw new RuntimeException("Failed to set worker count.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the current number of registered workers.
|
||||||
|
* <p>
|
||||||
|
* Note that the returned value may be out of data.
|
||||||
|
* @return Number of registered workers.
|
||||||
|
*/
|
||||||
|
public synchronized int getWorkers() {
|
||||||
|
return started.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait until every tasks scheduled to stop has actually stopped.
|
||||||
|
* @return TRUE if the current listener should stop, FALSE otherwise.
|
||||||
|
* @throws InterruptedException - If the current thread was interrupted.
|
||||||
|
*/
|
||||||
|
private boolean waitForStops() throws InterruptedException {
|
||||||
|
while (stoppedTasks.size() > 0 && !cancelled) {
|
||||||
|
wait();
|
||||||
|
}
|
||||||
|
return cancelled;
|
||||||
|
}
|
||||||
|
|
||||||
// DO NOT call this method from the main thread
|
// DO NOT call this method from the main thread
|
||||||
private void listenerLoop() {
|
private void listenerLoop(int taskID) {
|
||||||
|
|
||||||
// Danger, danger!
|
// Danger, danger!
|
||||||
if (Thread.currentThread().getId() == mainThread.getId())
|
if (Thread.currentThread().getId() == mainThread.getId())
|
||||||
@ -153,18 +279,34 @@ public class AsyncListenerHandler {
|
|||||||
if (cancelled)
|
if (cancelled)
|
||||||
throw new IllegalStateException("Listener has been cancelled. Create a new listener instead.");
|
throw new IllegalStateException("Listener has been cancelled. Create a new listener instead.");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Wait if certain threads are stopping
|
||||||
|
synchronized (stopLock) {
|
||||||
|
if (waitForStops())
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Proceed
|
// Proceed
|
||||||
started.incrementAndGet();
|
started.incrementAndGet();
|
||||||
|
|
||||||
try {
|
|
||||||
mainLoop:
|
mainLoop:
|
||||||
while (!cancelled) {
|
while (!cancelled) {
|
||||||
PacketEvent packet = queuedPackets.take();
|
PacketEvent packet = queuedPackets.take();
|
||||||
AsyncMarker marker = packet.getAsyncMarker();
|
AsyncMarker marker = packet.getAsyncMarker();
|
||||||
|
|
||||||
// Handle cancel requests
|
// Handle cancel requests
|
||||||
if (packet == null || marker == null || !packet.isAsynchronous()) {
|
if (packet == null || marker == null || packet == INTERUPT_PACKET) {
|
||||||
break;
|
return;
|
||||||
|
|
||||||
|
} else if (packet == WAKEUP_PACKET) {
|
||||||
|
// This is a bit slow, but it should be safe
|
||||||
|
synchronized (stopLock) {
|
||||||
|
// Are we the one who is supposed to stop?
|
||||||
|
if (stoppedTasks.contains(taskID))
|
||||||
|
return;
|
||||||
|
if (waitForStops())
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Here's the core of the asynchronous processing
|
// Here's the core of the asynchronous processing
|
||||||
@ -221,8 +363,11 @@ public class AsyncListenerHandler {
|
|||||||
private void stopThreads() {
|
private void stopThreads() {
|
||||||
// Poison Pill Shutdown
|
// Poison Pill Shutdown
|
||||||
queuedPackets.clear();
|
queuedPackets.clear();
|
||||||
|
stop(started.get());
|
||||||
|
|
||||||
for (int i = 0; i < started.get(); i++)
|
// Individual shut down is irrelevant now
|
||||||
queuedPackets.add(INTERUPT_PACKET);
|
synchronized (stopLock) {
|
||||||
|
notifyAll();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
23
ProtocolLib/src/com/comphenix/protocol/async/AsyncRunnable.java
Normale Datei
23
ProtocolLib/src/com/comphenix/protocol/async/AsyncRunnable.java
Normale Datei
@ -0,0 +1,23 @@
|
|||||||
|
package com.comphenix.protocol.async;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A runnable representing a asynchronous event listener.
|
||||||
|
*
|
||||||
|
* @author Kristian
|
||||||
|
*/
|
||||||
|
public interface AsyncRunnable extends Runnable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop the given runnable.
|
||||||
|
* <p>
|
||||||
|
* This may not occur right away.
|
||||||
|
* @return TRUE if the thread was stopped, FALSE if it was already stopped.
|
||||||
|
*/
|
||||||
|
public boolean stop() throws InterruptedException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if we're running or not.
|
||||||
|
* @return TRUE if we're running, FALSE otherwise.
|
||||||
|
*/
|
||||||
|
public boolean isRunning();
|
||||||
|
}
|
In neuem Issue referenzieren
Einen Benutzer sperren