Package edu.brown.protorpc

Source Code of edu.brown.protorpc.NIOEventLoop$Timer

package edu.brown.protorpc;

import java.io.IOException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.PriorityQueue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;

import org.apache.log4j.Logger;

import sun.misc.Signal;
import sun.misc.SignalHandler;

/** Wraps a Java NIO selector to dispatch events. */
public class NIOEventLoop implements EventLoop {
    private static final Logger LOG = Logger.getLogger(NIOEventLoop.class);
   
    public NIOEventLoop() {
        try {
            selector = Selector.open();
        } catch (IOException e) { throw new RuntimeException(e); }
    }

    public void setExitOnSigInt(boolean exitOnSigInt) {
        if (exitOnSigInt) {
            if (sigintHandler != null) {
                throw new IllegalStateException("SIGINT handler already enabled.");
            }
            // Install a handler to exit cleanly on sigint
            // TODO: This doesn't actually work if there are multiple EventLoops in an app.
            sigintHandler = new SigintHandler();
            sigintHandler.install();
        } else {
            if (sigintHandler == null) {
                throw new IllegalStateException("SIGINT handler not enabled.");
            }
            sigintHandler.remove();
            sigintHandler = null;
        }
    }

    private static final Signal SIGINT = new Signal("INT");
    private class SigintHandler implements SignalHandler {
        @Override
        public void handle(Signal signal) {
            System.out.println(signal);
            // mark that we should quit and interrupt the selector. unregister SIGINT
            setExitOnSigInt(false);
            exitLoop();
        }

        public void install() {
            assert oldHandler == null;
            oldHandler = Signal.handle(SIGINT, this);
        }

        public void remove() {
            if (oldHandler != null) {
                Signal.handle(SIGINT, oldHandler);
                oldHandler = null;
            }
        }

        private SignalHandler oldHandler = null;
    }

    @Override
    public void registerRead(SelectableChannel channel, Handler handler) {
        // Disallow both being registered for read events and connection events at the same time.
        // On Linux, when a connect fails, the socket is ready for both events, which causes
        // errors when reads are attempted on the closed socket.
        assert channel.keyFor(selector) == null ||
                (channel.keyFor(selector).interestOps() & SelectionKey.OP_CONNECT) == 0;
        addInterest(channel, SelectionKey.OP_READ, handler);
    }

    @Override
    public void registerAccept(ServerSocketChannel server, Handler handler) {
        assert server.keyFor(selector) == null;
        register(server, SelectionKey.OP_ACCEPT, handler);
    }

    @Override
    public void registerConnect(SocketChannel channel, Handler handler) {
        // Should not be registered
        assert channel.keyFor(selector) == null;
        register(channel, SelectionKey.OP_CONNECT, handler);
    }

    @Override
    public void registerWrite(SelectableChannel channel, Handler handler) {
        addInterest(channel, SelectionKey.OP_WRITE, handler);
    }

    @Override
    public void registerTimer(int timerMilliseconds, Handler handler) {
        assert timerMilliseconds >= 0;
        assert handler != null;
        long expirationMs = System.currentTimeMillis() + timerMilliseconds;
        timers.add(new Timer(expirationMs, handler));
    }

    @Override
    public void cancelTimer(Handler handler) {
        Iterator<Timer> timerIterator = timers.iterator();
        while (timerIterator.hasNext()) {
            Timer timer = timerIterator.next();
            if (timer.handler == handler) {
                timerIterator.remove();
                return;
            }
        }
        throw new IllegalArgumentException("Timer handler not found");
    }

