com.bytesgo.littleproxy.server.ServerGroup Maven / Gradle / Ivy
Show all versions of littleproxy Show documentation
package com.bytesgo.littleproxy.server;
import java.nio.channels.spi.SelectorProvider;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bytesgo.littleproxy.config.ProxyServerConfiguration;
import com.bytesgo.littleproxy.exception.UnknownTransportProtocolException;
import com.bytesgo.littleproxy.model.enums.TransportProtocol;
import com.bytesgo.littleproxy.util.NettyUdtUtil;
import com.bytesgo.littleproxy.util.ProxyUtil;
import io.netty.channel.EventLoopGroup;
/**
* Manages thread pools for one or more proxy server instances. When servers are
* created, they must register with the ServerGroup using
* {@link #registerProxyServer(HttpProxyServer)}, and when they shut down, must
* unregister with the ServerGroup using
* {@link #unregisterProxyServer(HttpProxyServer, boolean)}.
*/
public class ServerGroup {
private static final Logger log = LoggerFactory.getLogger(ServerGroup.class);
/**
* The default number of threads to accept incoming requests from clients.
* (Requests are serviced by worker threads, not acceptor threads.)
*/
public static final int DEFAULT_INCOMING_ACCEPTOR_THREADS = 2;
/**
* The default number of threads to service incoming requests from clients.
*/
public static final int DEFAULT_INCOMING_WORKER_THREADS = 8;
/**
* The default number of threads to service outgoing requests to servers.
*/
public static final int DEFAULT_OUTGOING_WORKER_THREADS = 8;
/**
* Global counter for the {@link #serverGroupId}.
*/
private static final AtomicInteger serverGroupCount = new AtomicInteger(0);
/**
* A name for this ServerGroup to use in naming threads.
*/
private final String name;
/**
* The ID of this server group. Forms part of the name of each thread created
* for this server group. Useful for differentiating threads when multiple proxy
* instances are running.
*/
private final int serverGroupId;
private final int incomingAcceptorThreadSize;
private final int incomingWorkerThreadSize;
private final int outgoingWorkerThreadSize;
/**
* List of all servers registered to use this ServerGroup. Any access to this
* list should be synchronized using the {@link #SERVER_REGISTRATION_LOCK}.
*/
public final List httpProxyServers = new ArrayList(1);
/**
* A mapping of {@link TransportProtocol}s to their initialized
* {@link ProxyThreadPool}. Each transport uses a different thread pool, since
* the initialization parameters are different.
*/
private final EnumMap protocolThreadPools = new EnumMap(
TransportProtocol.class);
/**
* A mapping of selector providers to transport protocols. Avoids special-casing
* each transport protocol during transport protocol initialization.
*/
private static final EnumMap TRANSPORT_PROTOCOL_SELECTOR_PROVIDERS = new EnumMap(
TransportProtocol.class);
static {
TRANSPORT_PROTOCOL_SELECTOR_PROVIDERS.put(TransportProtocol.TCP, SelectorProvider.provider());
// allow the proxy to operate without UDT support. this allows clients that do
// not use UDT to exclude the barchart
// dependency completely.
if (ProxyUtil.isUdtAvailable()) {
TRANSPORT_PROTOCOL_SELECTOR_PROVIDERS.put(TransportProtocol.UDT, NettyUdtUtil.getUdtProvider());
} else {
log.debug("UDT provider not found on classpath. UDT transport will not be available.");
}
}
/**
* True when this ServerGroup is stopped.
*/
private final AtomicBoolean stopped = new AtomicBoolean(false);
/**
* Creates a new ServerGroup instance for a proxy. Threads created for this
* ServerGroup will have the specified ServerGroup name in the Thread name. This
* constructor does not actually initialize any thread pools; instead, thread
* pools for specific transport protocols are lazily initialized as needed.
*
* @param name ServerGroup name to include in thread names
* @param incomingAcceptorThreadSize number of acceptor threads per protocol
* @param incomingWorkerThreadSize number of client-to-proxy worker threads
* per protocol
* @param outgoingWorkerThreadSize number of proxy-to-server worker threads
* per protocol
*/
public ServerGroup(String name, ProxyServerConfiguration proxyServerConfiguration) {
this.name = name;
if (proxyServerConfiguration == null) {
proxyServerConfiguration = new ProxyServerConfiguration();
}
this.serverGroupId = serverGroupCount.getAndIncrement();
this.incomingAcceptorThreadSize = proxyServerConfiguration.getAcceptorThreadSize();
this.incomingWorkerThreadSize = proxyServerConfiguration.getClientToProxyWorkerThreadSize();
this.outgoingWorkerThreadSize = proxyServerConfiguration.getProxyToServerWorkerThreadSize();
}
/**
* Lock for initializing any transport protocols.
*/
private final Object THREAD_POOL_INIT_LOCK = new Object();
/**
* Retrieves the {@link ProxyThreadPool} for the specified transport protocol.
* Lazily initializes the thread pools for the transport protocol if they have
* not yet been initialized. If the protocol has already been initialized, this
* method returns immediately, without synchronization. If initialization is
* necessary, the initialization process creates the acceptor and worker threads
* necessary to service requests to/from the proxy.
*
* This method is thread-safe; no external locking is necessary.
*
* @param protocol transport protocol to retrieve thread pools for
* @return thread pools for the specified transport protocol
*/
private ProxyThreadPool getThreadPool(TransportProtocol protocol) {
// if the thread pools have not been initialized for this protocol, initialize
// them
if (protocolThreadPools.get(protocol) == null) {
synchronized (THREAD_POOL_INIT_LOCK) {
if (protocolThreadPools.get(protocol) == null) {
log.debug(
"Initializing thread pools for {} with {} acceptor threads, {} incoming worker threads, and {} outgoing worker threads",
protocol, incomingAcceptorThreadSize, incomingWorkerThreadSize, outgoingWorkerThreadSize);
SelectorProvider selectorProvider = TRANSPORT_PROTOCOL_SELECTOR_PROVIDERS.get(protocol);
if (selectorProvider == null) {
throw new UnknownTransportProtocolException(protocol);
}
ProxyThreadPool threadPool = new ProxyThreadPool(selectorProvider, incomingAcceptorThreadSize, incomingWorkerThreadSize,
outgoingWorkerThreadSize, name, serverGroupId);
protocolThreadPools.put(protocol, threadPool);
}
}
}
return protocolThreadPools.get(protocol);
}
/**
* Lock controlling access to the {@link #registerProxyServer(HttpProxyServer)}
* and {@link #unregisterProxyServer(HttpProxyServer, boolean)} methods.
*/
private final Object SERVER_REGISTRATION_LOCK = new Object();
/**
* Registers the specified proxy server as a consumer of this server group. The
* server group will not be shut down until the proxy unregisters itself.
*
* @param proxyServer proxy server instance to register
*/
public void registerProxyServer(HttpProxyServer proxyServer) {
synchronized (SERVER_REGISTRATION_LOCK) {
httpProxyServers.add(proxyServer);
}
}
/**
* Unregisters the specified proxy server from this server group. If this was
* the last registered proxy server, the server group will be shut down.
*
* @param proxyServer proxy server instance to unregister
* @param graceful when true, the server group shutdown (if necessary) will
* be graceful
*/
public void unregisterProxyServer(HttpProxyServer proxyServer, boolean graceful) {
synchronized (SERVER_REGISTRATION_LOCK) {
boolean wasRegistered = httpProxyServers.remove(proxyServer);
if (!wasRegistered) {
log.warn(
"Attempted to unregister proxy server from ServerGroup that it was not registered with. Was the proxy unregistered twice?");
}
if (httpProxyServers.isEmpty()) {
log.debug("Proxy server unregistered from ServerGroup. No proxy servers remain registered, so shutting down ServerGroup.");
shutdown(graceful);
} else {
log.debug("Proxy server unregistered from ServerGroup. Not shutting down ServerGroup ({} proxy servers remain registered).",
httpProxyServers.size());
}
}
}
/**
* Shuts down all event loops owned by this server group.
*
* @param graceful when true, event loops will "gracefully" terminate, waiting
* for submitted tasks to finish
*/
private void shutdown(boolean graceful) {
if (!stopped.compareAndSet(false, true)) {
log.info("Shutdown requested, but ServerGroup is already stopped. Doing nothing.");
return;
}
log.info("Shutting down server group event loops " + (graceful ? "(graceful)" : "(non-graceful)"));
// loop through all event loops managed by this server group. this includes
// acceptor and worker event loops
// for both TCP and UDP transport protocols.
List allEventLoopGroups = new ArrayList();
for (ProxyThreadPool threadPools : protocolThreadPools.values()) {
allEventLoopGroups.addAll(threadPools.getAllEventLoops());
}
for (EventLoopGroup group : allEventLoopGroups) {
if (graceful) {
group.shutdownGracefully();
} else {
group.shutdownGracefully(0, 0, TimeUnit.SECONDS);
}
}
if (graceful) {
for (EventLoopGroup group : allEventLoopGroups) {
try {
group.awaitTermination(60, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.warn("Interrupted while shutting down event loop");
}
}
}
log.debug("Done shutting down server group");
}
/**
* Retrieves the client-to-proxy acceptor thread pool for the specified
* protocol. Initializes the pool if it has not yet been initialized.
*
* This method is thread-safe; no external locking is necessary.
*
* @param protocol transport protocol to retrieve the thread pool for
* @return the client-to-proxy acceptor thread pool
*/
public EventLoopGroup getClientToProxyAcceptor(TransportProtocol protocol) {
return getThreadPool(protocol).getClientToProxyAcceptor();
}
/**
* Retrieves the client-to-proxy acceptor worker pool for the specified
* protocol. Initializes the pool if it has not yet been initialized.
*
* This method is thread-safe; no external locking is necessary.
*
* @param protocol transport protocol to retrieve the thread pool for
* @return the client-to-proxy worker thread pool
*/
public EventLoopGroup getClientToProxyWorker(TransportProtocol protocol) {
return getThreadPool(protocol).getClientToProxyWorker();
}
/**
* Retrieves the proxy-to-server worker thread pool for the specified protocol.
* Initializes the pool if it has not yet been initialized.
*
* This method is thread-safe; no external locking is necessary.
*
* @param protocol transport protocol to retrieve the thread pool for
* @return the proxy-to-server worker thread pool
*/
public EventLoopGroup getProxyToServerWorker(TransportProtocol protocol) {
return getThreadPool(protocol).getProxyToServerWorker();
}
/**
* @return true if this ServerGroup has already been stopped
*/
public boolean isStopped() {
return stopped.get();
}
}