/* * The MIT License * * Copyright 2011-2014 Tim Boudreau. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.mastfrog.acteur.server; import com.google.inject.Provider; import com.google.inject.name.Named; import static com.mastfrog.acteur.server.ServerModule.EVENT_THREADS; import static com.mastfrog.acteur.server.ServerModule.SETTINGS_KEY_SYSTEM_EXIT_ON_BIND_FAILURE; import static com.mastfrog.acteur.server.ServerModule.WORKER_THREADS; import com.mastfrog.acteur.spi.ApplicationControl; import com.mastfrog.acteur.util.Server; import com.mastfrog.acteur.util.ServerControl; import com.mastfrog.giulius.ShutdownHookRegistry; import com.mastfrog.settings.Settings; import com.mastfrog.util.Exceptions; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import java.io.IOException; import java.lang.ref.Reference; import java.lang.ref.WeakReference; import java.net.BindException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.Date; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import javax.inject.Inject; /** * * @author Tim BoudreauUpstreamHandlerImpl */ final class ServerImpl implements Server { private final ChannelInitializer<SocketChannel> pipelineFactory; private int port = 8123; private final ThreadFactory eventThreadFactory; private final ThreadCount eventThreadCount; private final ThreadFactory workerThreadFactory; private final ThreadCount workerThreadCount; private final String applicationName; private final ShutdownHookRegistry registry; private final Provider<ServerBootstrap> bootstrapProvider; private final Provider<ApplicationControl> app; private final Settings settings; @Inject ServerImpl( ChannelInitializer<SocketChannel> pipelineFactory, @Named(EVENT_THREADS) ThreadFactory eventThreadFactory, @Named(EVENT_THREADS) ThreadCount eventThreadCount, @Named(WORKER_THREADS) ThreadFactory workerThreadFactory, @Named(WORKER_THREADS) ThreadCount workerThreadCount, @Named("application") String applicationName, Provider<ServerBootstrap> bootstrapProvider, ShutdownHookRegistry registry, Provider<ApplicationControl> app, Settings settings) { this.port = settings.getInt(ServerModule.PORT, 8123); this.pipelineFactory = pipelineFactory; this.eventThreadFactory = eventThreadFactory; this.eventThreadCount = eventThreadCount; this.workerThreadFactory = workerThreadFactory; this.workerThreadCount = workerThreadCount; this.applicationName = applicationName; this.bootstrapProvider = bootstrapProvider; this.registry = registry; this.app = app; this.settings = settings; } @Override public int getPort() { return port; } @Override public String toString() { return applicationName + " on port " + port + " with " + eventThreadCount.get() + " event threads and " + workerThreadCount.get() + " worker threads."; } @Override public ServerControl start(int port) throws IOException { this.port = port; try { final CountDownLatch afterStart = new CountDownLatch(1); final ServerControlImpl result = new ServerControlImpl(port, afterStart); ServerBootstrap bootstrap = bootstrapProvider.get(); String bindAddress = settings.getString("bindAddress"); InetAddress addr = null; if (bindAddress != null) { addr = InetAddress.getByName(bindAddress); } bootstrap.group(result.events, result.workers) .channel(NioServerSocketChannel.class) .option(ChannelOption.TCP_NODELAY, true) .childHandler(pipelineFactory); if (addr == null) { bootstrap = bootstrap.localAddress(new InetSocketAddress(port)); } else { bootstrap = bootstrap.localAddress(addr, port); } // Bind and start to accept incoming connections. bootstrap.bind().addListener(result); System.err.println("Starting " + this); if (settings.getBoolean(ServerModule.SETTINGS_KEY_CORS_ENABLED, true)) { // XXX ugly place to do this app.get().enableDefaultCorsHandling(); } afterStart.await(); return result.throwIfFailure(); } catch (InterruptedException ex) { return Exceptions.chuck(ex); } } public ServerControl start() throws IOException { return start(this.port); } @Override public ServerControl start(boolean ssl) throws IOException { throw new UnsupportedOperationException("Not supported yet."); } @Override public ServerControl start(int port, boolean ssl) throws IOException { throw new UnsupportedOperationException("Not supported yet."); } private static class WeakRunnable implements Runnable { private final Reference<Runnable> delegate; WeakRunnable(Runnable real) { this.delegate = new WeakReference<>(real); } @Override public void run() { Runnable real = delegate.get(); if (real != null) { real.run(); } } } private class ServerControlImpl implements ServerControl, Runnable, ChannelFutureListener { private Channel localChannel; private final EventLoopGroup events = new NioEventLoopGroup(eventThreadCount.get(), eventThreadFactory); private final EventLoopGroup workers = new NioEventLoopGroup(workerThreadCount.get(), workerThreadFactory); private final int port; private final CountDownLatch afterStart; private final CountDownLatch waitClose = new CountDownLatch(1); private volatile boolean shuttingDown; ServerControlImpl(int port, CountDownLatch afterStart) { this.port = port; this.afterStart = afterStart; } public void shutdown(boolean immediately, long timeout, TimeUnit unit) throws InterruptedException { shutdown(timeout, unit, true); } private synchronized boolean isTerminated() { return localChannel == null ? true : !localChannel.isOpen(); } private void shutdown(long timeout, TimeUnit unit, boolean await) throws InterruptedException { if (shuttingDown) { // We can reenter on the shutdown hook thread await(timeout, unit); return; } shuttingDown = true; try { Channel ch; synchronized (this) { ch = localChannel; } if (ch != null) { if (ch.isOpen()) { if (await) { ch.close().await(timeout, unit); } else { ch.close(); } } } } catch (InterruptedException ex) { // OK } finally { if (await) { events.shutdownGracefully(0, timeout / 3, unit); workers.shutdownGracefully(0, timeout / 3, unit); } else { events.shutdownGracefully(); workers.shutdownGracefully(); } shuttingDown = false; synchronized(this) { localChannel = null; } } } @Override public void shutdown(boolean immediately) throws InterruptedException { shutdown(1, TimeUnit.SECONDS, true); await(); } @Override public void await() throws InterruptedException { try { // Can be interrupted if the JVM starts running shutdown hooks // and we are already waiting waitClose.await(); } catch (InterruptedException ex) { return; } } @Override public void awaitUninterruptibly() { while (!isTerminated()) { try { await(); } catch (InterruptedException ex) { //do nothing } } } @Override public long awaitNanos(long nanosTimeout) throws InterruptedException { waitClose.await(nanosTimeout, TimeUnit.NANOSECONDS); return 0; } @Override public boolean await(long time, TimeUnit unit) throws InterruptedException { waitClose.await(time, unit); return isTerminated(); } @Override public boolean awaitUntil(Date deadline) throws InterruptedException { long howLong = deadline.getTime() - System.currentTimeMillis(); if (howLong > 0) { return await(howLong, TimeUnit.MILLISECONDS); } return isTerminated(); } @Override public void signal() { signalAll(); } @Override public void signalAll() { try { shutdown(0, TimeUnit.MILLISECONDS, false); } catch (InterruptedException ex) { Logger.getLogger(ServerImpl.class.getName()).log(Level.SEVERE, null, ex); } } @Override public void run() { try { if (!isTerminated()) { System.err.println("Orderly server shutdown"); shutdown(0, TimeUnit.MILLISECONDS, false); } } catch (InterruptedException ex) { Exceptions.chuck(ex); } } @Override public int getPort() { return port; } private Throwable failure; private boolean initialized; @Override public synchronized void operationComplete(ChannelFuture f) throws Exception { if (!initialized) { initialized = true; failure = f.cause(); if (failure == null) { localChannel = f.channel(); registry.add(new WeakRunnable(this)); } else { events.shutdownGracefully(); workers.shutdownGracefully(); } afterStart.countDown(); f.channel().closeFuture().addListener(this); } else { // Waiting for close waitClose.countDown(); } } public synchronized ServerControl throwIfFailure() { if (failure != null) { if (failure instanceof BindException && settings.getBoolean(SETTINGS_KEY_SYSTEM_EXIT_ON_BIND_FAILURE, true)) { failure.printStackTrace(System.err); System.err.flush(); System.exit(1); } Exceptions.chuck(failure); } return this; } } }