/*
* $HeadURL: http://svn.apache.org/repos/asf/jakarta/httpcomponents/httpclient/tags/4.0-alpha1/module-client/src/main/java/org/apache/http/impl/conn/ThreadSafeClientConnManager.java $
* $Revision: 534769 $
* $Date: 2007-05-03 11:51:22 +0200 (Thu, 03 May 2007) $
*
* ====================================================================
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.http.impl.conn;
import java.io.IOException;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.WeakHashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.ClientConnectionOperator;
import org.apache.http.conn.ConnectionPoolTimeoutException;
import org.apache.http.conn.HostConfiguration;
import org.apache.http.conn.HttpRoute;
import org.apache.http.conn.ManagedClientConnection;
import org.apache.http.conn.OperatedClientConnection;
import org.apache.http.conn.SchemeRegistry;
import org.apache.http.conn.params.HttpConnectionManagerParams;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;
/**
* Manages a pool of {@link OperatedClientConnection client connections}.
* <p>
* This class is derived from <code>MultiThreadedHttpConnectionManager</code>
* in HttpClient 3. See there for original authors.
* </p>
*
* @author <a href="mailto:rolandw at apache.org">Roland Weber</a>
* @author <a href="mailto:becke@u.washington.edu">Michael Becke</a>
*
*
* <!-- empty lines to avoid svn diff problems -->
* @version $Revision: 534769 $ $Date: 2007-05-03 11:51:22 +0200 (Thu, 03 May 2007) $
*
* @since 4.0
*/
public class ThreadSafeClientConnManager
implements ClientConnectionManager {
private static final Log LOG =
LogFactory.getLog(ThreadSafeClientConnManager.class);
/**
* A mapping from Reference to ConnectionSource.
* Used to reclaim resources when connections are lost
* to the garbage collector.
*/
private static final Map REFERENCE_TO_CONNECTION_SOURCE = new HashMap();
/**
* The reference queue used to track when connections are lost to the
* garbage collector
*/
private static final ReferenceQueue REFERENCE_QUEUE = new ReferenceQueue();
/**
* The thread responsible for handling lost connections.
*/
private static ReferenceQueueThread REFERENCE_QUEUE_THREAD;
/**
* Holds references to all active instances of this class.
*/
private static WeakHashMap ALL_CONNECTION_MANAGERS = new WeakHashMap();
/** The schemes supported by this connection manager. */
protected SchemeRegistry schemeRegistry;
/** The parameters of this connection manager. */
private HttpParams params = new BasicHttpParams();
/** The pool of connections being managed. */
private ConnectionPool connectionPool;
/** The operator for opening and updating connections. */
private ClientConnectionOperator connOperator;
/** Indicates whether this connection manager is shut down. */
private volatile boolean isShutDown;
/**
* Creates a new thread safe connection manager.
*
* @param params the parameters for this manager
* @param schreg the scheme registry, or
* <code>null</code> for the default registry
*/
public ThreadSafeClientConnManager(HttpParams params,
SchemeRegistry schreg) {
if (params == null) {
throw new IllegalArgumentException("Parameters must not be null.");
}
this.params = params;
this.schemeRegistry = schreg;
this.connectionPool = new ConnectionPool();
this.connOperator = createConnectionOperator(schreg);
this.isShutDown = false;
synchronized(ALL_CONNECTION_MANAGERS) {
ALL_CONNECTION_MANAGERS.put(this, null);
}
} // <constructor>
public SchemeRegistry getSchemeRegistry() {
return this.schemeRegistry;
}
// non-javadoc, see interface ClientConnectionManager
public ManagedClientConnection getConnection(HttpRoute route) {
while (true) {
try {
return getConnection(route, 0);
} catch (ConnectionPoolTimeoutException e) {
// we'll go ahead and log this, but it should never happen.
// Exceptions are only thrown when the timeout occurs and
// since we have no timeout, it doesn't happen.
LOG.debug(
"Unexpected exception while waiting for connection",
e
);
}
}
}
// non-javadoc, see interface ClientConnectionManager
public ManagedClientConnection getConnection(HttpRoute route,
long timeout)
throws ConnectionPoolTimeoutException {
if (route == null) {
throw new IllegalArgumentException("Route may not be null.");
}
if (LOG.isDebugEnabled()) {
LOG.debug("ThreadSafeClientConnManager.getConnection: "
+ route + ", timeout = " + timeout);
}
final TrackingPoolEntry entry =
doGetConnection(route.toHostConfig(), timeout);
return new HttpConnectionAdapter(entry);
}
/**
* Obtains a connection within the given timeout.
*
* @param route the route for which to get the connection
* @param timeout the timeout, or 0 for no timeout
*
* @return the pool entry for the connection
*
* @throws ConnectionPoolTimeoutException if the timeout expired
*/
private TrackingPoolEntry doGetConnection(HostConfiguration route,
long timeout)
throws ConnectionPoolTimeoutException {
TrackingPoolEntry entry = null;
int maxHostConnections = HttpConnectionManagerParams
.getMaxConnectionsPerHost(this.params, route);
int maxTotalConnections = HttpConnectionManagerParams
.getMaxTotalConnections(this.params);
synchronized (connectionPool) {
// we used to clone the hostconfig here, but it is now immutable:
//route = new HostConfiguration(route);
HostConnectionPool hostPool = connectionPool.getHostPool(route);
WaitingThread waitingThread = null;
boolean useTimeout = (timeout > 0);
long timeToWait = timeout;
long startWait = 0;
long endWait = 0;
while (entry == null) {
if (isShutDown) {
throw new IllegalStateException
("Connection manager has been shut down.");
}
// happen to have a free connection with the right specs
//
if (hostPool.freeConnections.size() > 0) {
entry = connectionPool.getFreeConnection(route);
// have room to make more
//
} else if ((hostPool.numConnections < maxHostConnections)
&& (connectionPool.numConnections < maxTotalConnections)) {
entry = createPoolEntry(route);
// have room to add host connection, and there is at least one
// free connection that can be liberated to make overall room
//
} else if ((hostPool.numConnections < maxHostConnections)
&& (connectionPool.freeConnections.size() > 0)) {
connectionPool.deleteLeastUsedConnection();
entry = createPoolEntry(route);
// otherwise, we have to wait for one of the above conditions
// to become true
//
} else {
// TODO: keep track of which routes have waiting
// threads, so they avoid being sacrificed before necessary
try {
if (useTimeout && timeToWait <= 0) {
throw new ConnectionPoolTimeoutException("Timeout waiting for connection");
}
if (LOG.isDebugEnabled()) {
LOG.debug("Unable to get a connection, waiting..., hostConfig=" + route);
}
if (waitingThread == null) {
waitingThread = new WaitingThread();
waitingThread.hostConnectionPool = hostPool;
waitingThread.thread = Thread.currentThread();
} else {
waitingThread.interruptedByConnectionPool = false;
}
if (useTimeout) {
startWait = System.currentTimeMillis();
}
hostPool.waitingThreads.addLast(waitingThread);
connectionPool.waitingThreads.addLast(waitingThread);
connectionPool.wait(timeToWait);
} catch (InterruptedException e) {
if (!waitingThread.interruptedByConnectionPool) {
LOG.debug("Interrupted while waiting for connection", e);
throw new IllegalThreadStateException(
"Interrupted while waiting in ThreadSafeClientConnManager");
}
// Else, do nothing, we were interrupted by the
// connection pool and should now have a connection
// waiting for us. Continue in the loop and get it.
// Or else we are shutting down, which is also
// detected in the loop.
} finally {
if (!waitingThread.interruptedByConnectionPool) {
// Either we timed out, experienced a
// "spurious wakeup", or were interrupted by an
// external thread. Regardless we need to
// cleanup for ourselves in the wait queue.
hostPool.waitingThreads.remove(waitingThread);
connectionPool.waitingThreads.remove(waitingThread);
}
if (useTimeout) {
endWait = System.currentTimeMillis();
timeToWait -= (endWait - startWait);
}
}
}
}
}
return entry;
} // doGetConnection
/**
* Creates a connection to be managed, along with a pool entry.
*
* @param route the route for which to create the connection
*
* @return the pool entry for the new connection
*/
private TrackingPoolEntry createPoolEntry(HostConfiguration route) {
OperatedClientConnection occ = connOperator.createConnection();
return connectionPool.createEntry(route, occ);
}
/**
* Hook for creating the connection operator.
* It is called by the constructor.
* Derived classes can override this method to change the
* instantiation of the operator.
* The default implementation here instantiates
* {@link DefaultClientConnectionOperator DefaultClientConnectionOperator}.
*
* @param schreg the scheme registry to use, or <code>null</code>
*
* @return the connection operator to use
*/
protected ClientConnectionOperator
createConnectionOperator(SchemeRegistry schreg) {
return new DefaultClientConnectionOperator(schreg);
}
/**
* Releases an allocated connection.
* If another thread is blocked in getConnection() that could use this
* connection, it will be woken up.
*
* @param conn the connection to make available.
*/
public void releaseConnection(ManagedClientConnection conn) {
if (!(conn instanceof HttpConnectionAdapter)) {
throw new IllegalArgumentException
("Connection class mismatch, " +
"connection not obtained from this manager.");
}
HttpConnectionAdapter hca = (HttpConnectionAdapter) conn;
if ((hca.poolEntry != null) &&
//@@@ (hca.poolEntry.manager != this) &&
(hca.connManager != this)) {
throw new IllegalArgumentException
("Connection not obtained from this manager.");
}
try {
// make sure that the response has been read completely
if (hca.isOpen() && !hca.isMarkedReusable()) {
if (LOG.isDebugEnabled()) {
LOG.debug
("Released connection open but not marked reusable.");
}
// In MTHCM, method releasePoolEntry below would call
// SimpleHttpConnectionManager.finishLastResponse(conn);
// Consuming the response is handled outside in 4.0.
// make sure this connection will not be re-used
// Shut down rather than close, we might have gotten here
// because of a shutdown trigger.
// Shutdown of the adapter also clears the tracked route.
hca.shutdown();
}
} catch (IOException iox) {
//@@@ log as warning? let pass?
if (LOG.isDebugEnabled())
LOG.debug("Exception shutting down released connection.",
iox);
} finally {
TrackingPoolEntry entry = (TrackingPoolEntry) hca.poolEntry;
hca.detach();
releasePoolEntry(entry);
}
}
/**
* Releases an allocated connection by the pool entry.
*
* @param entry the pool entry for the connection to release,
* or <code>null</code>
*/
private void releasePoolEntry(TrackingPoolEntry entry) {
if (entry == null)
return;
connectionPool.freeConnection(entry);
}
// ######################################################################
// ######################################################################
// ########## old code below ##########
// ######################################################################
// ######################################################################
/**
* Shuts down and cleans up resources used by all instances of
* ThreadSafeClientConnManager. All static resources are released, all threads are
* stopped, and {@link #shutdown()} is called on all live instances of
* ThreadSafeClientConnManager.
*
* @see #shutdown()
*/
public static void shutdownAll() {
synchronized (REFERENCE_TO_CONNECTION_SOURCE) {
// shutdown all connection managers
synchronized (ALL_CONNECTION_MANAGERS) {
// Don't use an iterator here. Iterators on WeakHashMap can
// get ConcurrentModificationException on garbage collection.
ThreadSafeClientConnManager[]
connManagers = (ThreadSafeClientConnManager[])
ALL_CONNECTION_MANAGERS.keySet().toArray(
new ThreadSafeClientConnManager
[ALL_CONNECTION_MANAGERS.size()]
);
// The map may shrink after size() is called, or some entry
// may get GCed while the array is built, so expect null.
for (int i=0; i<connManagers.length; i++) {
if (connManagers[i] != null)
connManagers[i].shutdown();
}
}
// shutdown static resources
if (REFERENCE_QUEUE_THREAD != null) {
REFERENCE_QUEUE_THREAD.shutdown();
REFERENCE_QUEUE_THREAD = null;
}
REFERENCE_TO_CONNECTION_SOURCE.clear();
}
}
/**
* Stores a weak reference to the given pool entry.
* Along with the reference, the route and connection pool are stored.
* These values will be used to reclaim resources if the connection
* is lost to the garbage collector. This method should be called
* before a connection is handed out by the connection manager.
* <br/>
* A static reference to the connection manager will also be stored.
* To ensure that the connection manager can be GCed,
* {@link #removeReferenceToConnection removeReferenceToConnection}
* should be called for all pool entry to which the manager
* keeps a strong reference.
*
* @param connection the pool entry to store a reference for
* @param hostConfiguration the connection's route
* @param connectionPool the connection pool that created the entry
*
* @see #removeReferenceToConnection
*/
private static void storeReferenceToConnection(
TrackingPoolEntry connection,
HostConfiguration hostConfiguration,
ConnectionPool connectionPool
) {
ConnectionSource source = new ConnectionSource();
source.connectionPool = connectionPool;
source.hostConfiguration = hostConfiguration;
synchronized (REFERENCE_TO_CONNECTION_SOURCE) {
// start the reference queue thread if needed
if (REFERENCE_QUEUE_THREAD == null) {
REFERENCE_QUEUE_THREAD = new ReferenceQueueThread();
REFERENCE_QUEUE_THREAD.start();
}
REFERENCE_TO_CONNECTION_SOURCE.put(
connection.reference,
source
);
}
}
/**
* Removes the reference being stored for the given connection.
* This method should be called when the manager again has a
* direct reference to the pool entry.
*
* @param entry the pool entry for which to remove the reference
*
* @see #storeReferenceToConnection
*/
private static void removeReferenceToConnection(TrackingPoolEntry entry) {
synchronized (REFERENCE_TO_CONNECTION_SOURCE) {
REFERENCE_TO_CONNECTION_SOURCE.remove(entry.reference);
}
}
/**
* Closes and releases all connections currently checked out of the
* given connection pool.
* @param connectionPool the pool for which to shutdown the connections
*/
private static
void shutdownCheckedOutConnections(ConnectionPool connectionPool) {
// keep a list of the connections to be closed
ArrayList connectionsToClose = new ArrayList();
synchronized (REFERENCE_TO_CONNECTION_SOURCE) {
Iterator referenceIter = REFERENCE_TO_CONNECTION_SOURCE.keySet().iterator();
while (referenceIter.hasNext()) {
Reference ref = (Reference) referenceIter.next();
ConnectionSource source =
(ConnectionSource) REFERENCE_TO_CONNECTION_SOURCE.get(ref);
if (source.connectionPool == connectionPool) {
referenceIter.remove();
Object entry = ref.get(); // TrackingPoolEntry
if (entry != null) {
connectionsToClose.add(entry);
}
}
}
}
// close and release the connections outside of the synchronized block
// to avoid holding the lock for too long
for (Iterator i = connectionsToClose.iterator(); i.hasNext();) {
TrackingPoolEntry entry = (TrackingPoolEntry) i.next();
closeConnection(entry.connection);
// remove the reference to the connection manager. this ensures
// that the we don't accidentally end up here again
//@@@ connection.setHttpConnectionManager(null);
entry.manager.releasePoolEntry(entry);
}
}
// ------------------------------------------------------- Instance Methods
/**
* Shuts down the connection manager and releases all resources. All connections associated
* with this class will be closed and released.
*
* <p>The connection manager can no longer be used once shutdown.
*
* <p>Calling this method more than once will have no effect.
*/
public synchronized void shutdown() {
synchronized (connectionPool) {
if (!isShutDown) {
isShutDown = true;
connectionPool.shutdown();
}
}
}
/**
* Gets the total number of pooled connections for the given host configuration. This
* is the total number of connections that have been created and are still in use
* by this connection manager for the host configuration. This value will
* not exceed the maximum number of connections per host.
*
* @param hostConfiguration The host configuration
* @return The total number of pooled connections
*/
public int getConnectionsInPool(HostConfiguration hostConfiguration) {
synchronized (connectionPool) {
HostConnectionPool hostPool = connectionPool.getHostPool(hostConfiguration);
return hostPool.numConnections;
}
}
/**
* Gets the total number of pooled connections. This is the total number of
* connections that have been created and are still in use by this connection
* manager. This value will not exceed the maximum number of connections
* in total.
*
* @return the total number of pooled connections
*/
public int getConnectionsInPool() {
synchronized (connectionPool) {
return connectionPool.numConnections;
}
}
/**
* Deletes all free connections that are closed.
* Only connections currently owned by the connection
* manager are processed.
*/
private void deleteClosedConnections() {
connectionPool.deleteClosedConnections();
}
/**
* Deletes all free connections that are idle or closed.
*/
public void closeIdleConnections(long idleTimeout) {
connectionPool.closeIdleConnections(idleTimeout);
deleteClosedConnections();
}
/**
* Returns {@link HttpParams parameters} associated
* with this connection manager.
*/
public HttpParams getParams() {
return this.params;
}
/**
* Assigns {@link HttpParams parameters} for this
* connection manager.
*
* @see HttpConnectionManagerParams
*/
public void setParams(final HttpParams params) {
if (params == null) {
throw new IllegalArgumentException("Parameters may not be null");
}
this.params = params;
}
/**
* A structured pool of connections.
* This class keeps track of all connections, using overall lists
* as well as per-route lists.
*/
private class ConnectionPool {
/** The list of free connections */
private LinkedList freeConnections = new LinkedList();
/** The list of WaitingThreads waiting for a connection */
private LinkedList waitingThreads = new LinkedList();
/**
* Map where keys are {@link HostConfiguration}s and values are
* {@link HostConnectionPool}s
*/
private final Map mapHosts = new HashMap();
private IdleConnectionHandler idleConnectionHandler = new IdleConnectionHandler();
/** The number of created connections */
private int numConnections = 0;
/**
* Cleans up all connection pool resources.
*/
public synchronized void shutdown() {
// close all free connections
Iterator iter = freeConnections.iterator();
while (iter.hasNext()) {
TrackingPoolEntry entry = (TrackingPoolEntry) iter.next();
iter.remove();
closeConnection(entry.connection);
}
// close all connections that have been checked out
shutdownCheckedOutConnections(this);
// interrupt all waiting threads
iter = waitingThreads.iterator();
while (iter.hasNext()) {
WaitingThread waiter = (WaitingThread) iter.next();
iter.remove();
waiter.interruptedByConnectionPool = true;
waiter.thread.interrupt();
}
// clear out map hosts
mapHosts.clear();
// remove all references to connections
idleConnectionHandler.removeAll();
}
/**
* Creates a new pool entry for an operated connection.
* This method assumes that the new connection will be handed
* out immediately.
*
* @param route the route associated with the new entry
* @param conn the underlying connection for the new entry
*
* @return the new pool entry
*/
protected synchronized
TrackingPoolEntry createEntry(HostConfiguration route,
OperatedClientConnection conn) {
HostConnectionPool hostPool = getHostPool(route);
if (LOG.isDebugEnabled()) {
LOG.debug("Allocating new connection, hostConfiguration=" + route);
}
TrackingPoolEntry entry = new TrackingPoolEntry(conn);
entry.plannedRoute = route;
numConnections++;
hostPool.numConnections++;
// store a reference to this entry so that it can be cleaned up
// in the event it is not correctly released
storeReferenceToConnection(entry, route, this);
return entry;
}
/**
* Handles cleaning up for a lost connection with the given config.
* Decrements any connection counts and notifies waiting threads,
* if appropriate.
*
* @param config the host configuration of the connection that was lost
*/
public synchronized
void handleLostConnection(HostConfiguration config) {
HostConnectionPool hostPool = getHostPool(config);
hostPool.numConnections--;
if (hostPool.numConnections < 1)
mapHosts.remove(config);
numConnections--;
notifyWaitingThread(config);
}
/**
* Get the pool (list) of connections available for the given route.
*
* @param route the configuraton for the connection pool
* @return a pool (list) of connections available for the given route
*/
public synchronized
HostConnectionPool getHostPool(HostConfiguration route) {
// Look for a list of connections for the given config
HostConnectionPool listConnections =
(HostConnectionPool) mapHosts.get(route);
if (listConnections == null) {
// First time for this config
listConnections = new HostConnectionPool();
listConnections.hostConfiguration = route;
mapHosts.put(route, listConnections);
}
return listConnections;
}
/**
* If available, get a free connection for this host
*
* @param hostConfiguration the configuraton for the connection pool
* @return an available connection for the given config
*/
public synchronized TrackingPoolEntry getFreeConnection(HostConfiguration hostConfiguration) {
TrackingPoolEntry entry = null;
HostConnectionPool hostPool = getHostPool(hostConfiguration);
if (hostPool.freeConnections.size() > 0) {
entry = (TrackingPoolEntry) hostPool.freeConnections.removeLast();
freeConnections.remove(entry);
// store a reference to this entry so that it can be cleaned up
// in the event it is not correctly released
storeReferenceToConnection(entry, hostConfiguration, this);
if (LOG.isDebugEnabled()) {
LOG.debug("Getting free connection, hostConfig=" + hostConfiguration);
}
// remove the connection from the timeout handler
idleConnectionHandler.remove(entry.connection);
} else if (LOG.isDebugEnabled()) {
LOG.debug("There were no free connections to get, hostConfig="
+ hostConfiguration);
}
return entry;
}
/**
* Deletes all closed connections.
*/
public synchronized void deleteClosedConnections() {
Iterator iter = freeConnections.iterator();
while (iter.hasNext()) {
TrackingPoolEntry entry =
(TrackingPoolEntry) iter.next();
if (!entry.connection.isOpen()) {
iter.remove();
deleteConnection(entry);
}
}
}
/**
* Closes idle connections.
* @param idleTimeout
*/
public synchronized void closeIdleConnections(long idleTimeout) {
idleConnectionHandler.closeIdleConnections(idleTimeout);
}
/**
* Deletes the given connection.
* This will remove all reference to the connection
* so that it can be GCed.
*
* <p><b>Note:</b> Does not remove the connection from the
* freeConnections list. It
* is assumed that the caller has already handled this step.</p>
*
* @param entry the pool entry for the connection to delete
*/
private synchronized void deleteConnection(TrackingPoolEntry entry) {
HostConfiguration route = entry.plannedRoute;
if (LOG.isDebugEnabled()) {
LOG.debug("Reclaiming connection, hostConfig=" + route);
}
closeConnection(entry.connection);
HostConnectionPool hostPool = getHostPool(route);
hostPool.freeConnections.remove(entry);
hostPool.numConnections--;
numConnections--;
if (hostPool.numConnections < 1)
mapHosts.remove(route);
// remove the connection from the timeout handler
idleConnectionHandler.remove(entry.connection);
}
/**
* Close and delete an old, unused connection to make room for a new one.
*/
public synchronized void deleteLeastUsedConnection() {
TrackingPoolEntry entry =
(TrackingPoolEntry) freeConnections.removeFirst();
if (entry != null) {
deleteConnection(entry);
} else if (LOG.isDebugEnabled()) {
LOG.debug("Attempted to reclaim an unused connection but there were none.");
}
}
/**
* Notifies a waiting thread that a connection for the given configuration is
* available.
* @param configuration the host config to use for notifying
* @see #notifyWaitingThread(HostConnectionPool)
*/
public synchronized void notifyWaitingThread(HostConfiguration configuration) {
notifyWaitingThread(getHostPool(configuration));
}
/**
* Notifies a waiting thread that a connection for the given configuration is
* available. This will wake a thread waiting in this host pool or if there is not
* one a thread in the connection pool will be notified.
*
* @param hostPool the host pool to use for notifying
*/
public synchronized void notifyWaitingThread(HostConnectionPool hostPool) {
// find the thread we are going to notify, we want to ensure that each
// waiting thread is only interrupted once so we will remove it from
// all wait queues before interrupting it
WaitingThread waitingThread = null;
if (hostPool.waitingThreads.size() > 0) {
if (LOG.isDebugEnabled()) {
LOG.debug("Notifying thread waiting on host pool, hostConfig="
+ hostPool.hostConfiguration);
}
waitingThread = (WaitingThread) hostPool.waitingThreads.removeFirst();
waitingThreads.remove(waitingThread);
} else if (waitingThreads.size() > 0) {
if (LOG.isDebugEnabled()) {
LOG.debug("No-one waiting on host pool, notifying next waiting thread.");
}
waitingThread = (WaitingThread) waitingThreads.removeFirst();
waitingThread.hostConnectionPool.waitingThreads.remove(waitingThread);
} else if (LOG.isDebugEnabled()) {
LOG.debug("Notifying no-one, there are no waiting threads");
}
if (waitingThread != null) {
waitingThread.interruptedByConnectionPool = true;
waitingThread.thread.interrupt();
}
}
/**
* Marks the given connection as free.
*
* @param entry the pool entry for the connection
*/
private void freeConnection(TrackingPoolEntry entry) {
HostConfiguration route = entry.plannedRoute;
if (LOG.isDebugEnabled()) {
LOG.debug("Freeing connection, hostConfig=" + route);
}
synchronized (this) {
if (isShutDown) {
// the connection manager has been shutdown, release the
// connection's resources and get out of here
closeConnection(entry.connection);
return;
}
HostConnectionPool hostPool = getHostPool(route);
// Put the connection back in the available list
// and notify a waiter
hostPool.freeConnections.add(entry);
if (hostPool.numConnections == 0) {
// for some reason this connection pool didn't already exist
LOG.error("Host connection pool not found, hostConfig="
+ route);
hostPool.numConnections = 1;
}
freeConnections.add(entry);
// We can remove the reference to this connection as we have
// control over it again. This also ensures that the connection
// manager can be GCed.
removeReferenceToConnection(entry);
if (numConnections == 0) {
// for some reason this connection pool didn't already exist
LOG.error("Host connection pool not found, hostConfig="
+ route);
numConnections = 1;
}
// register the connection with the timeout handler
idleConnectionHandler.add(entry.connection);
notifyWaitingThread(hostPool);
}
}
}
private static void closeConnection(final OperatedClientConnection conn) {
try {
conn.close();
} catch (IOException ex) {
LOG.debug("I/O error closing connection", ex);
}
}
/**
* A simple struct-like class to combine the objects needed to release
* a connection's resources when claimed by the garbage collector.
*/
private static class ConnectionSource {
/** The connection pool that created the connection */
public ConnectionPool connectionPool;
/** The connection's host configuration */
public HostConfiguration hostConfiguration;
}
/**
* A simple struct-like class to combine the connection list and the count
* of created connections.
*/
private static class HostConnectionPool {
/** The hostConfig this pool is for */
public HostConfiguration hostConfiguration;
/** The list of free connections */
public LinkedList freeConnections = new LinkedList();
/** The list of WaitingThreads for this host */
public LinkedList waitingThreads = new LinkedList();
/** The number of created connections */
public int numConnections = 0;
}
/**
* A simple struct-like class to combine the waiting thread and the connection
* pool it is waiting on.
*/
private static class WaitingThread {
/** The thread that is waiting for a connection */
public Thread thread;
/** The connection pool the thread is waiting for */
public HostConnectionPool hostConnectionPool;
/**
* Indicates the source of an interruption.
* Set to <code>true</code> inside
* {@link ConnectionPool#notifyWaitingThread(HostConnectionPool)}
* and {@link ThreadSafeClientConnManager#shutdown shutdown()}
* before the thread is interrupted.
* If not set, the thread was interrupted from the outside.
*/
public boolean interruptedByConnectionPool = false;
}
/**
* A thread for listening for HttpConnections reclaimed by the garbage
* collector.
*/
private static class ReferenceQueueThread extends Thread {
private volatile boolean isShutDown = false;
/**
* Create an instance and make this a daemon thread.
*/
public ReferenceQueueThread() {
setDaemon(true);
setName("ThreadSafeClientConnManager cleanup");
}
public void shutdown() {
this.isShutDown = true;
this.interrupt();
}
/**
* Handles cleaning up for the given connection reference.
*
* @param ref the reference to clean up
*/
private void handleReference(Reference ref) {
ConnectionSource source = null;
synchronized (REFERENCE_TO_CONNECTION_SOURCE) {
source = (ConnectionSource) REFERENCE_TO_CONNECTION_SOURCE.remove(ref);
}
// only clean up for this reference if it is still associated with
// a ConnectionSource
if (source != null) {
if (LOG.isDebugEnabled()) {
LOG.debug(
"Connection reclaimed by garbage collector, hostConfig="
+ source.hostConfiguration);
}
source.connectionPool.handleLostConnection(source.hostConfiguration);
}
}
/**
* Start execution.
*/
public void run() {
while (!isShutDown) {
try {
// remove the next reference and process it
Reference ref = REFERENCE_QUEUE.remove();
if (ref != null) {
handleReference(ref);
}
} catch (InterruptedException e) {
LOG.debug("ReferenceQueueThread interrupted", e);
}
}
}
}
/**
* A pool entry representing a connection that tracks it's route.
* For historical reasons, these entries are sometimes referred to
* as <i>connections</i> throughout the code.
*/
private class TrackingPoolEntry extends AbstractPoolEntry {
/** The route for which this entry gets allocated. */
private HostConfiguration plannedRoute;
/** The connection manager. */
private ThreadSafeClientConnManager manager;
/**
* A weak reference used to detect GCed pool entries.
* Of course, pool entries can only be GCed when they are allocated
* and therefore not referenced with a hard link in the manager.
*/
private WeakReference reference;
/**
* Creates a new pool entry.
*
* @param occ the underlying connection for this entry
*/
private TrackingPoolEntry(OperatedClientConnection occ) {
super(occ);
//@@@ pass planned route to the constructor?
//@@@ or update when the adapter is created?
this.manager = ThreadSafeClientConnManager.this;
this.reference = new WeakReference(this, REFERENCE_QUEUE);
}
// non-javadoc, see base AbstractPoolEntry
protected ClientConnectionOperator getOperator() {
return ThreadSafeClientConnManager.this.connOperator;
}
} // class TrackingPoolEntry
/**
* A connection wrapper and callback handler.
* All connections given out by the manager are wrappers which
* can be {@link #detach detach}ed to prevent further use on release.
*/
private class HttpConnectionAdapter extends AbstractPooledConnAdapter {
/**
* Creates a new adapter.
*
* @param entry the pool entry for the connection being wrapped
*/
protected HttpConnectionAdapter(TrackingPoolEntry entry) {
super(ThreadSafeClientConnManager.this, entry);
super.markedReusable = true;
}
}
} // class ThreadSafeClientConnManager