    private void register(SelectableChannel channel, int ops, Handler callback) {
        try {
            channel.configureBlocking(false);
            /*SelectionKey serverKey =*/ channel.register(selector, ops, callback);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private void addInterest(SelectableChannel channel, int operation, Handler callback) {
        // TODO: Support multiple handlers?
        SelectionKey key = channel.keyFor(selector);
        if (key != null) {
            assert (key.interestOps() & operation) == 0;
            if (key.attachment() == null) {
                key.attach(callback);
            } else {
                assert callback == key.attachment();
            }
            key.interestOps(key.interestOps() | operation);
            // TODO: This fixes a synchronization issue where one thread changes the interest set
            // of a thread while another thread is blocked in select(), because the Selector
            // documentation states that it waits for events registered "as of the moment that the
            // selection operation began. Is there a better fix?
            selector.wakeup();
        } else {
            register(channel, operation, callback);
        }
    }

    public void run() {
        if (LOG.isDebugEnabled()) LOG.debug("Starting run() loop");
        while (!exitLoop) {
            runOnce();
        }
        exitLoop = false;
        if (LOG.isDebugEnabled()) LOG.debug("Completed run() loop");
    }

    public void runOnce() {
        long timeoutMs = 0;
        if (!timers.isEmpty()) {
            long now = System.currentTimeMillis();
            timeoutMs = triggerExpiredTimers(now);
        }

        try {
            int readyCount = selector.select(timeoutMs);
            handleSelectedKeys();
            if (readyCount == 0) {
                // TODO: Avoid checking this at both the top and the bottom of the loop.
                triggerExpiredTimers(System.currentTimeMillis());
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /** @return milliseconds until the next timer, or 0 if there are none. */
    private long triggerExpiredTimers(long now) {
        while (!timers.isEmpty()) {
            // hope that using an iterator to fetch and remove the least element is efficient?
            Timer least = timers.peek();
            if (least.expirationMs <= now) {
                timers.poll();

                least.handler.timerCallback();
            } else {
                // this timer has not expired yet: return its time
                long timeoutMs = least.expirationMs - now;
                assert timeoutMs > 0;
                return timeoutMs;
            }
        }

        return 0;
    }

//    public void close() {
//        try {
//            for (SelectionKey key : selector.keys()) {
//                if (key.attachment() != server) {
//                    MessageConnection connection = (MessageConnection) key.attachment();
//                    connection.close();
//                }
//            }
//            selector.close();
//            server.close();
//            eventQueue.clear();
//        } catch (IOException e) { throw new RuntimeException(e); }
//    }
//

    private void handleSelectedKeys() throws IOException {
        Set<SelectionKey> selectedKeys = selector.selectedKeys();
        for (Iterator<SelectionKey> it = selectedKeys.iterator(); it.hasNext(); ) {
            SelectionKey key = it.next();
            EventLoop.Handler callback = (EventLoop.Handler) key.attachment();

            // only handle one event per loop. more efficient: most times only one event is ready,
            // and it avoids canceled key exceptions
            if (key.isReadable()) {
                callback.readCallback(key.channel());
            } else if (key.isWritable()) {
                boolean stillNeedsWrite = callback.writeCallback(key.channel());
                if (!stillNeedsWrite) {
                    // Unregister write callbacks
                    assert key.interestOps() == (SelectionKey.OP_WRITE | SelectionKey.OP_READ);
                    key.interestOps(SelectionKey.OP_READ);
                }
            } else if (key.isAcceptable()) {
                callback.acceptCallback(key.channel());
            } else if (key.isConnectable()) {
                assert key.interestOps() == SelectionKey.OP_CONNECT;
                key.interestOps(0);
                key.attach(null);
                callback.connectCallback((SocketChannel) key.channel());
            } else {
                // Mac OS X has a bug: when an async connect fails, this triggers with
                // key.readyOps == 0.
                assert key.readyOps() == 0;
                assert (key.interestOps() & SelectionKey.OP_CONNECT) != 0;
                System.out.println("Mac bug? no interest: connection failed?");
                callback.connectCallback((SocketChannel) key.channel());
            }
        }

        // Must remove the keys from the selected set
        selectedKeys.clear();

        // Handle any queued thread events
        Runnable callback = null;
        while ((callback = threadEvents.poll()) != null) {
            callback.run();
        }
    }

    public void runInEventThread(Runnable callback) {
        threadEvents.add(callback);
        selector.wakeup();
    }

    public void exitLoop() {
        if (LOG.isDebugEnabled()) LOG.debug("Stopping running loop");
        exitLoop = true;
        selector.wakeup();
    }

    private final Selector selector;
    private SigintHandler sigintHandler;
    // volatile because signal handlers run in other threads
    private volatile boolean exitLoop = false;
    private final ConcurrentLinkedQueue<Runnable> threadEvents =
            new ConcurrentLinkedQueue<Runnable>();

    private static final class Timer implements Comparable<Timer> {
        public final long expirationMs;
        public final Handler handler;

        public Timer(long expirationMs, Handler handler) {
            this.expirationMs = expirationMs;
            this.handler = handler;
        }

        @Override
        public int compareTo(Timer other) {
            if (this.expirationMs < other.expirationMs) return -1;
            if (this.expirationMs > other.expirationMs) return 1;
            return 0;
        }

        @Override
        public boolean equals(Object other) {
            throw new RuntimeException("TODO: implement");
        }

        @Override
        public int hashCode() {
            throw new RuntimeException("TODO: implement");
        }
    }
    private final PriorityQueue<Timer> timers = new PriorityQueue<Timer>();
}
TOP

Related Classes of edu.brown.protorpc.NIOEventLoop$Timer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.