thread constraint
This commit is contained in:
@@ -9,7 +9,7 @@ import java.util.concurrent.ConcurrentMap;
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The event emitter which is ported from the JavaScript module.
|
* The event emitter which is ported from the JavaScript module. This class is thread-safe.
|
||||||
*
|
*
|
||||||
* @see <a href="https://github.com/component/emitter">https://github.com/component/emitter</a>
|
* @see <a href="https://github.com/component/emitter">https://github.com/component/emitter</a>
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package com.github.nkzawa.engineio.client;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ThreadFactory;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The main thread for Engine.IO Client.
|
||||||
|
*/
|
||||||
|
class EventThread extends Thread {
|
||||||
|
|
||||||
|
private static final ExecutorService service = Executors.newSingleThreadExecutor(new ThreadFactory() {
|
||||||
|
@Override
|
||||||
|
public Thread newThread(Runnable runnable) {
|
||||||
|
thread = new EventThread(runnable);
|
||||||
|
return thread;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
private static volatile EventThread thread;
|
||||||
|
|
||||||
|
|
||||||
|
private EventThread(Runnable runnable) {
|
||||||
|
super(runnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* check if the current thread is EventThread.
|
||||||
|
*
|
||||||
|
* @return true if the current thread is EventThread.
|
||||||
|
*/
|
||||||
|
public static boolean isCurrent() {
|
||||||
|
return currentThread() == thread;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes a task in EventThread.
|
||||||
|
*
|
||||||
|
* @param task
|
||||||
|
*/
|
||||||
|
public static void exec(Runnable task) {
|
||||||
|
if (isCurrent()) {
|
||||||
|
task.run();
|
||||||
|
} else {
|
||||||
|
service.execute(task);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes a task on the next loop in EventThread.
|
||||||
|
*
|
||||||
|
* @param task
|
||||||
|
*/
|
||||||
|
public static void nextTick(Runnable task) {
|
||||||
|
service.execute(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -108,8 +108,8 @@ public abstract class Socket extends Emitter {
|
|||||||
private List<String> transports;
|
private List<String> transports;
|
||||||
private List<String> upgrades;
|
private List<String> upgrades;
|
||||||
private Map<String, String> query;
|
private Map<String, String> query;
|
||||||
private ConcurrentLinkedQueue<Packet> writeBuffer = new ConcurrentLinkedQueue<Packet>();
|
private Queue<Packet> writeBuffer = new LinkedList<Packet>();
|
||||||
private ConcurrentLinkedQueue<Runnable> callbackBuffer = new ConcurrentLinkedQueue<Runnable>();
|
private Queue<Runnable> callbackBuffer = new LinkedList<Runnable>();
|
||||||
private Transport transport;
|
private Transport transport;
|
||||||
private Future pingTimeoutTimer;
|
private Future pingTimeoutTimer;
|
||||||
private Future pingIntervalTimer;
|
private Future pingIntervalTimer;
|
||||||
@@ -176,11 +176,16 @@ public abstract class Socket extends Emitter {
|
|||||||
* Connects the client.
|
* Connects the client.
|
||||||
*/
|
*/
|
||||||
public void open() {
|
public void open() {
|
||||||
this.readyState = OPENING;
|
EventThread.exec(new Runnable() {
|
||||||
Transport transport = this.createTransport(this.transports.get(0));
|
@Override
|
||||||
this.setTransport(transport);
|
public void run() {
|
||||||
|
Socket.this.readyState = OPENING;
|
||||||
|
Transport transport = Socket.this.createTransport(Socket.this.transports.get(0));
|
||||||
|
Socket.this.setTransport(transport);
|
||||||
transport.open();
|
transport.open();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private Transport createTransport(String name) {
|
private Transport createTransport(String name) {
|
||||||
logger.fine(String.format("creating transport '%s'", name));
|
logger.fine(String.format("creating transport '%s'", name));
|
||||||
@@ -409,7 +414,7 @@ public abstract class Socket extends Emitter {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private synchronized void onHeartbeat(long timeout) {
|
private void onHeartbeat(long timeout) {
|
||||||
if (this.pingTimeoutTimer != null) {
|
if (this.pingTimeoutTimer != null) {
|
||||||
pingTimeoutTimer.cancel(true);
|
pingTimeoutTimer.cancel(true);
|
||||||
}
|
}
|
||||||
@@ -420,27 +425,37 @@ public abstract class Socket extends Emitter {
|
|||||||
|
|
||||||
final Socket self = this;
|
final Socket self = this;
|
||||||
this.pingTimeoutTimer = this.heartbeatScheduler.schedule(new Runnable() {
|
this.pingTimeoutTimer = this.heartbeatScheduler.schedule(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
EventThread.exec(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (self.readyState == CLOSED) return;
|
if (self.readyState == CLOSED) return;
|
||||||
self.onClose("ping timeout");
|
self.onClose("ping timeout");
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}, timeout, TimeUnit.MILLISECONDS);
|
}, timeout, TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void ping() {
|
private void ping() {
|
||||||
if (this.pingIntervalTimer != null) {
|
if (this.pingIntervalTimer != null) {
|
||||||
pingIntervalTimer.cancel(true);
|
pingIntervalTimer.cancel(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
final Socket self = this;
|
final Socket self = this;
|
||||||
this.pingIntervalTimer = this.heartbeatScheduler.schedule(new Runnable() {
|
this.pingIntervalTimer = this.heartbeatScheduler.schedule(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
EventThread.exec(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
logger.fine(String.format("writing ping packet - expecting pong within %sms", self.pingTimeout));
|
logger.fine(String.format("writing ping packet - expecting pong within %sms", self.pingTimeout));
|
||||||
self.sendPacket(Packet.PING);
|
self.sendPacket(Packet.PING);
|
||||||
self.onHeartbeat(self.pingTimeout);
|
self.onHeartbeat(self.pingTimeout);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}, this.pingInterval, TimeUnit.MILLISECONDS);
|
}, this.pingInterval, TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -502,8 +517,13 @@ public abstract class Socket extends Emitter {
|
|||||||
* @param msg
|
* @param msg
|
||||||
* @param fn callback to be called on drain
|
* @param fn callback to be called on drain
|
||||||
*/
|
*/
|
||||||
public void send(String msg, Runnable fn) {
|
public void send(final String msg, final Runnable fn) {
|
||||||
this.sendPacket(Packet.MESSAGE, msg, fn);
|
EventThread.exec(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Socket.this.sendPacket(Packet.MESSAGE, msg, fn);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendPacket(String type) {
|
private void sendPacket(String type) {
|
||||||
@@ -529,13 +549,18 @@ public abstract class Socket extends Emitter {
|
|||||||
* @return a reference to to this object.
|
* @return a reference to to this object.
|
||||||
*/
|
*/
|
||||||
public Socket close() {
|
public Socket close() {
|
||||||
if (this.readyState == OPENING || this.readyState == OPEN) {
|
EventThread.exec(new Runnable() {
|
||||||
this.onClose("forced close");
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (Socket.this.readyState == OPENING || Socket.this.readyState == OPEN) {
|
||||||
|
Socket.this.onClose("forced close");
|
||||||
logger.fine("socket closing - telling transport to close");
|
logger.fine("socket closing - telling transport to close");
|
||||||
this.transport.close();
|
Socket.this.transport.close();
|
||||||
this.transport.off();
|
Socket.this.transport.off();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -558,18 +583,16 @@ public abstract class Socket extends Emitter {
|
|||||||
if (this.pingTimeoutTimer != null) {
|
if (this.pingTimeoutTimer != null) {
|
||||||
this.pingTimeoutTimer.cancel(true);
|
this.pingTimeoutTimer.cancel(true);
|
||||||
}
|
}
|
||||||
|
EventThread.nextTick(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Socket.this.writeBuffer.clear();
|
||||||
|
Socket.this.callbackBuffer.clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
this.readyState = CLOSED;
|
this.readyState = CLOSED;
|
||||||
this.emit(EVENT_CLOSE, reason, desc);
|
this.emit(EVENT_CLOSE, reason, desc);
|
||||||
this.onclose();
|
this.onclose();
|
||||||
// TODO:
|
|
||||||
// clean buffer in next tick, so developers can still
|
|
||||||
// grab the buffers on `close` event
|
|
||||||
// setTimeout(function() {}
|
|
||||||
// self.writeBuffer = [];
|
|
||||||
// self.callbackBuffer = [];
|
|
||||||
// );
|
|
||||||
this.writeBuffer.clear();
|
|
||||||
this.callbackBuffer.clear();
|
|
||||||
this.id = null;
|
this.id = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,28 +58,43 @@ public abstract class Transport extends Emitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Transport open() {
|
public Transport open() {
|
||||||
if (this.readyState == CLOSED || this.readyState < 0) {
|
exec(new Runnable() {
|
||||||
this.readyState = OPENING;
|
@Override
|
||||||
this.doOpen();
|
public void run() {
|
||||||
|
if (Transport.this.readyState == CLOSED || Transport.this.readyState < 0) {
|
||||||
|
Transport.this.readyState = OPENING;
|
||||||
|
Transport.this.doOpen();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Transport close() {
|
public Transport close() {
|
||||||
if (this.readyState == OPENING || this.readyState == OPEN) {
|
exec(new Runnable() {
|
||||||
this.doClose();
|
@Override
|
||||||
this.onClose();
|
public void run() {
|
||||||
|
if (Transport.this.readyState == OPENING || Transport.this.readyState == OPEN) {
|
||||||
|
Transport.this.doClose();
|
||||||
|
Transport.this.onClose();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void send(Packet[] packets) {
|
public void send(final Packet[] packets) {
|
||||||
if (this.readyState == OPEN) {
|
exec(new Runnable() {
|
||||||
this.write(packets);
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (Transport.this.readyState == OPEN) {
|
||||||
|
Transport.this.write(packets);
|
||||||
} else {
|
} else {
|
||||||
throw new RuntimeException("Transport not open");
|
throw new RuntimeException("Transport not open");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
protected void onOpen() {
|
protected void onOpen() {
|
||||||
this.readyState = OPEN;
|
this.readyState = OPEN;
|
||||||
@@ -106,6 +121,14 @@ public abstract class Transport extends Emitter {
|
|||||||
|
|
||||||
abstract protected void doClose();
|
abstract protected void doClose();
|
||||||
|
|
||||||
|
protected static void exec(Runnable task) {
|
||||||
|
EventThread.exec(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void nextTick(Runnable task) {
|
||||||
|
EventThread.nextTick(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static class Options {
|
public static class Options {
|
||||||
|
|
||||||
|
|||||||
@@ -33,10 +33,12 @@ abstract public class Polling extends Transport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void pause(final Runnable onPause) {
|
public void pause(final Runnable onPause) {
|
||||||
int pending = 0;
|
exec(new Runnable() {
|
||||||
final Polling self = this;
|
@Override
|
||||||
|
public void run() {
|
||||||
|
final Polling self = Polling.this;
|
||||||
|
|
||||||
this.readyState = PAUSED;
|
Polling.this.readyState = PAUSED;
|
||||||
|
|
||||||
final Runnable pause = new Runnable() {
|
final Runnable pause = new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
@@ -47,13 +49,13 @@ abstract public class Polling extends Transport {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.polling || !this.writable) {
|
if (Polling.this.polling || !Polling.this.writable) {
|
||||||
final int[] total = new int[] {0};
|
final int[] total = new int[] {0};
|
||||||
|
|
||||||
if (this.polling) {
|
if (Polling.this.polling) {
|
||||||
logger.fine("we are currently polling - waiting to pause");
|
logger.fine("we are currently polling - waiting to pause");
|
||||||
total[0]++;
|
total[0]++;
|
||||||
this.once(EVENT_POLL_COMPLETE, new Listener() {
|
Polling.this.once(EVENT_POLL_COMPLETE, new Listener() {
|
||||||
@Override
|
@Override
|
||||||
public void call(Object... args) {
|
public void call(Object... args) {
|
||||||
logger.fine("pre-pause polling complete");
|
logger.fine("pre-pause polling complete");
|
||||||
@@ -64,10 +66,10 @@ abstract public class Polling extends Transport {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.writable) {
|
if (!Polling.this.writable) {
|
||||||
logger.fine("we are currently writing - waiting to pause");
|
logger.fine("we are currently writing - waiting to pause");
|
||||||
total[0]++;
|
total[0]++;
|
||||||
this.once(EVENT_DRAIN, new Listener() {
|
Polling.this.once(EVENT_DRAIN, new Listener() {
|
||||||
@Override
|
@Override
|
||||||
public void call(Object... args) {
|
public void call(Object... args) {
|
||||||
logger.fine("pre-pause writing complete");
|
logger.fine("pre-pause writing complete");
|
||||||
@@ -81,6 +83,8 @@ abstract public class Polling extends Transport {
|
|||||||
pause.run();
|
pause.run();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void poll() {
|
private void poll() {
|
||||||
logger.fine("polling");
|
logger.fine("polling");
|
||||||
|
|||||||
@@ -42,16 +42,26 @@ public class PollingXHR extends Polling {
|
|||||||
req.on(Request.EVENT_SUCCESS, new Listener() {
|
req.on(Request.EVENT_SUCCESS, new Listener() {
|
||||||
@Override
|
@Override
|
||||||
public void call(Object... args) {
|
public void call(Object... args) {
|
||||||
|
exec(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
fn.run();
|
fn.run();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
req.on(Request.EVENT_ERROR, new Listener() {
|
req.on(Request.EVENT_ERROR, new Listener() {
|
||||||
@Override
|
@Override
|
||||||
public void call(Object... args) {
|
public void call(final Object... args) {
|
||||||
|
exec(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
Exception err = args.length > 0 && args[0] instanceof Exception ? (Exception)args[0] : null;
|
Exception err = args.length > 0 && args[0] instanceof Exception ? (Exception)args[0] : null;
|
||||||
self.onError("xhr post error", err);
|
self.onError("xhr post error", err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
req.create();
|
req.create();
|
||||||
this.sendXhr = req;
|
this.sendXhr = req;
|
||||||
}
|
}
|
||||||
@@ -62,18 +72,28 @@ public class PollingXHR extends Polling {
|
|||||||
final PollingXHR self = this;
|
final PollingXHR self = this;
|
||||||
req.on(Request.EVENT_DATA, new Listener() {
|
req.on(Request.EVENT_DATA, new Listener() {
|
||||||
@Override
|
@Override
|
||||||
public void call(Object... args) {
|
public void call(final Object... args) {
|
||||||
|
exec(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
String data = args.length > 0 ? (String) args[0] : null;
|
String data = args.length > 0 ? (String) args[0] : null;
|
||||||
self.onData(data);
|
self.onData(data);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
req.on(Request.EVENT_ERROR, new Listener() {
|
req.on(Request.EVENT_ERROR, new Listener() {
|
||||||
@Override
|
@Override
|
||||||
public void call(Object... args) {
|
public void call(final Object... args) {
|
||||||
|
exec(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
Exception err = args.length > 0 && args[0] instanceof Exception ? (Exception) args[0] : null;
|
Exception err = args.length > 0 && args[0] instanceof Exception ? (Exception) args[0] : null;
|
||||||
self.onError("xhr poll error", err);
|
self.onError("xhr poll error", err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
req.create();
|
req.create();
|
||||||
this.pollXhr = req;
|
this.pollXhr = req;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,20 +44,40 @@ public class WebSocket extends Transport {
|
|||||||
this.socket = new WebSocketClient(new URI(this.uri()), new Draft_17()) {
|
this.socket = new WebSocketClient(new URI(this.uri()), new Draft_17()) {
|
||||||
@Override
|
@Override
|
||||||
public void onOpen(ServerHandshake serverHandshake) {
|
public void onOpen(ServerHandshake serverHandshake) {
|
||||||
|
exec(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
self.onOpen();
|
self.onOpen();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
public void onClose(int i, String s, boolean b) {
|
public void onClose(int i, String s, boolean b) {
|
||||||
|
exec(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
self.onClose();
|
self.onClose();
|
||||||
}
|
}
|
||||||
@Override
|
});
|
||||||
public void onMessage(String s) {
|
|
||||||
self.onData(s);
|
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void onError(Exception e) {
|
public void onMessage(final String s) {
|
||||||
|
exec(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
self.onData(s);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void onError(final Exception e) {
|
||||||
|
exec(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
self.onError("websocket error", e);
|
self.onError("websocket error", e);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
this.socket.connect();
|
this.socket.connect();
|
||||||
} catch (URISyntaxException e) {
|
} catch (URISyntaxException e) {
|
||||||
@@ -82,6 +102,9 @@ public class WebSocket extends Transport {
|
|||||||
|
|
||||||
if (this.socket.getConnection().hasBufferedData()) {
|
if (this.socket.getConnection().hasBufferedData()) {
|
||||||
this.bufferedAmountId = this.drainScheduler.scheduleAtFixedRate(new Runnable() {
|
this.bufferedAmountId = this.drainScheduler.scheduleAtFixedRate(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
exec(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (!self.socket.getConnection().hasBufferedData()) {
|
if (!self.socket.getConnection().hasBufferedData()) {
|
||||||
@@ -89,9 +112,11 @@ public class WebSocket extends Transport {
|
|||||||
ondrain.run();
|
ondrain.run();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}, 50, 50, TimeUnit.MILLISECONDS);
|
}, 50, 50, TimeUnit.MILLISECONDS);
|
||||||
} else {
|
} else {
|
||||||
this.drainScheduler.schedule(ondrain, 0, TimeUnit.MILLISECONDS);
|
nextTick(ondrain);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,8 +80,8 @@ public class ServerConnectionTest {
|
|||||||
public void stopServer() throws InterruptedException {
|
public void stopServer() throws InterruptedException {
|
||||||
System.out.println("Stopping server ...");
|
System.out.println("Stopping server ...");
|
||||||
serverProcess.destroy();
|
serverProcess.destroy();
|
||||||
serverOutout.cancel(false);
|
serverOutout.cancel(true);
|
||||||
serverError.cancel(false);
|
serverError.cancel(true);
|
||||||
serverService.shutdown();
|
serverService.shutdown();
|
||||||
serverService.awaitTermination(3000, TimeUnit.MILLISECONDS);
|
serverService.awaitTermination(3000, TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user