thread constraint

This commit is contained in:
Naoyuki Kanezawa
2013-05-06 20:48:38 +09:00
parent a5898573c6
commit 06c1da57b4
8 changed files with 261 additions and 106 deletions

View File

@@ -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>
*/

View File

@@ -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);
}
}

View File

@@ -108,8 +108,8 @@ public abstract class Socket extends Emitter {
private List<String> transports;
private List<String> upgrades;
private Map<String, String> query;
private ConcurrentLinkedQueue<Packet> writeBuffer = new ConcurrentLinkedQueue<Packet>();
private ConcurrentLinkedQueue<Runnable> callbackBuffer = new ConcurrentLinkedQueue<Runnable>();
private Queue<Packet> writeBuffer = new LinkedList<Packet>();
private Queue<Runnable> callbackBuffer = new LinkedList<Runnable>();
private Transport transport;
private Future pingTimeoutTimer;
private Future pingIntervalTimer;
@@ -176,11 +176,16 @@ public abstract class Socket extends Emitter {
* Connects the client.
*/
public void open() {
this.readyState = OPENING;
Transport transport = this.createTransport(this.transports.get(0));
this.setTransport(transport);
EventThread.exec(new Runnable() {
@Override
public void run() {
Socket.this.readyState = OPENING;
Transport transport = Socket.this.createTransport(Socket.this.transports.get(0));
Socket.this.setTransport(transport);
transport.open();
}
});
}
private Transport createTransport(String 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) {
pingTimeoutTimer.cancel(true);
}
@@ -420,27 +425,37 @@ public abstract class Socket extends Emitter {
final Socket self = this;
this.pingTimeoutTimer = this.heartbeatScheduler.schedule(new Runnable() {
@Override
public void run() {
EventThread.exec(new Runnable() {
@Override
public void run() {
if (self.readyState == CLOSED) return;
self.onClose("ping timeout");
}
});
}
}, timeout, TimeUnit.MILLISECONDS);
}
private synchronized void ping() {
private void ping() {
if (this.pingIntervalTimer != null) {
pingIntervalTimer.cancel(true);
}
final Socket self = this;
this.pingIntervalTimer = this.heartbeatScheduler.schedule(new Runnable() {
@Override
public void run() {
EventThread.exec(new Runnable() {
@Override
public void run() {
logger.fine(String.format("writing ping packet - expecting pong within %sms", self.pingTimeout));
self.sendPacket(Packet.PING);
self.onHeartbeat(self.pingTimeout);
}
});
}
}, this.pingInterval, TimeUnit.MILLISECONDS);
}
@@ -502,8 +517,13 @@ public abstract class Socket extends Emitter {
* @param msg
* @param fn callback to be called on drain
*/
public void send(String msg, Runnable fn) {
this.sendPacket(Packet.MESSAGE, msg, fn);
public void send(final String msg, final Runnable fn) {
EventThread.exec(new Runnable() {
@Override
public void run() {
Socket.this.sendPacket(Packet.MESSAGE, msg, fn);
}
});
}
private void sendPacket(String type) {
@@ -529,13 +549,18 @@ public abstract class Socket extends Emitter {
* @return a reference to to this object.
*/
public Socket close() {
if (this.readyState == OPENING || this.readyState == OPEN) {
this.onClose("forced close");
EventThread.exec(new Runnable() {
@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");
this.transport.close();
this.transport.off();
Socket.this.transport.close();
Socket.this.transport.off();
}
}
});
return this;
}
@@ -558,18 +583,16 @@ public abstract class Socket extends Emitter {
if (this.pingTimeoutTimer != null) {
this.pingTimeoutTimer.cancel(true);
}
EventThread.nextTick(new Runnable() {
@Override
public void run() {
Socket.this.writeBuffer.clear();
Socket.this.callbackBuffer.clear();
}
});
this.readyState = CLOSED;
this.emit(EVENT_CLOSE, reason, desc);
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;
}
}

View File

@@ -58,28 +58,43 @@ public abstract class Transport extends Emitter {
}
public Transport open() {
if (this.readyState == CLOSED || this.readyState < 0) {
this.readyState = OPENING;
this.doOpen();
exec(new Runnable() {
@Override
public void run() {
if (Transport.this.readyState == CLOSED || Transport.this.readyState < 0) {
Transport.this.readyState = OPENING;
Transport.this.doOpen();
}
}
});
return this;
}
public Transport close() {
if (this.readyState == OPENING || this.readyState == OPEN) {
this.doClose();
this.onClose();
exec(new Runnable() {
@Override
public void run() {
if (Transport.this.readyState == OPENING || Transport.this.readyState == OPEN) {
Transport.this.doClose();
Transport.this.onClose();
}
}
});
return this;
}
public void send(Packet[] packets) {
if (this.readyState == OPEN) {
this.write(packets);
public void send(final Packet[] packets) {
exec(new Runnable() {
@Override
public void run() {
if (Transport.this.readyState == OPEN) {
Transport.this.write(packets);
} else {
throw new RuntimeException("Transport not open");
}
}
});
}
protected void onOpen() {
this.readyState = OPEN;
@@ -106,6 +121,14 @@ public abstract class Transport extends Emitter {
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 {

View File

@@ -33,10 +33,12 @@ abstract public class Polling extends Transport {
}
public void pause(final Runnable onPause) {
int pending = 0;
final Polling self = this;
exec(new Runnable() {
@Override
public void run() {
final Polling self = Polling.this;
this.readyState = PAUSED;
Polling.this.readyState = PAUSED;
final Runnable pause = new Runnable() {
@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};
if (this.polling) {
if (Polling.this.polling) {
logger.fine("we are currently polling - waiting to pause");
total[0]++;
this.once(EVENT_POLL_COMPLETE, new Listener() {
Polling.this.once(EVENT_POLL_COMPLETE, new Listener() {
@Override
public void call(Object... args) {
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");
total[0]++;
this.once(EVENT_DRAIN, new Listener() {
Polling.this.once(EVENT_DRAIN, new Listener() {
@Override
public void call(Object... args) {
logger.fine("pre-pause writing complete");
@@ -81,6 +83,8 @@ abstract public class Polling extends Transport {
pause.run();
}
}
});
}
private void poll() {
logger.fine("polling");

View File

@@ -42,16 +42,26 @@ public class PollingXHR extends Polling {
req.on(Request.EVENT_SUCCESS, new Listener() {
@Override
public void call(Object... args) {
exec(new Runnable() {
@Override
public void run() {
fn.run();
}
});
}
});
req.on(Request.EVENT_ERROR, new Listener() {
@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;
self.onError("xhr post error", err);
}
});
}
});
req.create();
this.sendXhr = req;
}
@@ -62,18 +72,28 @@ public class PollingXHR extends Polling {
final PollingXHR self = this;
req.on(Request.EVENT_DATA, new Listener() {
@Override
public void call(Object... args) {
String data = args.length > 0 ? (String)args[0] : null;
public void call(final Object... args) {
exec(new Runnable() {
@Override
public void run() {
String data = args.length > 0 ? (String) args[0] : null;
self.onData(data);
}
});
}
});
req.on(Request.EVENT_ERROR, new Listener() {
@Override
public void call(Object... args) {
Exception err = args.length > 0 && args[0] instanceof Exception ? (Exception)args[0] : null;
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;
self.onError("xhr poll error", err);
}
});
}
});
req.create();
this.pollXhr = req;
}

View File

@@ -44,20 +44,40 @@ public class WebSocket extends Transport {
this.socket = new WebSocketClient(new URI(this.uri()), new Draft_17()) {
@Override
public void onOpen(ServerHandshake serverHandshake) {
exec(new Runnable() {
@Override
public void run() {
self.onOpen();
}
});
}
@Override
public void onClose(int i, String s, boolean b) {
exec(new Runnable() {
@Override
public void run() {
self.onClose();
}
@Override
public void onMessage(String s) {
self.onData(s);
});
}
@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);
}
});
}
};
this.socket.connect();
} catch (URISyntaxException e) {
@@ -82,6 +102,9 @@ public class WebSocket extends Transport {
if (this.socket.getConnection().hasBufferedData()) {
this.bufferedAmountId = this.drainScheduler.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
exec(new Runnable() {
@Override
public void run() {
if (!self.socket.getConnection().hasBufferedData()) {
@@ -89,9 +112,11 @@ public class WebSocket extends Transport {
ondrain.run();
}
}
});
}
}, 50, 50, TimeUnit.MILLISECONDS);
} else {
this.drainScheduler.schedule(ondrain, 0, TimeUnit.MILLISECONDS);
nextTick(ondrain);
}
}

View File

@@ -80,8 +80,8 @@ public class ServerConnectionTest {
public void stopServer() throws InterruptedException {
System.out.println("Stopping server ...");
serverProcess.destroy();
serverOutout.cancel(false);
serverError.cancel(false);
serverOutout.cancel(true);
serverError.cancel(true);
serverService.shutdown();
serverService.awaitTermination(3000, TimeUnit.MILLISECONDS);
}