HTTPCLIENT-636: tearing apart TSCCM

git-svn-id: https://svn.apache.org/repos/asf/jakarta/httpcomponents/httpclient/trunk@558813 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Roland Weber 2007-07-23 17:55:59 +00:00
parent 86dea0d47c
commit 82b3d0aff5
2 changed files with 341 additions and 263 deletions

View File

@ -0,0 +1,304 @@
/*
* $HeadURL$
* $Revision$
* $Date$
*
* ====================================================================
*
* 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.tsccm;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.Map;
import java.util.HashMap;
import java.util.WeakHashMap;
import java.util.Iterator;
import java.util.ArrayList;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.conn.HttpRoute;
/**
* Some static maps and associated methods.
* These are currently still used, but should be removed a.s.a.p.
*/
final /*default*/ class BadStaticMaps {
private final static Log LOG = LogFactory.getLog(BadStaticMaps.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
*/
static /*default*/ 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.
*/
static /*default*/ WeakHashMap ALL_CONNECTION_MANAGERS = new WeakHashMap();
/** Disabled default constructor. */
private BadStaticMaps() {
// no body
}
/**
* 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()
*/
static /*default*/ 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 entry the pool entry to store a reference for
* @param route the connection's planned route
* @param connectionPool the connection pool that created the entry
*
* @see #removeReferenceToConnection
*/
static /*default*/ void storeReferenceToConnection(
ThreadSafeClientConnManager.TrackingPoolEntry entry,
HttpRoute route,
ThreadSafeClientConnManager.ConnectionPool connectionPool
) {
ConnectionSource source = new ConnectionSource();
source.connectionPool = connectionPool;
source.route = route;
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(entry.getWeakRef(), 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
*/
static /*default*/ void removeReferenceToConnection(ThreadSafeClientConnManager.TrackingPoolEntry entry) {
synchronized (REFERENCE_TO_CONNECTION_SOURCE) {
REFERENCE_TO_CONNECTION_SOURCE.remove(entry.getWeakRef());
}
}
/**
* Closes and releases all connections currently checked out of the
* given connection pool.
* @param connectionPool the pool for which to shutdown the connections
*/
static /*default*/
void shutdownCheckedOutConnections(ThreadSafeClientConnManager.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();) {
ThreadSafeClientConnManager.TrackingPoolEntry entry =
(ThreadSafeClientConnManager.TrackingPoolEntry) i.next();
ThreadSafeClientConnManager.closeConnection(entry.getConnection());
entry.getManager().releasePoolEntry(entry);
}
}
/**
* 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 ThreadSafeClientConnManager.ConnectionPool connectionPool;
/** The connection's planned route. */
public HttpRoute route;
}
/**
* 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, route="
+ source.route);
}
source.connectionPool.handleLostConnection(source.route);
}
}
/**
* 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);
}
}
}
} // class ReferenceQueueThread
}

View File

@ -80,30 +80,6 @@ public class ThreadSafeClientConnManager
private final static 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;
@ -142,8 +118,8 @@ public class ThreadSafeClientConnManager
this.connOperator = createConnectionOperator(schreg);
this.isShutDown = false;
synchronized(ALL_CONNECTION_MANAGERS) {
ALL_CONNECTION_MANAGERS.put(this, null);
synchronized(BadStaticMaps.ALL_CONNECTION_MANAGERS) {
BadStaticMaps.ALL_CONNECTION_MANAGERS.put(this, null);
}
} // <constructor>
@ -409,7 +385,8 @@ public class ThreadSafeClientConnManager
* @param entry the pool entry for the connection to release,
* or <code>null</code>
*/
private void releasePoolEntry(TrackingPoolEntry entry) {
//@@@ temporary default visibility, for BadStaticMaps
void /*default*/ releasePoolEntry(TrackingPoolEntry entry) {
if (entry == null)
return;
@ -419,6 +396,16 @@ public class ThreadSafeClientConnManager
/**
* Shuts down all instances of this class.
*
* @deprecated no replacement
*/
public static void shutdownAll() {
BadStaticMaps.shutdownAll();
}
// ######################################################################
// ######################################################################
// ########## old code below ##########
@ -426,151 +413,6 @@ public class ThreadSafeClientConnManager
// ######################################################################
/**
* 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 route the connection's planned route
* @param connectionPool the connection pool that created the entry
*
* @see #removeReferenceToConnection
*/
private static void storeReferenceToConnection(
TrackingPoolEntry connection,
HttpRoute route,
ConnectionPool connectionPool
) {
ConnectionSource source = new ConnectionSource();
source.connectionPool = connectionPool;
source.route = route;
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.getConnection());
entry.manager.releasePoolEntry(entry);
}
}
// ------------------------------------------------------- Instance Methods
/**
* Shuts down the connection manager and releases all resources.
* All connections associated with this manager will be closed
@ -664,7 +506,8 @@ public class ThreadSafeClientConnManager
* This class keeps track of all connections, using overall lists
* as well as per-route lists.
*/
private class ConnectionPool {
//@@@ temporary package visibility, for BadStaticMaps
class /*default*/ ConnectionPool {
/** The list of free connections */
private LinkedList freeConnections = new LinkedList();
@ -682,7 +525,7 @@ public class ThreadSafeClientConnManager
/** A reference queue to track loss of pool entries to GC. */
//@@@ this should be a pool-specific reference queue
private ReferenceQueue refQueue = REFERENCE_QUEUE; //@@@
private ReferenceQueue refQueue = BadStaticMaps.REFERENCE_QUEUE; //@@@
/** A worker (thread) to track loss of pool entries to GC. */
private LostConnWorker refWorker;
@ -745,7 +588,7 @@ public class ThreadSafeClientConnManager
}
}
//@@@ while the static map exists, call there to clean it up
shutdownCheckedOutConnections(this); //@@@
BadStaticMaps.shutdownCheckedOutConnections(this); //@@@
// interrupt all waiting threads
iter = waitingThreads.iterator();
@ -788,7 +631,7 @@ public class ThreadSafeClientConnManager
// 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); //@@@
BadStaticMaps.storeReferenceToConnection(entry, route, this); //@@@
issuedConnections.add(entry.reference);
return entry;
@ -826,7 +669,8 @@ public class ThreadSafeClientConnManager
*
* @param config the route of the connection that was lost
*/
private synchronized
//@@@ temporary default visibility, for BadStaticMaps
synchronized /*default*/
void handleLostConnection(HttpRoute route) {
RouteConnPool routePool = getRoutePool(route);
@ -881,7 +725,7 @@ public class ThreadSafeClientConnManager
// 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); //@@@
BadStaticMaps.storeReferenceToConnection(entry, route, this); //@@@
issuedConnections.add(entry.reference);
if (LOG.isDebugEnabled()) {
LOG.debug("Getting free connection, route=" + route);
@ -1058,8 +902,8 @@ public class ThreadSafeClientConnManager
// 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); //@@@
issuedConnections.remove(entry.reference); //@@@ move up
BadStaticMaps.removeReferenceToConnection(entry); //@@@
issuedConnections.remove(entry.reference); //@@@ move above
if (numConnections == 0) {
// for some reason this pool didn't already exist
LOG.error("Master connection pool not found. " + route);
@ -1075,7 +919,7 @@ public class ThreadSafeClientConnManager
} // class ConnectionPool
private static void closeConnection(final OperatedClientConnection conn) {
static /*default*/ void closeConnection(final OperatedClientConnection conn) {
if (conn != null) {
try {
conn.close();
@ -1085,19 +929,6 @@ public class ThreadSafeClientConnManager
}
}
/**
* 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 planned route. */
public HttpRoute route;
}
/**
* A simple struct-like class to combine the connection list and the count
* of created connections.
@ -1140,72 +971,6 @@ public class ThreadSafeClientConnManager
}
/**
* 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, route="
+ source.route);
}
source.connectionPool.handleLostConnection(source.route);
}
}
/**
* 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);
}
}
}
} // class ReferenceQueueThread
/**
* Tracker for GCed connections.
* Can be started in a background thread.
@ -1329,7 +1094,8 @@ public class ThreadSafeClientConnManager
* For historical reasons, these entries are sometimes referred to
* as <i>connections</i> throughout the code.
*/
private static class TrackingPoolEntry extends AbstractPoolEntry {
//@@@ temporary default visibility, it's needed in BadStaticMaps
static /*default*/ class TrackingPoolEntry extends AbstractPoolEntry {
/** The connection manager. */
private ThreadSafeClientConnManager manager;
@ -1378,14 +1144,22 @@ public class ThreadSafeClientConnManager
}
private final OperatedClientConnection getConnection() {
protected final OperatedClientConnection getConnection() {
return super.connection;
}
private final HttpRoute getPlannedRoute() {
protected final HttpRoute getPlannedRoute() {
return super.plannedRoute;
}
protected final WeakReference getWeakRef() {
return this.reference;
}
protected final ThreadSafeClientConnManager getManager() {
return this.manager;
}
} // class TrackingPoolEntry