/** * Logback: the reliable, generic, fast and flexible logging framework. * Copyright (C) 1999-2015, QOS.ch. All rights reserved. * * This program and the accompanying materials are dual-licensed under * either the terms of the Eclipse Public License v1.0 as published by * the Eclipse Foundation * * or (per the licensee's choosing) * * under the terms of the GNU Lesser General Public License version 2.1 * as published by the Free Software Foundation. */ package ch.qos.logback.core.net.server; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.concurrent.Executor; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import ch.qos.logback.core.spi.ContextAwareBase; /** * A concurrent {@link ServerRunner}. * <p> * An instance of this object is created with a {@link ServerListener} and * an {@link Executor}. On invocation of the {@link #start()} method, it * passes itself to the given {@code Executor} and returns immediately. On * invocation of its {@link #run()} method by the {@link Executor} it begins * accepting client connections via its {@code ServerListener}. As each * new {@link Client} is accepted, the client is configured with the * runner's {@link LoggingContext} and is then passed to the {@code * Executor} for concurrent execution of the client's service loop. * <p> * On invocation of the {@link #stop()} method, the runner closes the listener * and each of the connected clients (by invoking {@link Client#close()} * effectively interrupting any blocked I/O calls and causing these concurrent * subtasks to exit gracefully). This ensures that before the {@link #stop()} * method returns (1) all I/O resources have been released and (2) all * of the threads of the {@code Executor} are idle. * * @author Carl Harris */ public abstract class ConcurrentServerRunner<T extends Client> extends ContextAwareBase implements Runnable, ServerRunner<T> { private final Lock clientsLock = new ReentrantLock(); private final Collection<T> clients = new ArrayList<T>(); private final ServerListener<T> listener; private final Executor executor; private boolean running; /** * Constructs a new server runner. * @param listener the listener from which the server will accept new * clients * @param executor a executor that will facilitate execution of the * listening and client-handling tasks; while any {@link Executor} * is allowed here, outside of unit testing the only reasonable choice * is a bounded thread pool of some kind. */ public ConcurrentServerRunner(ServerListener<T> listener, Executor executor) { this.listener = listener; this.executor = executor; } /** * {@inheritDoc} */ public boolean isRunning() { return running; } protected void setRunning(boolean running) { this.running = running; } /** * {@inheritDoc} */ public void stop() throws IOException { listener.close(); accept(new ClientVisitor<T>() { public void visit(T client) { client.close(); } }); } /** * {@inheritDoc} */ public void accept(ClientVisitor<T> visitor) { Collection<T> clients = copyClients(); for (T client : clients) { try { visitor.visit(client); } catch (RuntimeException ex) { addError(client + ": " + ex); } } } /** * Creates a copy of the collection of all clients that are presently * being tracked by the server. * @return collection of client objects */ private Collection<T> copyClients() { clientsLock.lock(); try { Collection<T> copy = new ArrayList<T>(clients); return copy; } finally { clientsLock.unlock(); } } /** * {@inheritDoc} */ public void run() { setRunning(true); try { addInfo("listening on " + listener); while (!Thread.currentThread().isInterrupted()) { T client = listener.acceptClient(); if (!configureClient(client)) { addError(client + ": connection dropped"); client.close(); continue; } try { executor.execute(new ClientWrapper(client)); } catch (RejectedExecutionException ex) { addError(client + ": connection dropped"); client.close(); } } } catch (InterruptedException ex) { assert true; // ok... we'll shut down } catch (Exception ex) { addError("listener: " + ex); } setRunning(false); addInfo("shutting down"); listener.close(); } /** * Configures a connected client. * <p> * A subclass implements this method to perform any necessary configuration * of the client object before its {@link Client#run()} method is invoked. * * @param client the subject client * @return {@code true} if configuration was successful; if the return * value is {@code false} the client connection will be dropped */ protected abstract boolean configureClient(T client); /** * Adds a client to the collection of those being tracked by the server. * @param client the client to add */ private void addClient(T client) { clientsLock.lock(); try { clients.add(client); } finally { clientsLock.unlock(); } } /** * Removes a client from the collection of those being tracked by the server. * @param client the client to remote */ private void removeClient(T client) { clientsLock.lock(); try { clients.remove(client); } finally { clientsLock.unlock(); } } /** * A wrapper for a {@link Client} responsible for ensuring that client * tracking is performed properly. */ private class ClientWrapper implements Client { private final T delegate; public ClientWrapper(T client) { this.delegate = client; } public void run() { addClient(delegate); try { delegate.run(); } finally { removeClient(delegate); } } public void close() { delegate.close(); } } }