/* Copyright (c) 2006, Sriram Srinivasan * * You may distribute this software under the terms of the license * specified in the file "License" */ package kilim.nio; import java.io.IOException; import java.net.InetSocketAddress; 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.LinkedList; import kilim.Mailbox; import kilim.Pausable; import kilim.RingQueue; import kilim.Scheduler; import kilim.Task; import kilim.http.IntList; /** * This class wraps a selector and runs it in a separate thread. * * It runs one or more ListenTasks (bound to their respective ports), which in turn spawn as many session tasks (see * {@link #listen(int, Class, Scheduler)}) as the number of new http connections. The supplied scheduler is used to * execute the tasks. It is possible, although not typical, to run tasks in the NioSelectorScheduler itself, as it too * is a scheduler. * * Usage is as follows: * <pre> * NioSelectorScheduler nss = new NioSelectorScheduler(); * nss.listen(8080, MySessionTask.class, Scheduler.getDefaultScheduler(); * * class MySessionTask extends SessionTask { * ... * } * </pre> * @see SessionTask */ public class NioSelectorScheduler extends Scheduler { //TODO: Fix hardcoding public static int LISTEN_BACKLOG = 1000; public Selector sel; /* * The thread in which the selector runs. THe NioSelectorScheduler only runs one thread, * unlike typical schedulers that manage a pool of threads. */ public SelectorThread selectorThread; /** * SessionTask registers its endpoint with the selector by sending a SockEvent * message on this mailbox. */ public Mailbox<SockEvent> registrationMbx = new Mailbox<SockEvent>(1000); /** * @throws IOException */ public NioSelectorScheduler() throws IOException { this.sel = Selector.open(); selectorThread = new SelectorThread(this); selectorThread.start(); Task t = new RegistrationTask(registrationMbx, sel); t.setScheduler(this); t.start(); } public int listen(int port, Class<? extends SessionTask> sockTaskClass, Scheduler sockTaskScheduler) throws IOException { ListenTask t = new ListenTask(port, this, sockTaskClass); t.setScheduler(this); t.start(); return t.port(); } @Override public void schedule(Task t) { addRunnable(t); if (Thread.currentThread() != selectorThread) { sel.wakeup(); } } @Override public void shutdown() { super.shutdown(); sel.wakeup(); } synchronized void addRunnable(Task t) { runnableTasks.put(t); } synchronized RingQueue<Task> swapRunnables(RingQueue<Task> emptyRunnables) { RingQueue<Task> ret = runnableTasks; runnableTasks = emptyRunnables; return ret; } static class SelectorThread extends Thread { NioSelectorScheduler _scheduler; public SelectorThread(NioSelectorScheduler scheduler) { super("KilimSelector"); _scheduler = scheduler; } @Override public void run() { Selector sel = _scheduler.sel; RingQueue<Task> runnables = new RingQueue<Task>(100); // to swap with scheduler while (true) { int n; try { if (_scheduler.isShutdown()) { Iterator<SelectionKey> it = sel.keys().iterator(); while (it.hasNext()) { SelectionKey sk = it.next(); sk.cancel(); Object o = sk.attachment(); if (o instanceof SockEvent && ((SockEvent)o).ch instanceof ServerSocketChannel) { // TODO FIX: Need a proper, orderly shutdown procedure for tasks. This closes down the task // irrespective of the thread it may be running on. Terrible. try { ((ServerSocketChannel)((SockEvent)o).ch).close(); } catch (IOException ignore) {} } } break; } if (_scheduler.numRunnables() > 0) { n = sel.selectNow(); } else { n = sel.select(); } } catch (IOException ignore) { n = 0; ignore.printStackTrace(); } if (n > 0) { Iterator<SelectionKey> it = sel.selectedKeys().iterator(); while (it.hasNext()) { SelectionKey sk = it.next(); it.remove(); Object o = sk.attachment(); sk.interestOps(0); if (o instanceof SockEvent) { SockEvent ev = (SockEvent) o; ev.replyTo.putnb(ev); } else if (o instanceof Task) { Task t = (Task) o; t.resume(); } } } runnables.reset(); runnables = _scheduler.swapRunnables(runnables); // Now execute all runnables inline // if (runnables.size() == 0) { // System.out.println("IDLE"); // } while (runnables.size() > 0) { Task t = runnables.get(); t._runExecute(null); // If task calls Task.yield, it would have added itself to scheduler already. // If task's pauseReason is YieldToSelector, then nothing more to do. // Task should be registered for the appropriate Selector op. // In all other cases, (Task.sleep(), Mailbox.get() etc.), unregister // the channel if (t instanceof SessionTask) { SessionTask st = (SessionTask) t; if (st.isDone()) { st.close(); } } } } } } public synchronized int numRunnables() { return runnableTasks.size(); } public static class ListenTask extends SessionTask { Class<? extends SessionTask> sessionClass; ServerSocketChannel ssc; int port; public ListenTask(int port, NioSelectorScheduler selScheduler, Class<? extends SessionTask> sessionClass) throws IOException { this.port = port; this.sessionClass = sessionClass; this.ssc = ServerSocketChannel.open(); ssc.socket().setReuseAddress(true); ssc.socket().bind(new InetSocketAddress(port), LISTEN_BACKLOG); // ssc.configureBlocking(false); setEndPoint(new EndPoint(selScheduler.registrationMbx, ssc)); // if port is automatically assigned then retrieve actual value if(port == 0) { this.port = ssc.socket().getLocalPort(); }; } public int port() { return this.port; } public String toString() { return "ListenTask: " + port; } @Override public void execute() throws Pausable, Exception { int n = 0; while (true) { SocketChannel ch = ssc.accept(); if (this.scheduler.isShutdown()) { ssc.close(); break; } if (ch == null) { endpoint.pauseUntilAcceptable(); } else { ch.socket().setTcpNoDelay(true); ch.configureBlocking(false); SessionTask task = sessionClass.newInstance(); try { EndPoint ep = new EndPoint(this.endpoint.sockEvMbx, ch); task.setEndPoint(ep); n++; // System.out.println("Num sessions created:" + n); task.start(); } catch (IOException ioe) { ch.close(); System.err.println("Unable to start session:"); ioe.printStackTrace(); } } } } } public static class RegistrationTask extends Task { Mailbox<SockEvent> mbx; Selector selector; public RegistrationTask(Mailbox<SockEvent> ambx, Selector asel) { mbx = ambx; selector = asel; } @Override public void execute() throws Pausable, Exception { while (true) { SockEvent ev = mbx.get(); SelectionKey sk = ev.ch.register(selector, ev.interestOps); sk.attach(ev); } } } }