/* * JLibs: Common Utilities for Java * Copyright (C) 2009 Santhosh Kumar T <santhosh.tekuri@gmail.com> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package jlibs.nio; import jlibs.core.lang.Waiter; import jlibs.core.util.Heap; import jlibs.nio.util.BufferAllocator; import jlibs.nio.util.PooledBufferAllocator; import jlibs.nio.util.UnpooledBufferAllocator; import javax.management.ObjectName; import java.io.IOException; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.util.*; import java.util.function.Consumer; import java.util.stream.Collectors; import static java.nio.channels.SelectionKey.OP_ACCEPT; import static jlibs.nio.Debugger.*; /** * @author Santhosh Kumar Tekuri */ public class Reactor{ public final int id; public final Selector selector; public final ConnectionPool connectionPool = new ConnectionPool(this); public final BufferAllocator allocator; long lastAcceptID; long lastConnectID; private final ObjectName objName; Reactor(int id) throws IOException{ this.id = id; selector = Selector.open(); executionID = "R"+id; toString = "Reactor"+id; if(BufferAllocator.Defaults.POOL_BUFFERS) allocator = new PooledBufferAllocator(BufferAllocator.Defaults.USE_DIRECT_BUFFERS); else allocator = BufferAllocator.Defaults.USE_DIRECT_BUFFERS ? UnpooledBufferAllocator.DIRECT : UnpooledBufferAllocator.HEAP; objName = Management.register(new Management.ReactorMXBean(){ @Override public int getServersCount(){ return servers.size(); } @Override public int getAccepted(){ return accepted; } @Override public int getConnectionPending(){ return connectionPending; } @Override public int getConnected(){ return connected; } @Override public int getPooled(){ return connectionPool.count(); } @Override public Map<String, Integer> getPool(){ Map<String, Integer> map[] = new Map[1]; try{ invokeAndWait(()->{ map[0] = connectionPool.entries.values().stream() .filter(t -> t.count>0) .collect(Collectors.toMap(t -> t.key, t -> t.count)); }); }catch(InterruptedException ex){ throw new RuntimeException(ex); } return map[0]; } }, "jlibs.nio:type=Reactor,id="+id); } private final String toString; public String toString(){ return toString; } /*-------------------------------------------------[ ExceptionHandler ]---------------------------------------------------*/ private Consumer<Throwable> exceptionHandler; public void setExceptionHandler(Consumer<Throwable> exceptionHandler){ this.exceptionHandler = exceptionHandler; } public void handleException(Throwable thr){ if(thr==null) return; if(exceptionHandler==null){ if(DEBUG) printStackTrace(thr); else{ System.err.println("Unexpected error during "+executionID+":"); thr.printStackTrace(); } }else exceptionHandler.accept(thr); } /*-------------------------------------------------[ Servers ]---------------------------------------------------*/ final List<TCPServer> servers = new ArrayList<>(); public int getServersCount(){ return servers.size(); } void register(TCPServer server) throws IOException{ if(server.selectable.keyFor(selector)==null){ server.selectable.register(selector, OP_ACCEPT, server); servers.add(server); if(DEBUG) println(server+".register"); } } void unregister(TCPServer server){ if(DEBUG) println(server+".unregister"); servers.remove(server); SelectionKey key = server.selectable.keyFor(selector); if(key!=null && key.isValid()) key.cancel(); } /*-------------------------------------------------[ Connections ]---------------------------------------------------*/ int accepted; int connectionPending; int connected; public int getAccepted(){ return accepted; } public int getConnectionPending(){ return connectionPending; } public int getConnected(){ return connected; } /*-------------------------------------------------[ Tasks ]---------------------------------------------------*/ private volatile Deque<Runnable> tasks = new ArrayDeque<>(); public synchronized void invokeLater(Runnable task){ tasks.push(task); selector.wakeup(); } public void invokeAndWait(Runnable task) throws InterruptedException{ if(Reactor.current()==this) task.run(); else{ task = task instanceof Waiter ? (Waiter)task : new Waiter(task); synchronized(task){ invokeLater(task); task.wait(); } } } /*-------------------------------------------------[ wakeupList ]---------------------------------------------------*/ private NBStream wakeupHead; void wakeup(NBStream nbStream){ if(nbStream.wakeupNext==null){ if(IO) println("wakeup("+nbStream+")"); nbStream.wakeupNext = wakeupHead ==null ? nbStream : wakeupHead; wakeupHead = nbStream; } } /*-------------------------------------------------[ Thread ]---------------------------------------------------*/ void start(){ new ReactorThread().start(); } public static Reactor current(){ Thread thread = Thread.currentThread(); return thread instanceof ReactorThread ? ((ReactorThread)thread).reactor : null; } NBChannel activeChannel; String executionID; public String getExecutionID(){ return activeChannel==null ? executionID : activeChannel.getExecutionID(); } NBChannel getExecutionOwner(){ return activeChannel==null ? null : activeChannel.workingFor; } private class ReactorThread extends Thread implements Thread.UncaughtExceptionHandler{ public Reactor reactor = Reactor.this; ReactorThread(){ super(Reactor.this.toString); setUncaughtExceptionHandler(this); } public void run(){ final Selector selector = reactor.selector; final TimeoutTracker timeoutTracker = reactor.timeoutTracker; Deque<Runnable> tempTasks = new ArrayDeque<>(); NBChannel nbChannel; NBStream nbStream; while(true){ while(wakeupHead!=null){ nbStream = wakeupHead; wakeupHead = null; while(nbStream!=null){ if(nbStream.heapIndex!=-1) timeoutTracker.stopTimer(nbStream); activeChannel = nbStream; try{ nbStream.wakeupNow(); }catch(Throwable thr){ handleException(thr); } NBStream next = nbStream.wakeupNext==nbStream ? null : nbStream.wakeupNext; nbStream.wakeupNext = null; nbStream = next; } } // run tasks while(!tasks.isEmpty()){ synchronized(this){ Deque<Runnable> temp = tasks; tasks = tempTasks; tempTasks = temp; } while(!tempTasks.isEmpty()){ activeChannel = null; if(DEBUG) enter("runTask"); try{ tempTasks.pop().run(); }catch(Throwable thr){ handleException(thr); } if(DEBUG) exit(); } } if(shutdown && servers.size()==0 && connected==0 && connectionPending==0 && accepted==0){ try{ selector.close(); Management.unregister(objName); }catch(Throwable thr){ handleException(thr); } return; } boolean tracking = timeoutTracker.isTracking(); long selectTimeout = tracking ? timeoutTracker.waitTime() : 0L; int selected = 0; try{ if(IO) enter("select("+selectTimeout+")"); selected = selector.select(selectTimeout); }catch(IOException ex){ handleException(ex); } if(tracking) timeoutTracker.time = System.currentTimeMillis(); if(selected>0){ Set<SelectionKey> selectedKeys = selector.selectedKeys(); for(SelectionKey key: selectedKeys){ if(key.isValid()){ nbChannel = (NBChannel)key.attachment(); if(nbChannel.heapIndex!=-1) timeoutTracker.stopTimer(nbChannel); activeChannel = nbChannel; try{ nbChannel.process(false); }catch(Throwable thr){ handleException(thr); } } } selectedKeys.clear(); } if(IO) exit(); if(tracking){ while((nbChannel=timeoutTracker.next())!=null){ activeChannel = nbChannel; if(nbChannel instanceof Connection && ((Connection)nbChannel).poolNext!=null){ if(IO) println(nbChannel+".poolTimeout"); connectionPool.remove((Connection)nbChannel); nbChannel.close(); }else{ try{ nbChannel.process(true); }catch(Throwable thr){ handleException(thr); } } } } } } @Override public void uncaughtException(Thread thread, Throwable throwable){ handleException(throwable); assert !thread.isAlive(); new ReactorThread().start(); } } /*-------------------------------------------------[ Timeout ]---------------------------------------------------*/ private TimeoutTracker timeoutTracker = new TimeoutTracker(); void startTimer(NBChannel channel, long timeout){ if(timeout>0) timeoutTracker.startTimer(channel, timeout); } void stopTimer(NBChannel channel){ timeoutTracker.stopTimer(channel); } private final class TimeoutTracker{ private final Heap<NBChannel> heap = new Heap<NBChannel>(1000){ @Override protected void setIndex(NBChannel channel, int index){ channel.heapIndex = index; } @Override protected int compare(NBChannel channel1, NBChannel channel2){ return channel1.timeoutAt<channel2.timeoutAt ? -1 : (channel1.timeoutAt==channel2.timeoutAt?0:+1); } }; public boolean isTracking(){ return heap.size()>0; } public void startTimer(NBChannel channel, long timeout){ if(channel.heapIndex!=-1) stopTimer(channel); if(timeout>0){ channel.timeoutAt = System.currentTimeMillis() + timeout; heap.add(channel); } } public void stopTimer(NBChannel channel){ assert channel.heapIndex!=-1; NBChannel removed = heap.removeAt(channel.heapIndex); assert removed==channel; assert channel.heapIndex==-1; channel.timeoutAt = Long.MAX_VALUE; } long time; public NBChannel next(){ NBChannel root = heap.root(); if(root!=null && root.timeoutAt<time){ assert root.heapIndex==0; heap.removeAt(0); return root; }else return null; } public long waitTime(){ return heap.size()==0 ? 0L : Math.max(1000L, heap.root().timeoutAt-System.currentTimeMillis()); } } /*-------------------------------------------------[ Shutdown ]---------------------------------------------------*/ private boolean shutdown; void shutdown(boolean force){ if(!shutdown){ shutdown = true; if(Debugger.IO) Debugger.println(Reactor.this+".shutdownInitialized: servers="+getServersCount()+ " connected="+connected+ " connectionPending="+connectionPending+ " accepted="+accepted); while(servers.size()>0) unregister(servers.get(0)); if(force){ for(SelectionKey key: selector.keys()){ try{ key.channel().close(); }catch(IOException ex){ handleException(ex); } } connected = connectionPending = accepted = 0; } } } /*-------------------------------------------------[ Misc ]---------------------------------------------------*/ private StringBuilder builder = new StringBuilder(500); public static StringBuilder stringBuilder(){ Reactor reactor = Reactor.current(); if(reactor==null) return new StringBuilder(100); else{ StringBuilder builder = reactor.builder; reactor.builder = null; builder.setLength(0); return builder; } } public static String free(StringBuilder builder){ Reactor reactor = Reactor.current(); if(reactor!=null) reactor.builder = builder; return builder.toString(); } }