org.apache.rocketmq.shaded.ch.qos.logback.core.net.server.ConcurrentServerRunner Maven / Gradle / Ivy
/**
* 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 org.apache.rocketmq.shaded.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 org.apache.rocketmq.shaded.ch.qos.logback.core.spi.ContextAwareBase;
/**
* A concurrent {@link ServerRunner}.
*
* 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.
*
* 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 extends ContextAwareBase implements Runnable, ServerRunner {
private final Lock clientsLock = new ReentrantLock();
private final Collection clients = new ArrayList();
private final ServerListener 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 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() {
public void visit(T client) {
client.close();
}
});
}
/**
* {@inheritDoc}
*/
public void accept(ClientVisitor visitor) {
Collection 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 copyClients() {
clientsLock.lock();
try {
Collection copy = new ArrayList(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.
*
* 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();
}
}
}