com.kingsoft.services.http.IdleConnectionReaper Maven / Gradle / Ivy
package com.kingsoft.services.http;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.apache.http.conn.ClientConnectionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Daemon thread to periodically check connection pools for idle connections.
*
* Connections sitting around idle in the HTTP connection pool for too long will
* eventually be terminated by the KWS end of the connection, and will go into
* CLOSE_WAIT. If this happens, sockets will sit around in CLOSE_WAIT, still
* using resources on the client side to manage that socket. Many sockets stuck
* in CLOSE_WAIT can prevent the OS from creating new connections.
*
* This class closes idle connections before they can move into the CLOSE_WAIT
* state.
*
* This thread is important because by default, we disable Apache HttpClient's
* stale connection checking, so without this thread running in the background,
* cleaning up old/inactive HTTP connections, we'd see more IO exceptions when
* stale connections (i.e. closed on the KWS side) are left in the connection
* pool, and requests grab one of them to begin executing a request.
*/
public final class IdleConnectionReaper extends Thread {
/** The period between invocations of the idle connection reaper. */
private static final int PERIOD_MILLISECONDS = 1000 * 60 * 1;
/**
* The list of registered connection managers, whose connections will be
* periodically checked and idle connections closed.
*/
private static final ArrayList connectionManagers = new ArrayList();
/**
* Set to true when shutting down the reaper; Once set to true, this flag is
* never set back to false.
*/
private volatile boolean shuttingDown;
/** Singleton instance of the connection reaper. */
private static IdleConnectionReaper instance;
/** Shared log for any errors during connection reaping. */
private static final Logger log = LoggerFactory.getLogger(IdleConnectionReaper.class);
/** Private constructor - singleton pattern. */
private IdleConnectionReaper() {
super("java-sdk-http-connection-reaper");
setDaemon(true);
}
/**
* Registers the given connection manager with this reaper;
*
* @return true if the connection manager has been successfully registered;
* false otherwise.
*/
public static synchronized boolean registerConnectionManager(
ClientConnectionManager connectionManager) {
if (instance == null) {
instance = new IdleConnectionReaper();
instance.start();
}
return connectionManagers.add(connectionManager);
}
/**
* Removes the given connection manager from this reaper, and shutting down
* the reaper if there is zero connection manager left.
*
* @return true if the connection manager has been successfully removed; false
* otherwise.
*/
public static synchronized boolean removeConnectionManager(
ClientConnectionManager connectionManager) {
boolean b = connectionManagers.remove(connectionManager);
if (connectionManagers.isEmpty())
shutdown();
return b;
}
private void markShuttingDown() {
shuttingDown = true;
}
@SuppressWarnings("unchecked")
@Override
public void run() {
while (true) {
if (shuttingDown) {
log.debug("Shutting down reaper thread.");
return;
}
try {
Thread.sleep(PERIOD_MILLISECONDS);
// Copy the list of managed ConnectionManagers to avoid possible
// ConcurrentModificationExceptions if registerConnectionManager or
// removeConnectionManager are called while we're iterating (rather
// than block/lock while this loop executes).
List connectionManagers = null;
synchronized (IdleConnectionReaper.class) {
connectionManagers = (List) IdleConnectionReaper.connectionManagers
.clone();
}
for (ClientConnectionManager connectionManager : connectionManagers) {
// When we release connections, the connection manager leaves them
// open so they can be reused. We want to close out any idle
// connections so that they don't sit around in CLOSE_WAIT.
try {
connectionManager.closeIdleConnections(60, TimeUnit.SECONDS);
} catch (Exception t) {
log.warn("Unable to close idle connections", t);
}
}
} catch (Throwable t) {
log.debug("Reaper thread: ", t);
}
}
}
/**
* Shuts down the thread, allowing the class and instance to be collected.
*
* Since this is a daemon thread, its running will not prevent JVM shutdown.
* It will, however, prevent this class from being unloaded or garbage
* collected, in the context of a long-running application, until it is
* interrupted. This method will stop the thread's execution and clear its
* state. Any use of a service client will cause the thread to be restarted.
*
* @return true if an actual shutdown has been made; false otherwise.
*/
public static synchronized boolean shutdown() {
if (instance != null) {
instance.markShuttingDown();
instance.interrupt();
connectionManagers.clear();
instance = null;
return true;
}
return false;
}
/**
* For testing purposes. Returns the number of connection managers currently
* monitored by this reaper.
*/
static synchronized int size() {
return connectionManagers.size();
}
}