package com.koushikdutta.async;
import android.os.Build;
import android.os.Handler;
import android.util.Log;
import com.koushikdutta.async.callback.CompletedCallback;
import com.koushikdutta.async.callback.ConnectCallback;
import com.koushikdutta.async.callback.ListenCallback;
import com.koushikdutta.async.future.Cancellable;
import com.koushikdutta.async.future.Future;
import com.koushikdutta.async.future.FutureCallback;
import com.koushikdutta.async.future.SimpleCancellable;
import com.koushikdutta.async.future.SimpleFuture;
import com.koushikdutta.async.future.TransformFuture;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.channels.spi.SelectorProvider;
import java.util.Comparator;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class AsyncServer {
public static final String LOGTAG = "NIO";
public static class AsyncSemaphore {
Semaphore semaphore = new Semaphore(0);
public void acquire() throws InterruptedException {
ThreadQueue threadQueue = getOrCreateThreadQueue(Thread.currentThread());
AsyncSemaphore last = threadQueue.waiter;
threadQueue.waiter = this;
Semaphore queueSemaphore = threadQueue.queueSemaphore;
try {
if (semaphore.tryAcquire())
return;
while (true) {
// run the queue
while (true) {
Runnable run = threadQueue.remove();
if (run == null)
break;
// Log.i(LOGTAG, "Pumping for AsyncSemaphore");
run.run();
}
int permits = Math.max(1, queueSemaphore.availablePermits());
queueSemaphore.acquire(permits);
if (semaphore.tryAcquire())
break;
}
}
finally {
threadQueue.waiter = last;
}
}
public boolean tryAcquire(long timeout, TimeUnit timeunit) throws InterruptedException {
long timeoutMs = TimeUnit.MILLISECONDS.convert(timeout, timeunit);
ThreadQueue threadQueue = getOrCreateThreadQueue(Thread.currentThread());
AsyncSemaphore last = threadQueue.waiter;
threadQueue.waiter = this;
Semaphore queueSemaphore = threadQueue.queueSemaphore;
try {
if (semaphore.tryAcquire())
return true;
long start = System.currentTimeMillis();
do {
// run the queue
while (true) {
Runnable run = threadQueue.remove();
if (run == null)
break;
// Log.i(LOGTAG, "Pumping for AsyncSemaphore");
run.run();
}
int permits = Math.max(1, queueSemaphore.availablePermits());
if (!queueSemaphore.tryAcquire(permits, timeoutMs, TimeUnit.MILLISECONDS))
return false;
if (semaphore.tryAcquire())
return true;
}
while (System.currentTimeMillis() - start < timeoutMs);
return false;
}
finally {
threadQueue.waiter = last;
}
}
public void release() {
semaphore.release();
synchronized (mThreadQueues) {
for (ThreadQueue threadQueue: mThreadQueues.values()) {
if (threadQueue.waiter == this)
threadQueue.queueSemaphore.release();
}
}
}
}
static ThreadQueue getOrCreateThreadQueue(Thread thread) {
ThreadQueue queue;
synchronized (mThreadQueues) {
queue = mThreadQueues.get(thread);
if (queue == null) {
queue = new ThreadQueue();
mThreadQueues.put(thread, queue);
}
}
return queue;
}
public static class ThreadQueue extends LinkedList<Runnable> {
AsyncSemaphore waiter;
Semaphore queueSemaphore = new Semaphore(0);
@Override
public boolean add(Runnable object) {
synchronized (this) {
return super.add(object);
}
}
@Override
public boolean remove(Object object) {
synchronized (this) {
return super.remove(object);
}
}
@Override
public Runnable remove() {
synchronized (this) {
if (this.isEmpty())
return null;
return super.remove();
}
}
}
private static class RunnableWrapper implements Runnable {
boolean hasRun;
Runnable runnable;
ThreadQueue threadQueue;
Handler handler;
@Override
public void run() {
synchronized (this) {
if (hasRun)
return;
hasRun = true;
}
try {
runnable.run();
}
finally {
threadQueue.remove(this);
handler.removeCallbacks(this);
threadQueue = null;
handler = null;
runnable = null;
}
}
}
private static WeakHashMap<Thread, ThreadQueue> mThreadQueues = new WeakHashMap<Thread, ThreadQueue>();
public static void post(Handler handler, Runnable runnable) {
RunnableWrapper wrapper = new RunnableWrapper();
ThreadQueue threadQueue = getOrCreateThreadQueue(handler.getLooper().getThread());
wrapper.threadQueue = threadQueue;
wrapper.handler = handler;
wrapper.runnable = runnable;
threadQueue.add(wrapper);
handler.post(wrapper);
// run the queue if the thread is blocking
threadQueue.queueSemaphore.release();
}
static {
try {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.FROYO) {
java.lang.System.setProperty("java.net.preferIPv4Stack", "true");
java.lang.System.setProperty("java.net.preferIPv6Addresses", "false");
}
}
catch (Throwable ex) {
}
}
static AsyncServer mInstance = new AsyncServer() {
{
setAutostart(true);
}
};
public static AsyncServer getDefault() {
return mInstance;
}
private boolean mAutoStart = false;
public void setAutostart(boolean autoStart) {
mAutoStart = autoStart;
}
public boolean getAutoStart() {
return mAutoStart;
}
private Selector mSelector;
public boolean isRunning() {
return mSelector != null;
}
public AsyncServer() {
}
private void handleSocket(final AsyncNetworkSocket handler) throws ClosedChannelException {
final ChannelWrapper sc = handler.getChannel();
SelectionKey ckey = sc.register(mSelector);
ckey.attach(handler);
handler.setup(this, ckey);
}
public void removeAllCallbacks(Object scheduled) {
synchronized (this) {
mQueue.remove(scheduled);
}
}
private static void wakeup(ExecutorService service, final Selector selector) {
if (selector == null)
return;
service.execute(new Runnable() {
@Override
public void run() {
selector.wakeup();
}
});
}
public Object postDelayed(Runnable runnable, long delay) {
Scheduled s;
synchronized (this) {
if (delay != 0)
delay += System.currentTimeMillis();
mQueue.add(s = new Scheduled(runnable, delay));
// start the server up if necessary
if (mSelector == null)
run(false, true);
if (!isAffinityThread()) {
wakeup(getExecutorService(), mSelector);
}
}
return s;
}
public Object post(Runnable runnable) {
return postDelayed(runnable, 0);
}
public Object post(final CompletedCallback callback, final Exception e) {
return post(new Runnable() {
@Override
public void run() {
callback.onCompleted(e);
}
});
}
public void run(final Runnable runnable) {
if (Thread.currentThread() == mAffinity) {
post(runnable);
lockAndRunQueue(this, mQueue);
return;
}
final Semaphore semaphore = new Semaphore(0);
post(new Runnable() {
@Override
public void run() {
runnable.run();
semaphore.release();
}
});
try {
semaphore.acquire();
}
catch (InterruptedException e) {
Log.e(LOGTAG, "run", e);
}
}
private static class Scheduled {
public Scheduled(Runnable runnable, long time) {
this.runnable = runnable;
this.time = time;
}
public Runnable runnable;
public long time;
}
LinkedList<Scheduled> mQueue = new LinkedList<Scheduled>();
public void stop() {
// Log.i(LOGTAG, "****AsyncServer is shutting down.****");
synchronized (this) {
if (mSelector == null)
return;
// replace the current queue with a new queue
// and post a shutdown.
// this is guaranteed to be the last job on the queue.
final Selector currentSelector = mSelector;
post(new Runnable() {
@Override
public void run() {
shutdownEverything(currentSelector);
}
});
synchronized (mServers) {
mServers.remove(mAffinity);
}
mQueue = new LinkedList<Scheduled>();
mSelector = null;
mAffinity = null;
}
}
protected void onDataTransmitted(int transmitted) {
}
public void listen(final InetAddress host, final int port, final ListenCallback handler) {
run(new Runnable() {
@Override
public void run() {
try {
final ServerSocketChannel server = ServerSocketChannel.open();
final ServerSocketChannelWrapper wrapper = new ServerSocketChannelWrapper(server);
InetSocketAddress isa;
if (host == null)
isa = new InetSocketAddress(port);
else
isa = new InetSocketAddress(host, port);
server.socket().bind(isa);
final SelectionKey key = wrapper.register(mSelector);
key.attach(handler);
handler.onListening(new AsyncServerSocket() {
@Override
public int getLocalPort() {
return server.socket().getLocalPort();
}
@Override
public void stop() {
try {
server.close();
}
catch (Exception e) {
}
try {
key.cancel();
}
catch (Exception e) {
}
}
});
}
catch (Exception e) {
handler.onCompleted(e);
}
}
});
}
private class ConnectFuture extends SimpleFuture<AsyncNetworkSocket> {
@Override
protected void cancelCleanup() {
super.cancelCleanup();
try {
if (socket != null)
socket.close();
}
catch (IOException e) {
}
}
SocketChannel socket;
ConnectCallback callback;
}
private ConnectFuture connectResolvedInetSocketAddress(final InetSocketAddress address, final ConnectCallback callback) {
final ConnectFuture cancel = new ConnectFuture();
assert !address.isUnresolved();
post(new Runnable() {
@Override
public void run() {
if (cancel.isCancelled())
return;
cancel.callback = callback;
SelectionKey ckey = null;
SocketChannel socket = null;
try {
socket = cancel.socket = SocketChannel.open();
socket.configureBlocking(false);
ckey = socket.register(mSelector, SelectionKey.OP_CONNECT);
ckey.attach(cancel);
socket.connect(address);
}
catch (Exception e) {
if (ckey != null)
ckey.cancel();
try {
if (socket != null)
socket.close();
}
catch (Exception ignored) {
}
cancel.setComplete(e);
}
}
});
return cancel;
}
public Cancellable connectSocket(final InetSocketAddress remote, final ConnectCallback callback) {
if (!remote.isUnresolved())
return connectResolvedInetSocketAddress(remote, callback);
return new TransformFuture<AsyncSocket, InetAddress>() {
@Override
protected void transform(InetAddress result) throws Exception {
setParent(connectResolvedInetSocketAddress(new InetSocketAddress(remote.getHostName(), remote.getPort()), callback));
}
}
.from(getByName(remote.getHostName()));
}
public Cancellable connectSocket(final String host, final int port, final ConnectCallback callback) {
return connectSocket(InetSocketAddress.createUnresolved(host, port), callback);
}
public ExecutorService getExecutorService() {
return synchronousWorkers;
}
ExecutorService synchronousWorkers = Executors.newFixedThreadPool(4);
public Future<InetAddress[]> getAllByName(final String host) {
final SimpleFuture<InetAddress[]> ret = new SimpleFuture<InetAddress[]>();
synchronousWorkers.execute(new Runnable() {
@Override
public void run() {
try {
final InetAddress[] result = InetAddress.getAllByName(host);
if (result == null || result.length == 0)
throw new Exception("no addresses for host");
post(new Runnable() {
@Override
public void run() {
ret.setComplete(null, result);
}
});
} catch (final Exception e) {
post(new Runnable() {
@Override
public void run() {
ret.setComplete(e, null);
}
});
}
}
});
return ret;
}
public Future<InetAddress> getByName(String host) {
return new TransformFuture<InetAddress, InetAddress[]>() {
@Override
protected void transform(InetAddress[] result) throws Exception {
setComplete(result[0]);
}
}
.from(getAllByName(host));
}
public AsyncDatagramSocket connectDatagram(final String host, final int port) throws IOException {
final DatagramChannel socket = DatagramChannel.open();
final AsyncDatagramSocket handler = new AsyncDatagramSocket();
handler.attach(socket);
// ugh.. this should really be post to make it nonblocking...
// but i want datagrams to be immediately writable.
// they're not really used anyways.
run(new Runnable() {
@Override
public void run() {
try {
final SocketAddress remote = new InetSocketAddress(host, port);
handleSocket(handler);
socket.connect(remote);
}
catch (Exception e) {
Log.e(LOGTAG, "Datagram error", e);
}
}
});
return handler;
}
public AsyncDatagramSocket openDatagram() throws IOException {
final DatagramChannel socket = DatagramChannel.open();
final AsyncDatagramSocket handler = new AsyncDatagramSocket();
handler.attach(socket);
// ugh.. this should really be post to make it nonblocking...
// but i want datagrams to be immediately writable.
// they're not really used anyways.
run(new Runnable() {
@Override
public void run() {
try {
socket.socket().bind(null);
handleSocket(handler);
}
catch (Exception e) {
Log.e(LOGTAG, "Datagram error", e);
}
}
});
return handler;
}
public AsyncDatagramSocket connectDatagram(final SocketAddress remote) throws IOException {
final DatagramChannel socket = DatagramChannel.open();
final AsyncDatagramSocket handler = new AsyncDatagramSocket();
handler.attach(socket);
// ugh.. this should really be post to make it nonblocking...
// but i want datagrams to be immediately writable.
// they're not really used anyways.
run(new Runnable() {
@Override
public void run() {
try {
handleSocket(handler);
socket.connect(remote);
}
catch (Exception e) {
}
}
});
return handler;
}
static WeakHashMap<Thread, AsyncServer> mServers = new WeakHashMap<Thread, AsyncServer>();
private boolean addMe() {
synchronized (mServers) {
AsyncServer current = mServers.get(Thread.currentThread());
if (current != null) {
// Log.e(LOGTAG, "****AsyncServer already running on this thread.****");
return false;
}
mServers.put(mAffinity, this);
}
return true;
}
public static AsyncServer getCurrentThreadServer() {
return mServers.get(Thread.currentThread());
}
Thread mAffinity;
public void run() {
run(false, false);
}
public void run(final boolean keepRunning, boolean newThread) {
final Selector selector;
final LinkedList<Scheduled> queue;
boolean reentrant = false;
synchronized (this) {
if (mSelector != null) {
Log.i(LOGTAG, "Reentrant call");
assert Thread.currentThread() == mAffinity;
// this is reentrant
reentrant = true;
selector = mSelector;
queue = mQueue;
}
else {
try {
selector = mSelector = SelectorProvider.provider().openSelector();
queue = mQueue;
}
catch (IOException e) {
return;
}
if (newThread) {
mAffinity = new Thread("AsyncServer") {
public void run() {
AsyncServer.run(AsyncServer.this, selector, queue, keepRunning);
}
};
}
else {
mAffinity = Thread.currentThread();
}
if (!addMe()) {
try {
mSelector.close();
}
catch (Exception e) {
}
mSelector = null;
mAffinity = null;
return;
}
if (newThread) {
mAffinity.start();
// kicked off the new thread, let's bail.
return;
}
// fall through to outside of the synchronization scope
// to allow the thread to run without locking.
}
}
if (reentrant) {
try {
runLoop(this, selector, queue, false);
}
catch (Exception e) {
Log.e(LOGTAG, "exception?", e);
}
return;
}
run(this, selector, queue, keepRunning);
}
private static void run(AsyncServer server, Selector selector, LinkedList<Scheduled> queue, boolean keepRunning) {
// Log.i(LOGTAG, "****AsyncServer is starting.****");
// at this point, this local queue and selector are owned
// by this thread.
// if a stop is called, the instance queue and selector
// will be replaced and nulled respectively.
// this will allow the old queue and selector to shut down
// gracefully, while also allowing a new selector thread
// to start up while the old one is still shutting down.
while(true) {
try {
runLoop(server, selector, queue, keepRunning);
}
catch (ClosedSelectorException e) {
}
catch (Exception e) {
Log.e(LOGTAG, "exception?", e);
}
// see if we keep looping, this must be in a synchronized block since the queue is accessed.
synchronized (server) {
if (selector.isOpen() && (selector.keys().size() > 0 || keepRunning || queue.size() > 0))
continue;
shutdownEverything(selector);
if (server.mSelector == selector) {
server.mQueue = new LinkedList<Scheduled>();
server.mSelector = null;
server.mAffinity = null;
}
break;
}
}
synchronized (mServers) {
mServers.remove(Thread.currentThread());
}
// Log.i(LOGTAG, "****AsyncServer has shut down.****");
}
private static void shutdownEverything(Selector selector) {
try {
for (SelectionKey key: selector.keys()) {
try {
key.channel().close();
}
catch (Exception e) {
}
try {
key.cancel();
}
catch (Exception e) {
}
}
}
catch (Exception ex) {
}
// SHUT. DOWN. EVERYTHING.
try {
selector.close();
}
catch (Exception e) {
}
}
private static final long QUEUE_EMPTY = Long.MAX_VALUE;
private static long lockAndRunQueue(AsyncServer server, LinkedList<Scheduled> queue) {
long wait = QUEUE_EMPTY;
// find the first item we can actually run
while (true) {
Scheduled run = null;
synchronized (server) {
long now = System.currentTimeMillis();
LinkedList<Scheduled> later = null;
while (queue.size() > 0) {
Scheduled s = queue.remove();
if (s.time <= now) {
run = s;
break;
}
else {
wait = Math.min(wait, s.time - now);
if (later == null)
later = new LinkedList<AsyncServer.Scheduled>();
later.add(s);
}
}
if (later != null)
queue.addAll(later);
}
if (run == null)
break;
run.runnable.run();
}
return wait;
}
private static void runLoop(AsyncServer server, Selector selector, LinkedList<Scheduled> queue, boolean keepRunning) throws IOException {
// Log.i(LOGTAG, "Keys: " + selector.keys().size());
boolean needsSelect = true;
// run the queue to populate the selector with keys
long wait = lockAndRunQueue(server, queue);
synchronized (server) {
// select now to see if anything is ready immediately. this
// also clears the canceled key queue.
int readyNow = selector.selectNow();
if (readyNow == 0) {
// if there is nothing to select now, make sure we don't have an empty key set
// which means it would be time to turn this thread off.
if (selector.keys().size() == 0 && !keepRunning && wait == QUEUE_EMPTY) {
// Log.i(LOGTAG, "Shutting down. keys: " + selector.keys().size() + " keepRunning: " + keepRunning);
return;
}
}
else {
needsSelect = false;
}
}
if (needsSelect) {
if (wait == QUEUE_EMPTY)
wait = 100;
// nothing to select immediately but there so let's block and wait.
selector.select(wait);
}
// process whatever keys are ready
Set<SelectionKey> readyKeys = selector.selectedKeys();
for (SelectionKey key : readyKeys) {
try {
if (key.isAcceptable()) {
ServerSocketChannel nextReady = (ServerSocketChannel) key.channel();
SocketChannel sc = nextReady.accept();
if (sc == null)
continue;
sc.configureBlocking(false);
SelectionKey ckey = sc.register(selector, SelectionKey.OP_READ);
ListenCallback serverHandler = (ListenCallback) key.attachment();
AsyncNetworkSocket handler = new AsyncNetworkSocket();
handler.attach(sc, (InetSocketAddress)sc.socket().getRemoteSocketAddress());
handler.setup(server, ckey);
ckey.attach(handler);
serverHandler.onAccepted(handler);
}
else if (key.isReadable()) {
AsyncNetworkSocket handler = (AsyncNetworkSocket) key.attachment();
int transmitted = handler.onReadable();
server.onDataTransmitted(transmitted);
}
else if (key.isWritable()) {
AsyncNetworkSocket handler = (AsyncNetworkSocket) key.attachment();
handler.onDataWritable();
}
else if (key.isConnectable()) {
ConnectFuture cancel = (ConnectFuture) key.attachment();
SocketChannel sc = (SocketChannel) key.channel();
key.interestOps(SelectionKey.OP_READ);
try {
sc.finishConnect();
AsyncNetworkSocket newHandler = new AsyncNetworkSocket();
newHandler.setup(server, key);
newHandler.attach(sc, (InetSocketAddress)sc.socket().getRemoteSocketAddress());
key.attach(newHandler);
if (cancel.setComplete(newHandler))
cancel.callback.onConnectCompleted(null, newHandler);
}
catch (Exception ex) {
key.cancel();
sc.close();
if (cancel.setComplete(ex))
cancel.callback.onConnectCompleted(ex, null);
}
}
else {
Log.i(LOGTAG, "wtf");
assert false;
}
}
catch (Exception ex) {
Log.e(LOGTAG, "inner loop exception", ex);
}
}
readyKeys.clear();
}
public void dump() {
post(new Runnable() {
@Override
public void run() {
if (mSelector == null) {
Log.i(LOGTAG, "Server dump not possible. No selector?");
return;
}
Log.i(LOGTAG, "Key Count: " + mSelector.keys().size());
for (SelectionKey key: mSelector.keys()) {
Log.i(LOGTAG, "Key: " + key);
}
}
});
}
public Thread getAffinity() {
return mAffinity;
}
public boolean isAffinityThread() {
return mAffinity == Thread.currentThread();
}
}