summaryrefslogtreecommitdiffstats
path: root/src/org/apache/http/impl/conn/tsccm
diff options
context:
space:
mode:
Diffstat (limited to 'src/org/apache/http/impl/conn/tsccm')
-rw-r--r--src/org/apache/http/impl/conn/tsccm/AbstractConnPool.java332
-rw-r--r--src/org/apache/http/impl/conn/tsccm/BasicPoolEntry.java88
-rw-r--r--src/org/apache/http/impl/conn/tsccm/BasicPoolEntryRef.java80
-rw-r--r--src/org/apache/http/impl/conn/tsccm/BasicPooledConnAdapter.java83
-rw-r--r--src/org/apache/http/impl/conn/tsccm/ConnPoolByRoute.java698
-rw-r--r--src/org/apache/http/impl/conn/tsccm/PoolEntryRequest.java68
-rw-r--r--src/org/apache/http/impl/conn/tsccm/RefQueueHandler.java48
-rw-r--r--src/org/apache/http/impl/conn/tsccm/RefQueueWorker.java139
-rw-r--r--src/org/apache/http/impl/conn/tsccm/RouteSpecificPool.java301
-rw-r--r--src/org/apache/http/impl/conn/tsccm/ThreadSafeClientConnManager.java282
-rw-r--r--src/org/apache/http/impl/conn/tsccm/WaitingThread.java197
-rw-r--r--src/org/apache/http/impl/conn/tsccm/WaitingThreadAborter.java62
-rw-r--r--src/org/apache/http/impl/conn/tsccm/doc-files/tsccm-structure.pngbin0 -> 4224 bytes
-rw-r--r--src/org/apache/http/impl/conn/tsccm/package.html205
14 files changed, 2583 insertions, 0 deletions
diff --git a/src/org/apache/http/impl/conn/tsccm/AbstractConnPool.java b/src/org/apache/http/impl/conn/tsccm/AbstractConnPool.java
new file mode 100644
index 0000000..2b37d72
--- /dev/null
+++ b/src/org/apache/http/impl/conn/tsccm/AbstractConnPool.java
@@ -0,0 +1,332 @@
+/*
+ * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/AbstractConnPool.java $
+ * $Revision: 673450 $
+ * $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 Jul 2008) $
+ *
+ * ====================================================================
+ *
+ * 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.io.IOException;
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.http.conn.ConnectionPoolTimeoutException;
+import org.apache.http.conn.OperatedClientConnection;
+import org.apache.http.conn.routing.HttpRoute;
+import org.apache.http.impl.conn.IdleConnectionHandler;
+
+
+/**
+ * An abstract connection pool.
+ * It is used by the {@link ThreadSafeClientConnManager}.
+ * The abstract pool includes a {@link #poolLock}, which is used to
+ * synchronize access to the internal pool datastructures.
+ * Don't use <code>synchronized</code> for that purpose!
+ */
+public abstract class AbstractConnPool implements RefQueueHandler {
+
+ private final Log log = LogFactory.getLog(getClass());
+
+ /**
+ * The global lock for this pool.
+ */
+ protected final Lock poolLock;
+
+
+ /**
+ * References to issued connections.
+ * Objects in this set are of class
+ * {@link BasicPoolEntryRef BasicPoolEntryRef},
+ * and point to the pool entry for the issued connection.
+ * GCed connections are detected by the missing pool entries.
+ */
+ protected Set<BasicPoolEntryRef> issuedConnections;
+
+ /** The handler for idle connections. */
+ protected IdleConnectionHandler idleConnHandler;
+
+ /** The current total number of connections. */
+ protected int numConnections;
+
+ /**
+ * A reference queue to track loss of pool entries to GC.
+ * The same queue is used to track loss of the connection manager,
+ * so we cannot specialize the type.
+ */
+ protected ReferenceQueue<Object> refQueue;
+
+ /** A worker (thread) to track loss of pool entries to GC. */
+ private RefQueueWorker refWorker;
+
+
+ /** Indicates whether this pool is shut down. */
+ protected volatile boolean isShutDown;
+
+ /**
+ * Creates a new connection pool.
+ */
+ protected AbstractConnPool() {
+ issuedConnections = new HashSet<BasicPoolEntryRef>();
+ idleConnHandler = new IdleConnectionHandler();
+
+ boolean fair = false; //@@@ check parameters to decide
+ poolLock = new ReentrantLock(fair);
+ }
+
+
+ /**
+ * Enables connection garbage collection (GC).
+ * This method must be called immediately after creating the
+ * connection pool. It is not possible to enable connection GC
+ * after pool entries have been created. Neither is it possible
+ * to disable connection GC.
+ *
+ * @throws IllegalStateException
+ * if connection GC is already enabled, or if it cannot be
+ * enabled because there already are pool entries
+ */
+ public void enableConnectionGC()
+ throws IllegalStateException {
+
+ if (refQueue != null) {
+ throw new IllegalStateException("Connection GC already enabled.");
+ }
+ poolLock.lock();
+ try {
+ if (numConnections > 0) { //@@@ is this check sufficient?
+ throw new IllegalStateException("Pool already in use.");
+ }
+ } finally {
+ poolLock.unlock();
+ }
+
+ refQueue = new ReferenceQueue<Object>();
+ refWorker = new RefQueueWorker(refQueue, this);
+ Thread t = new Thread(refWorker); //@@@ use a thread factory
+ t.setDaemon(true);
+ t.setName("RefQueueWorker@" + this);
+ t.start();
+ }
+
+
+ /**
+ * Obtains a pool entry with a connection within the given timeout.
+ *
+ * @param route the route for which to get the connection
+ * @param timeout the timeout, 0 or negative for no timeout
+ * @param tunit the unit for the <code>timeout</code>,
+ * may be <code>null</code> only if there is no timeout
+ *
+ * @return pool entry holding a connection for the route
+ *
+ * @throws ConnectionPoolTimeoutException
+ * if the timeout expired
+ * @throws InterruptedException
+ * if the calling thread was interrupted
+ */
+ public final
+ BasicPoolEntry getEntry(
+ HttpRoute route,
+ Object state,
+ long timeout,
+ TimeUnit tunit)
+ throws ConnectionPoolTimeoutException, InterruptedException {
+ return requestPoolEntry(route, state).getPoolEntry(timeout, tunit);
+ }
+
+ /**
+ * Returns a new {@link PoolEntryRequest}, from which a {@link BasicPoolEntry}
+ * can be obtained, or the request can be aborted.
+ */
+ public abstract PoolEntryRequest requestPoolEntry(HttpRoute route, Object state);
+
+
+ /**
+ * Returns an entry into the pool.
+ * The connection of the entry is expected to be in a suitable state,
+ * either open and re-usable, or closed. The pool will not make any
+ * attempt to determine whether it can be re-used or not.
+ *
+ * @param entry the entry for the connection to release
+ * @param reusable <code>true</code> if the entry is deemed
+ * reusable, <code>false</code> otherwise.
+ * @param validDuration The duration that the entry should remain free and reusable.
+ * @param timeUnit The unit of time the duration is measured in.
+ */
+ public abstract void freeEntry(BasicPoolEntry entry, boolean reusable, long validDuration, TimeUnit timeUnit)
+ ;
+
+
+
+ // non-javadoc, see interface RefQueueHandler
+// BEGIN android-changed
+ public void handleReference(Reference ref) {
+// END android-changed
+ poolLock.lock();
+ try {
+
+ if (ref instanceof BasicPoolEntryRef) {
+ // check if the GCed pool entry was still in use
+ //@@@ find a way to detect this without lookup
+ //@@@ flag in the BasicPoolEntryRef, to be reset when freed?
+ final boolean lost = issuedConnections.remove(ref);
+ if (lost) {
+ final HttpRoute route =
+ ((BasicPoolEntryRef)ref).getRoute();
+ if (log.isDebugEnabled()) {
+ log.debug("Connection garbage collected. " + route);
+ }
+ handleLostEntry(route);
+ }
+ }
+
+ } finally {
+ poolLock.unlock();
+ }
+ }
+
+
+ /**
+ * Handles cleaning up for a lost pool entry with the given route.
+ * A lost pool entry corresponds to a connection that was
+ * garbage collected instead of being properly released.
+ *
+ * @param route the route of the pool entry that was lost
+ */
+ protected abstract void handleLostEntry(HttpRoute route)
+ ;
+
+
+ /**
+ * Closes idle connections.
+ *
+ * @param idletime the time the connections should have been idle
+ * in order to be closed now
+ * @param tunit the unit for the <code>idletime</code>
+ */
+ public void closeIdleConnections(long idletime, TimeUnit tunit) {
+
+ // idletime can be 0 or negative, no problem there
+ if (tunit == null) {
+ throw new IllegalArgumentException("Time unit must not be null.");
+ }
+
+ poolLock.lock();
+ try {
+ idleConnHandler.closeIdleConnections(tunit.toMillis(idletime));
+ } finally {
+ poolLock.unlock();
+ }
+ }
+
+ public void closeExpiredConnections() {
+ poolLock.lock();
+ try {
+ idleConnHandler.closeExpiredConnections();
+ } finally {
+ poolLock.unlock();
+ }
+ }
+
+
+ //@@@ revise this cleanup stuff (closeIdle+deleteClosed), it's not good
+
+ /**
+ * Deletes all entries for closed connections.
+ */
+ public abstract void deleteClosedConnections()
+ ;
+
+
+ /**
+ * Shuts down this pool and all associated resources.
+ * Overriding methods MUST call the implementation here!
+ */
+ public void shutdown() {
+
+ poolLock.lock();
+ try {
+
+ if (isShutDown)
+ return;
+
+ // no point in monitoring GC anymore
+ if (refWorker != null)
+ refWorker.shutdown();
+
+ // close all connections that are issued to an application
+ Iterator<BasicPoolEntryRef> iter = issuedConnections.iterator();
+ while (iter.hasNext()) {
+ BasicPoolEntryRef per = iter.next();
+ iter.remove();
+ BasicPoolEntry entry = per.get();
+ if (entry != null) {
+ closeConnection(entry.getConnection());
+ }
+ }
+
+ // remove all references to connections
+ //@@@ use this for shutting them down instead?
+ idleConnHandler.removeAll();
+
+ isShutDown = true;
+
+ } finally {
+ poolLock.unlock();
+ }
+ }
+
+
+ /**
+ * Closes a connection from this pool.
+ *
+ * @param conn the connection to close, or <code>null</code>
+ */
+ protected void closeConnection(final OperatedClientConnection conn) {
+ if (conn != null) {
+ try {
+ conn.close();
+ } catch (IOException ex) {
+ log.debug("I/O error closing connection", ex);
+ }
+ }
+ }
+
+
+
+
+
+} // class AbstractConnPool
+
diff --git a/src/org/apache/http/impl/conn/tsccm/BasicPoolEntry.java b/src/org/apache/http/impl/conn/tsccm/BasicPoolEntry.java
new file mode 100644
index 0000000..dded360
--- /dev/null
+++ b/src/org/apache/http/impl/conn/tsccm/BasicPoolEntry.java
@@ -0,0 +1,88 @@
+/*
+ * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/BasicPoolEntry.java $
+ * $Revision: 652721 $
+ * $Date: 2008-05-01 17:32:20 -0700 (Thu, 01 May 2008) $
+ *
+ * ====================================================================
+ *
+ * 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.ReferenceQueue;
+
+import org.apache.http.conn.OperatedClientConnection;
+import org.apache.http.conn.ClientConnectionOperator;
+import org.apache.http.conn.routing.HttpRoute;
+import org.apache.http.impl.conn.AbstractPoolEntry;
+
+
+
+/**
+ * Basic implementation of a connection pool entry.
+ */
+public class BasicPoolEntry extends AbstractPoolEntry {
+
+ /**
+ * A weak reference to <code>this</code> used to detect GC of entries.
+ * Pool entries can only be GCed when they are allocated by an application
+ * and therefore not referenced with a hard link in the manager.
+ */
+ private final BasicPoolEntryRef reference;
+
+ /**
+ * Creates a new pool entry.
+ *
+ * @param op the connection operator
+ * @param route the planned route for the connection
+ * @param queue the reference queue for tracking GC of this entry,
+ * or <code>null</code>
+ */
+ public BasicPoolEntry(ClientConnectionOperator op,
+ HttpRoute route,
+ ReferenceQueue<Object> queue) {
+ super(op, route);
+ if (route == null) {
+ throw new IllegalArgumentException("HTTP route may not be null");
+ }
+ this.reference = new BasicPoolEntryRef(this, queue);
+ }
+
+ protected final OperatedClientConnection getConnection() {
+ return super.connection;
+ }
+
+ protected final HttpRoute getPlannedRoute() {
+ return super.route;
+ }
+
+ protected final BasicPoolEntryRef getWeakRef() {
+ return this.reference;
+ }
+
+
+} // class BasicPoolEntry
+
+
diff --git a/src/org/apache/http/impl/conn/tsccm/BasicPoolEntryRef.java b/src/org/apache/http/impl/conn/tsccm/BasicPoolEntryRef.java
new file mode 100644
index 0000000..32df8a5
--- /dev/null
+++ b/src/org/apache/http/impl/conn/tsccm/BasicPoolEntryRef.java
@@ -0,0 +1,80 @@
+/*
+ * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/BasicPoolEntryRef.java $
+ * $Revision: 674186 $
+ * $Date: 2008-07-05 05:18:54 -0700 (Sat, 05 Jul 2008) $
+ *
+ * ====================================================================
+ *
+ * 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.WeakReference;
+import java.lang.ref.ReferenceQueue;
+
+import org.apache.http.conn.routing.HttpRoute;
+
+
+
+/**
+ * A weak reference to a {@link BasicPoolEntry BasicPoolEntry}.
+ * This reference explicitly keeps the planned route, so the connection
+ * can be reclaimed if it is lost to garbage collection.
+ */
+public class BasicPoolEntryRef extends WeakReference<BasicPoolEntry> {
+
+ /** The planned route of the entry. */
+ private final HttpRoute route;
+
+
+ /**
+ * Creates a new reference to a pool entry.
+ *
+ * @param entry the pool entry, must not be <code>null</code>
+ * @param queue the reference queue, or <code>null</code>
+ */
+ public BasicPoolEntryRef(BasicPoolEntry entry,
+ ReferenceQueue<Object> queue) {
+ super(entry, queue);
+ if (entry == null) {
+ throw new IllegalArgumentException
+ ("Pool entry must not be null.");
+ }
+ route = entry.getPlannedRoute();
+ }
+
+
+ /**
+ * Obtain the planned route for the referenced entry.
+ * The planned route is still available, even if the entry is gone.
+ *
+ * @return the planned route
+ */
+ public final HttpRoute getRoute() {
+ return this.route;
+ }
+
+} // class BasicPoolEntryRef
+
diff --git a/src/org/apache/http/impl/conn/tsccm/BasicPooledConnAdapter.java b/src/org/apache/http/impl/conn/tsccm/BasicPooledConnAdapter.java
new file mode 100644
index 0000000..29455d0
--- /dev/null
+++ b/src/org/apache/http/impl/conn/tsccm/BasicPooledConnAdapter.java
@@ -0,0 +1,83 @@
+/*
+ * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/BasicPooledConnAdapter.java $
+ * $Revision: 653214 $
+ * $Date: 2008-05-04 07:12:13 -0700 (Sun, 04 May 2008) $
+ *
+ * ====================================================================
+ *
+ * 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 org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.impl.conn.AbstractPoolEntry;
+import org.apache.http.impl.conn.AbstractPooledConnAdapter;
+
+
+
+/**
+ * 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.
+ */
+public class BasicPooledConnAdapter extends AbstractPooledConnAdapter {
+
+ /**
+ * Creates a new adapter.
+ *
+ * @param tsccm the connection manager
+ * @param entry the pool entry for the connection being wrapped
+ */
+ protected BasicPooledConnAdapter(ThreadSafeClientConnManager tsccm,
+ AbstractPoolEntry entry) {
+ super(tsccm, entry);
+ markReusable();
+ }
+
+
+ @Override
+ protected ClientConnectionManager getManager() {
+ // override needed only to make method visible in this package
+ return super.getManager();
+ }
+
+
+ /**
+ * Obtains the pool entry.
+ *
+ * @return the pool entry, or <code>null</code> if detached
+ */
+ protected AbstractPoolEntry getPoolEntry() {
+ return super.poolEntry;
+ }
+
+
+ // non-javadoc, see base class
+ @Override
+ protected void detach() {
+ // override needed only to make method visible in this package
+ super.detach();
+ }
+}
diff --git a/src/org/apache/http/impl/conn/tsccm/ConnPoolByRoute.java b/src/org/apache/http/impl/conn/tsccm/ConnPoolByRoute.java
new file mode 100644
index 0000000..cf59129
--- /dev/null
+++ b/src/org/apache/http/impl/conn/tsccm/ConnPoolByRoute.java
@@ -0,0 +1,698 @@
+/*
+ * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/ConnPoolByRoute.java $
+ * $Revision: 677240 $
+ * $Date: 2008-07-16 04:25:47 -0700 (Wed, 16 Jul 2008) $
+ *
+ * ====================================================================
+ *
+ * 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.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Queue;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.http.conn.routing.HttpRoute;
+import org.apache.http.conn.ClientConnectionOperator;
+import org.apache.http.conn.ConnectionPoolTimeoutException;
+import org.apache.http.conn.params.ConnPerRoute;
+import org.apache.http.conn.params.ConnManagerParams;
+import org.apache.http.params.HttpParams;
+
+
+/**
+ * A connection pool that maintains connections by route.
+ * This class is derived from <code>MultiThreadedHttpConnectionManager</code>
+ * in HttpClient 3.x, see there for original authors. It implements the same
+ * algorithm for connection re-use and connection-per-host enforcement:
+ * <ul>
+ * <li>connections are re-used only for the exact same route</li>
+ * <li>connection limits are enforced per route rather than per host</li>
+ * </ul>
+ * Note that access to the pool datastructures is synchronized via the
+ * {@link AbstractConnPool#poolLock poolLock} in the base class,
+ * not via <code>synchronized</code> methods.
+ *
+ * @author <a href="mailto:rolandw at apache.org">Roland Weber</a>
+ * @author <a href="mailto:becke@u.washington.edu">Michael Becke</a>
+ * @author and others
+ */
+public class ConnPoolByRoute extends AbstractConnPool {
+
+ private final Log log = LogFactory.getLog(getClass());
+
+ /** Connection operator for this pool */
+ protected final ClientConnectionOperator operator;
+
+ /** The list of free connections */
+ protected Queue<BasicPoolEntry> freeConnections;
+
+ /** The list of WaitingThreads waiting for a connection */
+ protected Queue<WaitingThread> waitingThreads;
+
+ /**
+ * A map of route-specific pools.
+ * Keys are of class {@link HttpRoute},
+ * values of class {@link RouteSpecificPool}.
+ */
+ protected final Map<HttpRoute, RouteSpecificPool> routeToPool;
+
+ protected final int maxTotalConnections;
+
+ private final ConnPerRoute connPerRoute;
+
+ /**
+ * Creates a new connection pool, managed by route.
+ */
+ public ConnPoolByRoute(final ClientConnectionOperator operator, final HttpParams params) {
+ super();
+ if (operator == null) {
+ throw new IllegalArgumentException("Connection operator may not be null");
+ }
+ this.operator = operator;
+
+ freeConnections = createFreeConnQueue();
+ waitingThreads = createWaitingThreadQueue();
+ routeToPool = createRouteToPoolMap();
+ maxTotalConnections = ConnManagerParams
+ .getMaxTotalConnections(params);
+ connPerRoute = ConnManagerParams
+ .getMaxConnectionsPerRoute(params);
+ }
+
+
+ /**
+ * Creates the queue for {@link #freeConnections}.
+ * Called once by the constructor.
+ *
+ * @return a queue
+ */
+ protected Queue<BasicPoolEntry> createFreeConnQueue() {
+ return new LinkedList<BasicPoolEntry>();
+ }
+
+ /**
+ * Creates the queue for {@link #waitingThreads}.
+ * Called once by the constructor.
+ *
+ * @return a queue
+ */
+ protected Queue<WaitingThread> createWaitingThreadQueue() {
+ return new LinkedList<WaitingThread>();
+ }
+
+ /**
+ * Creates the map for {@link #routeToPool}.
+ * Called once by the constructor.
+ *
+ * @return a map
+ */
+ protected Map<HttpRoute, RouteSpecificPool> createRouteToPoolMap() {
+ return new HashMap<HttpRoute, RouteSpecificPool>();
+ }
+
+
+ /**
+ * Creates a new route-specific pool.
+ * Called by {@link #getRoutePool} when necessary.
+ *
+ * @param route the route
+ *
+ * @return the new pool
+ */
+ protected RouteSpecificPool newRouteSpecificPool(HttpRoute route) {
+ return new RouteSpecificPool(route, connPerRoute.getMaxForRoute(route));
+ }
+
+
+ /**
+ * Creates a new waiting thread.
+ * Called by {@link #getRoutePool} when necessary.
+ *
+ * @param cond the condition to wait for
+ * @param rospl the route specific pool, or <code>null</code>
+ *
+ * @return a waiting thread representation
+ */
+ protected WaitingThread newWaitingThread(Condition cond,
+ RouteSpecificPool rospl) {
+ return new WaitingThread(cond, rospl);
+ }
+
+
+ /**
+ * Get a route-specific pool of available connections.
+ *
+ * @param route the route
+ * @param create whether to create the pool if it doesn't exist
+ *
+ * @return the pool for the argument route,
+ * never <code>null</code> if <code>create</code> is <code>true</code>
+ */
+ protected RouteSpecificPool getRoutePool(HttpRoute route,
+ boolean create) {
+ RouteSpecificPool rospl = null;
+ poolLock.lock();
+ try {
+
+ rospl = routeToPool.get(route);
+ if ((rospl == null) && create) {
+ // no pool for this route yet (or anymore)
+ rospl = newRouteSpecificPool(route);
+ routeToPool.put(route, rospl);
+ }
+
+ } finally {
+ poolLock.unlock();
+ }
+
+ return rospl;
+ }
+
+
+ //@@@ consider alternatives for gathering statistics
+ public int getConnectionsInPool(HttpRoute route) {
+
+ poolLock.lock();
+ try {
+ // don't allow a pool to be created here!
+ RouteSpecificPool rospl = getRoutePool(route, false);
+ return (rospl != null) ? rospl.getEntryCount() : 0;
+
+ } finally {
+ poolLock.unlock();
+ }
+ }
+
+ @Override
+ public PoolEntryRequest requestPoolEntry(
+ final HttpRoute route,
+ final Object state) {
+
+ final WaitingThreadAborter aborter = new WaitingThreadAborter();
+
+ return new PoolEntryRequest() {
+
+ public void abortRequest() {
+ poolLock.lock();
+ try {
+ aborter.abort();
+ } finally {
+ poolLock.unlock();
+ }
+ }
+
+ public BasicPoolEntry getPoolEntry(
+ long timeout,
+ TimeUnit tunit)
+ throws InterruptedException, ConnectionPoolTimeoutException {
+ return getEntryBlocking(route, state, timeout, tunit, aborter);
+ }
+
+ };
+ }
+
+ /**
+ * Obtains a pool entry with a connection within the given timeout.
+ * If a {@link WaitingThread} is used to block, {@link WaitingThreadAborter#setWaitingThread(WaitingThread)}
+ * must be called before blocking, to allow the thread to be interrupted.
+ *
+ * @param route the route for which to get the connection
+ * @param timeout the timeout, 0 or negative for no timeout
+ * @param tunit the unit for the <code>timeout</code>,
+ * may be <code>null</code> only if there is no timeout
+ * @param aborter an object which can abort a {@link WaitingThread}.
+ *
+ * @return pool entry holding a connection for the route
+ *
+ * @throws ConnectionPoolTimeoutException
+ * if the timeout expired
+ * @throws InterruptedException
+ * if the calling thread was interrupted
+ */
+ protected BasicPoolEntry getEntryBlocking(
+ HttpRoute route, Object state,
+ long timeout, TimeUnit tunit,
+ WaitingThreadAborter aborter)
+ throws ConnectionPoolTimeoutException, InterruptedException {
+
+ Date deadline = null;
+ if (timeout > 0) {
+ deadline = new Date
+ (System.currentTimeMillis() + tunit.toMillis(timeout));
+ }
+
+ BasicPoolEntry entry = null;
+ poolLock.lock();
+ try {
+
+ RouteSpecificPool rospl = getRoutePool(route, true);
+ WaitingThread waitingThread = null;
+
+ while (entry == null) {
+
+ if (isShutDown) {
+ throw new IllegalStateException
+ ("Connection pool shut down.");
+ }
+
+ if (log.isDebugEnabled()) {
+ log.debug("Total connections kept alive: " + freeConnections.size());
+ log.debug("Total issued connections: " + issuedConnections.size());
+ log.debug("Total allocated connection: " + numConnections + " out of " + maxTotalConnections);
+ }
+
+ // the cases to check for:
+ // - have a free connection for that route
+ // - allowed to create a free connection for that route
+ // - can delete and replace a free connection for another route
+ // - need to wait for one of the things above to come true
+
+ entry = getFreeEntry(rospl, state);
+ if (entry != null) {
+ break;
+ }
+
+ boolean hasCapacity = rospl.getCapacity() > 0;
+
+ if (log.isDebugEnabled()) {
+ log.debug("Available capacity: " + rospl.getCapacity()
+ + " out of " + rospl.getMaxEntries()
+ + " [" + route + "][" + state + "]");
+ }
+
+ if (hasCapacity && numConnections < maxTotalConnections) {
+
+ entry = createEntry(rospl, operator);
+
+ } else if (hasCapacity && !freeConnections.isEmpty()) {
+
+ deleteLeastUsedEntry();
+ entry = createEntry(rospl, operator);
+
+ } else {
+
+ if (log.isDebugEnabled()) {
+ log.debug("Need to wait for connection" +
+ " [" + route + "][" + state + "]");
+ }
+
+ if (waitingThread == null) {
+ waitingThread =
+ newWaitingThread(poolLock.newCondition(), rospl);
+ aborter.setWaitingThread(waitingThread);
+ }
+
+ boolean success = false;
+ try {
+ rospl.queueThread(waitingThread);
+ waitingThreads.add(waitingThread);
+ success = waitingThread.await(deadline);
+
+ } finally {
+ // In case of 'success', we were woken up by the
+ // connection pool and should now have a connection
+ // waiting for us, or else we're shutting down.
+ // Just continue in the loop, both cases are checked.
+ rospl.removeThread(waitingThread);
+ waitingThreads.remove(waitingThread);
+ }
+
+ // check for spurious wakeup vs. timeout
+ if (!success && (deadline != null) &&
+ (deadline.getTime() <= System.currentTimeMillis())) {
+ throw new ConnectionPoolTimeoutException
+ ("Timeout waiting for connection");
+ }
+ }
+ } // while no entry
+
+ } finally {
+ poolLock.unlock();
+ }
+
+ return entry;
+
+ } // getEntry
+
+
+ // non-javadoc, see base class AbstractConnPool
+ @Override
+ public void freeEntry(BasicPoolEntry entry, boolean reusable, long validDuration, TimeUnit timeUnit) {
+
+ HttpRoute route = entry.getPlannedRoute();
+ if (log.isDebugEnabled()) {
+ log.debug("Freeing connection" +
+ " [" + route + "][" + entry.getState() + "]");
+ }
+
+ poolLock.lock();
+ try {
+ if (isShutDown) {
+ // the pool is shut down, release the
+ // connection's resources and get out of here
+ closeConnection(entry.getConnection());
+ return;
+ }
+
+ // no longer issued, we keep a hard reference now
+ issuedConnections.remove(entry.getWeakRef());
+
+ RouteSpecificPool rospl = getRoutePool(route, true);
+
+ if (reusable) {
+ rospl.freeEntry(entry);
+ freeConnections.add(entry);
+ idleConnHandler.add(entry.getConnection(), validDuration, timeUnit);
+ } else {
+ rospl.dropEntry();
+ numConnections--;
+ }
+
+ notifyWaitingThread(rospl);
+
+ } finally {
+ poolLock.unlock();
+ }
+
+ } // freeEntry
+
+
+
+ /**
+ * If available, get a free pool entry for a route.
+ *
+ * @param rospl the route-specific pool from which to get an entry
+ *
+ * @return an available pool entry for the given route, or
+ * <code>null</code> if none is available
+ */
+ protected BasicPoolEntry getFreeEntry(RouteSpecificPool rospl, Object state) {
+
+ BasicPoolEntry entry = null;
+ poolLock.lock();
+ try {
+ boolean done = false;
+ while(!done) {
+
+ entry = rospl.allocEntry(state);
+
+ if (entry != null) {
+ if (log.isDebugEnabled()) {
+ log.debug("Getting free connection"
+ + " [" + rospl.getRoute() + "][" + state + "]");
+
+ }
+ freeConnections.remove(entry);
+ boolean valid = idleConnHandler.remove(entry.getConnection());
+ if(!valid) {
+ // If the free entry isn't valid anymore, get rid of it
+ // and loop to find another one that might be valid.
+ if(log.isDebugEnabled())
+ log.debug("Closing expired free connection"
+ + " [" + rospl.getRoute() + "][" + state + "]");
+ closeConnection(entry.getConnection());
+ // We use dropEntry instead of deleteEntry because the entry
+ // is no longer "free" (we just allocated it), and deleteEntry
+ // can only be used to delete free entries.
+ rospl.dropEntry();
+ numConnections--;
+ } else {
+ issuedConnections.add(entry.getWeakRef());
+ done = true;
+ }
+
+ } else {
+ done = true;
+ if (log.isDebugEnabled()) {
+ log.debug("No free connections"
+ + " [" + rospl.getRoute() + "][" + state + "]");
+ }
+ }
+ }
+ } finally {
+ poolLock.unlock();
+ }
+
+ return entry;
+ }
+
+
+ /**
+ * Creates a new pool entry.
+ * This method assumes that the new connection will be handed
+ * out immediately.
+ *
+ * @param rospl the route-specific pool for which to create the entry
+ * @param op the operator for creating a connection
+ *
+ * @return the new pool entry for a new connection
+ */
+ protected BasicPoolEntry createEntry(RouteSpecificPool rospl,
+ ClientConnectionOperator op) {
+
+ if (log.isDebugEnabled()) {
+ log.debug("Creating new connection [" + rospl.getRoute() + "]");
+ }
+
+ // the entry will create the connection when needed
+ BasicPoolEntry entry =
+ new BasicPoolEntry(op, rospl.getRoute(), refQueue);
+
+ poolLock.lock();
+ try {
+
+ rospl.createdEntry(entry);
+ numConnections++;
+
+ issuedConnections.add(entry.getWeakRef());
+
+ } finally {
+ poolLock.unlock();
+ }
+
+ return entry;
+ }
+
+
+ /**
+ * Deletes a given pool entry.
+ * This closes the pooled connection and removes all references,
+ * so that it can be GCed.
+ *
+ * <p><b>Note:</b> Does not remove the entry from the freeConnections list.
+ * It is assumed that the caller has already handled this step.</p>
+ * <!-- @@@ is that a good idea? or rather fix it? -->
+ *
+ * @param entry the pool entry for the connection to delete
+ */
+ protected void deleteEntry(BasicPoolEntry entry) {
+
+ HttpRoute route = entry.getPlannedRoute();
+
+ if (log.isDebugEnabled()) {
+ log.debug("Deleting connection"
+ + " [" + route + "][" + entry.getState() + "]");
+ }
+
+ poolLock.lock();
+ try {
+
+ closeConnection(entry.getConnection());
+
+ RouteSpecificPool rospl = getRoutePool(route, true);
+ rospl.deleteEntry(entry);
+ numConnections--;
+ if (rospl.isUnused()) {
+ routeToPool.remove(route);
+ }
+
+ idleConnHandler.remove(entry.getConnection());// not idle, but dead
+
+ } finally {
+ poolLock.unlock();
+ }
+ }
+
+
+ /**
+ * Delete an old, free pool entry to make room for a new one.
+ * Used to replace pool entries with ones for a different route.
+ */
+ protected void deleteLeastUsedEntry() {
+
+ try {
+ poolLock.lock();
+
+ //@@@ with get() instead of remove, we could
+ //@@@ leave the removing to deleteEntry()
+ BasicPoolEntry entry = freeConnections.remove();
+
+ if (entry != null) {
+ deleteEntry(entry);
+ } else if (log.isDebugEnabled()) {
+ log.debug("No free connection to delete.");
+ }
+
+ } finally {
+ poolLock.unlock();
+ }
+ }
+
+
+ // non-javadoc, see base class AbstractConnPool
+ @Override
+ protected void handleLostEntry(HttpRoute route) {
+
+ poolLock.lock();
+ try {
+
+ RouteSpecificPool rospl = getRoutePool(route, true);
+ rospl.dropEntry();
+ if (rospl.isUnused()) {
+ routeToPool.remove(route);
+ }
+
+ numConnections--;
+ notifyWaitingThread(rospl);
+
+ } finally {
+ poolLock.unlock();
+ }
+ }
+
+
+ /**
+ * Notifies a waiting thread that a connection is available.
+ * This will wake a thread waiting in the specific route pool,
+ * if there is one.
+ * Otherwise, a thread in the connection pool will be notified.
+ *
+ * @param rospl the pool in which to notify, or <code>null</code>
+ */
+ protected void notifyWaitingThread(RouteSpecificPool rospl) {
+
+ //@@@ while this strategy provides for best connection re-use,
+ //@@@ is it fair? only do this if the connection is open?
+ // 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.
+ WaitingThread waitingThread = null;
+
+ poolLock.lock();
+ try {
+
+ if ((rospl != null) && rospl.hasThread()) {
+ if (log.isDebugEnabled()) {
+ log.debug("Notifying thread waiting on pool" +
+ " [" + rospl.getRoute() + "]");
+ }
+ waitingThread = rospl.nextThread();
+ } else if (!waitingThreads.isEmpty()) {
+ if (log.isDebugEnabled()) {
+ log.debug("Notifying thread waiting on any pool");
+ }
+ waitingThread = waitingThreads.remove();
+ } else if (log.isDebugEnabled()) {
+ log.debug("Notifying no-one, there are no waiting threads");
+ }
+
+ if (waitingThread != null) {
+ waitingThread.wakeup();
+ }
+
+ } finally {
+ poolLock.unlock();
+ }
+ }
+
+
+ //@@@ revise this cleanup stuff
+ //@@@ move method to base class when deleteEntry() is fixed
+ // non-javadoc, see base class AbstractConnPool
+ @Override
+ public void deleteClosedConnections() {
+
+ poolLock.lock();
+ try {
+
+ Iterator<BasicPoolEntry> iter = freeConnections.iterator();
+ while (iter.hasNext()) {
+ BasicPoolEntry entry = iter.next();
+ if (!entry.getConnection().isOpen()) {
+ iter.remove();
+ deleteEntry(entry);
+ }
+ }
+
+ } finally {
+ poolLock.unlock();
+ }
+ }
+
+
+ // non-javadoc, see base class AbstractConnPool
+ @Override
+ public void shutdown() {
+
+ poolLock.lock();
+ try {
+
+ super.shutdown();
+
+ // close all free connections
+ //@@@ move this to base class?
+ Iterator<BasicPoolEntry> ibpe = freeConnections.iterator();
+ while (ibpe.hasNext()) {
+ BasicPoolEntry entry = ibpe.next();
+ ibpe.remove();
+ closeConnection(entry.getConnection());
+ }
+
+ // wake up all waiting threads
+ Iterator<WaitingThread> iwth = waitingThreads.iterator();
+ while (iwth.hasNext()) {
+ WaitingThread waiter = iwth.next();
+ iwth.remove();
+ waiter.wakeup();
+ }
+
+ routeToPool.clear();
+
+ } finally {
+ poolLock.unlock();
+ }
+ }
+
+
+} // class ConnPoolByRoute
+
diff --git a/src/org/apache/http/impl/conn/tsccm/PoolEntryRequest.java b/src/org/apache/http/impl/conn/tsccm/PoolEntryRequest.java
new file mode 100644
index 0000000..faf5e3b
--- /dev/null
+++ b/src/org/apache/http/impl/conn/tsccm/PoolEntryRequest.java
@@ -0,0 +1,68 @@
+/*
+ * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/PoolEntryRequest.java $
+ * $Revision: 652020 $
+ * $Date: 2008-04-27 14:23:31 -0700 (Sun, 27 Apr 2008) $
+ *
+ * ====================================================================
+ *
+ * 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.util.concurrent.TimeUnit;
+
+import org.apache.http.conn.ConnectionPoolTimeoutException;
+
+/**
+ * Encapsulates a request for a {@link BasicPoolEntry}.
+ */
+public interface PoolEntryRequest {
+
+ /**
+ * Obtains a pool entry with a connection within the given timeout.
+ * If {@link #abortRequest()} is called before this completes
+ * an {@link InterruptedException} is thrown.
+ *
+ * @param timeout the timeout, 0 or negative for no timeout
+ * @param tunit the unit for the <code>timeout</code>,
+ * may be <code>null</code> only if there is no timeout
+ *
+ * @return pool entry holding a connection for the route
+ *
+ * @throws ConnectionPoolTimeoutException
+ * if the timeout expired
+ * @throws InterruptedException
+ * if the calling thread was interrupted or the request was aborted
+ */
+ BasicPoolEntry getPoolEntry(
+ long timeout,
+ TimeUnit tunit) throws InterruptedException, ConnectionPoolTimeoutException;
+
+ /**
+ * Aborts the active or next call to
+ * {@link #getPoolEntry(long, TimeUnit)}.
+ */
+ void abortRequest();
+
+}
diff --git a/src/org/apache/http/impl/conn/tsccm/RefQueueHandler.java b/src/org/apache/http/impl/conn/tsccm/RefQueueHandler.java
new file mode 100644
index 0000000..3af28cc
--- /dev/null
+++ b/src/org/apache/http/impl/conn/tsccm/RefQueueHandler.java
@@ -0,0 +1,48 @@
+/*
+ * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/RefQueueHandler.java $
+ * $Revision: 603874 $
+ * $Date: 2007-12-13 02:42:41 -0800 (Thu, 13 Dec 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.tsccm;
+
+import java.lang.ref.Reference;
+
+
+/**
+ * Callback handler for {@link RefQueueWorker RefQueueWorker}.
+ */
+public interface RefQueueHandler {
+
+ /**
+ * Invoked when a reference is found on the queue.
+ *
+ * @param ref the reference to handle
+ */
+ public void handleReference(Reference<?> ref)
+ ;
+}
diff --git a/src/org/apache/http/impl/conn/tsccm/RefQueueWorker.java b/src/org/apache/http/impl/conn/tsccm/RefQueueWorker.java
new file mode 100644
index 0000000..9ad5c77
--- /dev/null
+++ b/src/org/apache/http/impl/conn/tsccm/RefQueueWorker.java
@@ -0,0 +1,139 @@
+/*
+ * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/RefQueueWorker.java $
+ * $Revision: 673450 $
+ * $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 Jul 2008) $
+ *
+ * ====================================================================
+ *
+ * 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 org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+
+
+/**
+ * A worker thread for processing queued references.
+ * {@link Reference Reference}s can be
+ * {@link ReferenceQueue queued}
+ * automatically by the garbage collector.
+ * If that feature is used, a daemon thread should be executing
+ * this worker. It will pick up the queued references and pass them
+ * on to a handler for appropriate processing.
+ */
+public class RefQueueWorker implements Runnable {
+
+ private final Log log = LogFactory.getLog(getClass());
+
+ /** The reference queue to monitor. */
+ protected final ReferenceQueue<?> refQueue;
+
+ /** The handler for the references found. */
+ protected final RefQueueHandler refHandler;
+
+
+ /**
+ * The thread executing this handler.
+ * This attribute is also used as a shutdown indicator.
+ */
+ protected volatile Thread workerThread;
+
+
+ /**
+ * Instantiates a new worker to listen for lost connections.
+ *
+ * @param queue the queue on which to wait for references
+ * @param handler the handler to pass the references to
+ */
+ public RefQueueWorker(ReferenceQueue<?> queue, RefQueueHandler handler) {
+ if (queue == null) {
+ throw new IllegalArgumentException("Queue must not be null.");
+ }
+ if (handler == null) {
+ throw new IllegalArgumentException("Handler must not be null.");
+ }
+
+ refQueue = queue;
+ refHandler = handler;
+ }
+
+
+ /**
+ * The main loop of this worker.
+ * If initialization succeeds, this method will only return
+ * after {@link #shutdown shutdown()}. Only one thread can
+ * execute the main loop at any time.
+ */
+ public void run() {
+
+ if (this.workerThread == null) {
+ this.workerThread = Thread.currentThread();
+ }
+
+ while (this.workerThread == Thread.currentThread()) {
+ try {
+ // remove the next reference and process it
+ Reference<?> ref = refQueue.remove();
+ refHandler.handleReference(ref);
+ } catch (InterruptedException e) {
+ //@@@ is logging really necessary? this here is the
+ //@@@ only reason for having a log in this class
+ if (log.isDebugEnabled()) {
+ log.debug(this.toString() + " interrupted", e);
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Shuts down this worker.
+ * It can be re-started afterwards by another call to {@link #run run()}.
+ */
+ public void shutdown() {
+ Thread wt = this.workerThread;
+ if (wt != null) {
+ this.workerThread = null; // indicate shutdown
+ wt.interrupt();
+ }
+ }
+
+
+ /**
+ * Obtains a description of this worker.
+ *
+ * @return a descriptive string for this worker
+ */
+ @Override
+ public String toString() {
+ return "RefQueueWorker::" + this.workerThread;
+ }
+
+} // class RefQueueWorker
+
diff --git a/src/org/apache/http/impl/conn/tsccm/RouteSpecificPool.java b/src/org/apache/http/impl/conn/tsccm/RouteSpecificPool.java
new file mode 100644
index 0000000..5c63933
--- /dev/null
+++ b/src/org/apache/http/impl/conn/tsccm/RouteSpecificPool.java
@@ -0,0 +1,301 @@
+/*
+ * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/RouteSpecificPool.java $
+ * $Revision: 677240 $
+ * $Date: 2008-07-16 04:25:47 -0700 (Wed, 16 Jul 2008) $
+ *
+ * ====================================================================
+ *
+ * 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.io.IOException;
+import java.util.ListIterator;
+import java.util.Queue;
+import java.util.LinkedList;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.http.conn.OperatedClientConnection;
+import org.apache.http.conn.routing.HttpRoute;
+import org.apache.http.util.LangUtils;
+
+
+/**
+ * A connection sub-pool for a specific route, used by {@link ConnPoolByRoute}.
+ * The methods in this class are unsynchronized. It is expected that the
+ * containing pool takes care of synchronization.
+ */
+public class RouteSpecificPool {
+
+ private final Log log = LogFactory.getLog(getClass());
+
+ /** The route this pool is for. */
+ protected final HttpRoute route;
+
+ /** the maximum number of entries allowed for this pool */
+ protected final int maxEntries;
+
+ /**
+ * The list of free entries.
+ * This list is managed LIFO, to increase idle times and
+ * allow for closing connections that are not really needed.
+ */
+ protected final LinkedList<BasicPoolEntry> freeEntries;
+
+ /** The list of threads waiting for this pool. */
+ protected final Queue<WaitingThread> waitingThreads;
+
+ /** The number of created entries. */
+ protected int numEntries;
+
+
+ /**
+ * Creates a new route-specific pool.
+ *
+ * @param route the route for which to pool
+ * @param maxEntries the maximum number of entries allowed for this pool
+ */
+ public RouteSpecificPool(HttpRoute route, int maxEntries) {
+ this.route = route;
+ this.maxEntries = maxEntries;
+ this.freeEntries = new LinkedList<BasicPoolEntry>();
+ this.waitingThreads = new LinkedList<WaitingThread>();
+ this.numEntries = 0;
+ }
+
+
+ /**
+ * Obtains the route for which this pool is specific.
+ *
+ * @return the route
+ */
+ public final HttpRoute getRoute() {
+ return route;
+ }
+
+
+ /**
+ * Obtains the maximum number of entries allowed for this pool.
+ *
+ * @return the max entry number
+ */
+ public final int getMaxEntries() {
+ return maxEntries;
+ }
+
+
+ /**
+ * Indicates whether this pool is unused.
+ * A pool is unused if there is neither an entry nor a waiting thread.
+ * All entries count, not only the free but also the allocated ones.
+ *
+ * @return <code>true</code> if this pool is unused,
+ * <code>false</code> otherwise
+ */
+ public boolean isUnused() {
+ return (numEntries < 1) && waitingThreads.isEmpty();
+ }
+
+
+ /**
+ * Return remaining capacity of this pool
+ *
+ * @return capacity
+ */
+ public int getCapacity() {
+ return maxEntries - numEntries;
+ }
+
+
+ /**
+ * Obtains the number of entries.
+ * This includes not only the free entries, but also those that
+ * have been created and are currently issued to an application.
+ *
+ * @return the number of entries for the route of this pool
+ */
+ public final int getEntryCount() {
+ return numEntries;
+ }
+
+
+ /**
+ * Obtains a free entry from this pool, if one is available.
+ *
+ * @return an available pool entry, or <code>null</code> if there is none
+ */
+ public BasicPoolEntry allocEntry(final Object state) {
+ if (!freeEntries.isEmpty()) {
+ ListIterator<BasicPoolEntry> it = freeEntries.listIterator(freeEntries.size());
+ while (it.hasPrevious()) {
+ BasicPoolEntry entry = it.previous();
+ if (LangUtils.equals(state, entry.getState())) {
+ it.remove();
+ return entry;
+ }
+ }
+ }
+ if (!freeEntries.isEmpty()) {
+ BasicPoolEntry entry = freeEntries.remove();
+ entry.setState(null);
+ OperatedClientConnection conn = entry.getConnection();
+ try {
+ conn.close();
+ } catch (IOException ex) {
+ log.debug("I/O error closing connection", ex);
+ }
+ return entry;
+ }
+ return null;
+ }
+
+
+ /**
+ * Returns an allocated entry to this pool.
+ *
+ * @param entry the entry obtained from {@link #allocEntry allocEntry}
+ * or presented to {@link #createdEntry createdEntry}
+ */
+ public void freeEntry(BasicPoolEntry entry) {
+
+ if (numEntries < 1) {
+ throw new IllegalStateException
+ ("No entry created for this pool. " + route);
+ }
+ if (numEntries <= freeEntries.size()) {
+ throw new IllegalStateException
+ ("No entry allocated from this pool. " + route);
+ }
+ freeEntries.add(entry);
+ }
+
+
+ /**
+ * Indicates creation of an entry for this pool.
+ * The entry will <i>not</i> be added to the list of free entries,
+ * it is only recognized as belonging to this pool now. It can then
+ * be passed to {@link #freeEntry freeEntry}.
+ *
+ * @param entry the entry that was created for this pool
+ */
+ public void createdEntry(BasicPoolEntry entry) {
+
+ if (!route.equals(entry.getPlannedRoute())) {
+ throw new IllegalArgumentException
+ ("Entry not planned for this pool." +
+ "\npool: " + route +
+ "\nplan: " + entry.getPlannedRoute());
+ }
+
+ numEntries++;
+ }
+
+
+ /**
+ * Deletes an entry from this pool.
+ * Only entries that are currently free in this pool can be deleted.
+ * Allocated entries can not be deleted.
+ *
+ * @param entry the entry to delete from this pool
+ *
+ * @return <code>true</code> if the entry was found and deleted, or
+ * <code>false</code> if the entry was not found
+ */
+ public boolean deleteEntry(BasicPoolEntry entry) {
+
+ final boolean found = freeEntries.remove(entry);
+ if (found)
+ numEntries--;
+ return found;
+ }
+
+
+ /**
+ * Forgets about an entry from this pool.
+ * This method is used to indicate that an entry
+ * {@link #allocEntry allocated}
+ * from this pool has been lost and will not be returned.
+ */
+ public void dropEntry() {
+ if (numEntries < 1) {
+ throw new IllegalStateException
+ ("There is no entry that could be dropped.");
+ }
+ numEntries--;
+ }
+
+
+ /**
+ * Adds a waiting thread.
+ * This pool makes no attempt to match waiting threads with pool entries.
+ * It is the caller's responsibility to check that there is no entry
+ * before adding a waiting thread.
+ *
+ * @param wt the waiting thread
+ */
+ public void queueThread(WaitingThread wt) {
+ if (wt == null) {
+ throw new IllegalArgumentException
+ ("Waiting thread must not be null.");
+ }
+ this.waitingThreads.add(wt);
+ }
+
+
+ /**
+ * Checks whether there is a waiting thread in this pool.
+ *
+ * @return <code>true</code> if there is a waiting thread,
+ * <code>false</code> otherwise
+ */
+ public boolean hasThread() {
+ return !this.waitingThreads.isEmpty();
+ }
+
+
+ /**
+ * Returns the next thread in the queue.
+ *
+ * @return a waiting thread, or <code>null</code> if there is none
+ */
+ public WaitingThread nextThread() {
+ return this.waitingThreads.peek();
+ }
+
+
+ /**
+ * Removes a waiting thread, if it is queued.
+ *
+ * @param wt the waiting thread
+ */
+ public void removeThread(WaitingThread wt) {
+ if (wt == null)
+ return;
+
+ this.waitingThreads.remove(wt);
+ }
+
+
+} // class RouteSpecificPool
diff --git a/src/org/apache/http/impl/conn/tsccm/ThreadSafeClientConnManager.java b/src/org/apache/http/impl/conn/tsccm/ThreadSafeClientConnManager.java
new file mode 100644
index 0000000..0781e05
--- /dev/null
+++ b/src/org/apache/http/impl/conn/tsccm/ThreadSafeClientConnManager.java
@@ -0,0 +1,282 @@
+/*
+ * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/ThreadSafeClientConnManager.java $
+ * $Revision: 673450 $
+ * $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 Jul 2008) $
+ *
+ * ====================================================================
+ *
+ * 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.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.http.conn.routing.HttpRoute;
+import org.apache.http.conn.scheme.SchemeRegistry;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.conn.ClientConnectionOperator;
+import org.apache.http.conn.ClientConnectionRequest;
+import org.apache.http.conn.ConnectionPoolTimeoutException;
+import org.apache.http.conn.ManagedClientConnection;
+import org.apache.http.conn.OperatedClientConnection;
+import org.apache.http.params.HttpParams;
+import org.apache.http.impl.conn.DefaultClientConnectionOperator;
+
+
+
+/**
+ * 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: 673450 $ $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 Jul 2008) $
+ *
+ * @since 4.0
+ */
+public class ThreadSafeClientConnManager implements ClientConnectionManager {
+
+ private final Log log = LogFactory.getLog(getClass());
+
+ /** The schemes supported by this connection manager. */
+ protected SchemeRegistry schemeRegistry;
+
+ /** The pool of connections being managed. */
+ protected final AbstractConnPool connectionPool;
+
+ /** The operator for opening and updating connections. */
+ protected ClientConnectionOperator connOperator;
+
+
+
+ /**
+ * 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("HTTP parameters may not be null");
+ }
+ this.schemeRegistry = schreg;
+ this.connOperator = createConnectionOperator(schreg);
+ this.connectionPool = createConnectionPool(params);
+
+ } // <constructor>
+
+
+ @Override
+ protected void finalize() throws Throwable {
+ shutdown();
+ super.finalize();
+ }
+
+
+ /**
+ * Hook for creating the connection pool.
+ *
+ * @return the connection pool to use
+ */
+ protected AbstractConnPool createConnectionPool(final HttpParams params) {
+
+ AbstractConnPool acp = new ConnPoolByRoute(connOperator, params);
+ boolean conngc = true; //@@@ check parameters to decide
+ if (conngc) {
+ acp.enableConnectionGC();
+ }
+ return acp;
+ }
+
+
+ /**
+ * 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);
+ }
+
+
+ // non-javadoc, see interface ClientConnectionManager
+ public SchemeRegistry getSchemeRegistry() {
+ return this.schemeRegistry;
+ }
+
+
+ public ClientConnectionRequest requestConnection(
+ final HttpRoute route,
+ final Object state) {
+
+ final PoolEntryRequest poolRequest = connectionPool.requestPoolEntry(
+ route, state);
+
+ return new ClientConnectionRequest() {
+
+ public void abortRequest() {
+ poolRequest.abortRequest();
+ }
+
+ public ManagedClientConnection getConnection(
+ long timeout, TimeUnit tunit) throws InterruptedException,
+ ConnectionPoolTimeoutException {
+ if (route == null) {
+ throw new IllegalArgumentException("Route may not be null.");
+ }
+
+ if (log.isDebugEnabled()) {
+ log.debug("ThreadSafeClientConnManager.getConnection: "
+ + route + ", timeout = " + timeout);
+ }
+
+ BasicPoolEntry entry = poolRequest.getPoolEntry(timeout, tunit);
+ return new BasicPooledConnAdapter(ThreadSafeClientConnManager.this, entry);
+ }
+
+ };
+
+ }
+
+
+ // non-javadoc, see interface ClientConnectionManager
+ public void releaseConnection(ManagedClientConnection conn, long validDuration, TimeUnit timeUnit) {
+
+ if (!(conn instanceof BasicPooledConnAdapter)) {
+ throw new IllegalArgumentException
+ ("Connection class mismatch, " +
+ "connection not obtained from this manager.");
+ }
+ BasicPooledConnAdapter hca = (BasicPooledConnAdapter) conn;
+ if ((hca.getPoolEntry() != null) && (hca.getManager() != 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, there would be a call to
+ // 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 {
+ BasicPoolEntry entry = (BasicPoolEntry) hca.getPoolEntry();
+ boolean reusable = hca.isMarkedReusable();
+ hca.detach();
+ if (entry != null) {
+ connectionPool.freeEntry(entry, reusable, validDuration, timeUnit);
+ }
+ }
+ }
+
+
+ // non-javadoc, see interface ClientConnectionManager
+ public void shutdown() {
+ connectionPool.shutdown();
+ }
+
+
+ /**
+ * Gets the total number of pooled connections for the given route.
+ * This is the total number of connections that have been created and
+ * are still in use by this connection manager for the route.
+ * This value will not exceed the maximum number of connections per host.
+ *
+ * @param route the route in question
+ *
+ * @return the total number of pooled connections for that route
+ */
+ public int getConnectionsInPool(HttpRoute route) {
+ return ((ConnPoolByRoute)connectionPool).getConnectionsInPool(
+ route);
+ }
+
+
+ /**
+ * 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; //@@@
+ }
+ }
+
+
+ // non-javadoc, see interface ClientConnectionManager
+ public void closeIdleConnections(long idleTimeout, TimeUnit tunit) {
+ // combine these two in a single call?
+ connectionPool.closeIdleConnections(idleTimeout, tunit);
+ connectionPool.deleteClosedConnections();
+ }
+
+ public void closeExpiredConnections() {
+ connectionPool.closeExpiredConnections();
+ connectionPool.deleteClosedConnections();
+ }
+
+
+} // class ThreadSafeClientConnManager
+
diff --git a/src/org/apache/http/impl/conn/tsccm/WaitingThread.java b/src/org/apache/http/impl/conn/tsccm/WaitingThread.java
new file mode 100644
index 0000000..a50e11f
--- /dev/null
+++ b/src/org/apache/http/impl/conn/tsccm/WaitingThread.java
@@ -0,0 +1,197 @@
+/*
+ * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/WaitingThread.java $
+ * $Revision: 649217 $
+ * $Date: 2008-04-17 11:32:32 -0700 (Thu, 17 Apr 2008) $
+ *
+ * ====================================================================
+ *
+ * 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.util.Date;
+import java.util.concurrent.locks.Condition;
+
+
+/**
+ * Represents a thread waiting for a connection.
+ * This class implements throwaway objects. It is instantiated whenever
+ * a thread needs to wait. Instances are not re-used, except if the
+ * waiting thread experiences a spurious wakeup and continues to wait.
+ * <br/>
+ * All methods assume external synchronization on the condition
+ * passed to the constructor.
+ * Instances of this class do <i>not</i> synchronize access!
+ *
+ * @author <a href="mailto:rolandw at apache.org">Roland Weber</a>
+ */
+public class WaitingThread {
+
+ /** The condition on which the thread is waiting. */
+ private final Condition cond;
+
+ /** The route specific pool on which the thread is waiting. */
+ //@@@ replace with generic pool interface
+ private final RouteSpecificPool pool;
+
+ /** The thread that is waiting for an entry. */
+ private Thread waiter;
+
+ /** True if this was interrupted. */
+ private boolean aborted;
+
+
+ /**
+ * Creates a new entry for a waiting thread.
+ *
+ * @param cond the condition for which to wait
+ * @param pool the pool on which the thread will be waiting,
+ * or <code>null</code>
+ */
+ public WaitingThread(Condition cond, RouteSpecificPool pool) {
+
+ if (cond == null) {
+ throw new IllegalArgumentException("Condition must not be null.");
+ }
+
+ this.cond = cond;
+ this.pool = pool;
+ }
+
+
+ /**
+ * Obtains the condition.
+ *
+ * @return the condition on which to wait, never <code>null</code>
+ */
+ public final Condition getCondition() {
+ // not synchronized
+ return this.cond;
+ }
+
+
+ /**
+ * Obtains the pool, if there is one.
+ *
+ * @return the pool on which a thread is or was waiting,
+ * or <code>null</code>
+ */
+ public final RouteSpecificPool getPool() {
+ // not synchronized
+ return this.pool;
+ }
+
+
+ /**
+ * Obtains the thread, if there is one.
+ *
+ * @return the thread which is waiting, or <code>null</code>
+ */
+ public final Thread getThread() {
+ // not synchronized
+ return this.waiter;
+ }
+
+
+ /**
+ * Blocks the calling thread.
+ * This method returns when the thread is notified or interrupted,
+ * if a timeout occurrs, or if there is a spurious wakeup.
+ * <br/>
+ * This method assumes external synchronization.
+ *
+ * @param deadline when to time out, or <code>null</code> for no timeout
+ *
+ * @return <code>true</code> if the condition was satisfied,
+ * <code>false</code> in case of a timeout.
+ * Typically, a call to {@link #wakeup} is used to indicate
+ * that the condition was satisfied. Since the condition is
+ * accessible outside, this cannot be guaranteed though.
+ *
+ * @throws InterruptedException if the waiting thread was interrupted
+ *
+ * @see #wakeup
+ */
+ public boolean await(Date deadline)
+ throws InterruptedException {
+
+ // This is only a sanity check. We cannot synchronize here,
+ // the lock would not be released on calling cond.await() below.
+ if (this.waiter != null) {
+ throw new IllegalStateException
+ ("A thread is already waiting on this object." +
+ "\ncaller: " + Thread.currentThread() +
+ "\nwaiter: " + this.waiter);
+ }
+
+ if (aborted)
+ throw new InterruptedException("Operation interrupted");
+
+ this.waiter = Thread.currentThread();
+
+ boolean success = false;
+ try {
+ if (deadline != null) {
+ success = this.cond.awaitUntil(deadline);
+ } else {
+ this.cond.await();
+ success = true;
+ }
+ if (aborted)
+ throw new InterruptedException("Operation interrupted");
+ } finally {
+ this.waiter = null;
+ }
+ return success;
+
+ } // await
+
+
+ /**
+ * Wakes up the waiting thread.
+ * <br/>
+ * This method assumes external synchronization.
+ */
+ public void wakeup() {
+
+ // If external synchronization and pooling works properly,
+ // this cannot happen. Just a sanity check.
+ if (this.waiter == null) {
+ throw new IllegalStateException
+ ("Nobody waiting on this object.");
+ }
+
+ // One condition might be shared by several WaitingThread instances.
+ // It probably isn't, but just in case: wake all, not just one.
+ this.cond.signalAll();
+ }
+
+ public void interrupt() {
+ aborted = true;
+ this.cond.signalAll();
+ }
+
+
+} // class WaitingThread
diff --git a/src/org/apache/http/impl/conn/tsccm/WaitingThreadAborter.java b/src/org/apache/http/impl/conn/tsccm/WaitingThreadAborter.java
new file mode 100644
index 0000000..1844457
--- /dev/null
+++ b/src/org/apache/http/impl/conn/tsccm/WaitingThreadAborter.java
@@ -0,0 +1,62 @@
+/*
+ * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/WaitingThreadAborter.java $
+ * $Revision: 649220 $
+ * $Date: 2008-04-17 11:40:24 -0700 (Thu, 17 Apr 2008) $
+ *
+ * ====================================================================
+ *
+ * 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;
+
+/** A simple class that can interrupt a {@link WaitingThread}. */
+public class WaitingThreadAborter {
+
+ private WaitingThread waitingThread;
+ private boolean aborted;
+
+ /**
+ * If a waiting thread has been set, interrupts it.
+ */
+ public void abort() {
+ aborted = true;
+
+ if (waitingThread != null)
+ waitingThread.interrupt();
+
+ }
+
+ /**
+ * Sets the waiting thread. If this has already been aborted,
+ * the waiting thread is immediately interrupted.
+ *
+ * @param waitingThread The thread to interrupt when aborting.
+ */
+ public void setWaitingThread(WaitingThread waitingThread) {
+ this.waitingThread = waitingThread;
+ if (aborted)
+ waitingThread.interrupt();
+ }
+
+}
diff --git a/src/org/apache/http/impl/conn/tsccm/doc-files/tsccm-structure.png b/src/org/apache/http/impl/conn/tsccm/doc-files/tsccm-structure.png
new file mode 100644
index 0000000..2e2820d
--- /dev/null
+++ b/src/org/apache/http/impl/conn/tsccm/doc-files/tsccm-structure.png
Binary files differ
diff --git a/src/org/apache/http/impl/conn/tsccm/package.html b/src/org/apache/http/impl/conn/tsccm/package.html
new file mode 100644
index 0000000..5aca5d4
--- /dev/null
+++ b/src/org/apache/http/impl/conn/tsccm/package.html
@@ -0,0 +1,205 @@
+<html>
+<head>
+<!--
+/*
+ * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/package.html $
+ * $Revision: 653041 $
+ * $Date: 2008-05-03 03:39:28 -0700 (Sat, 03 May 2008) $
+ *
+ * ====================================================================
+ * 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/>.
+ *
+ */
+-->
+</head>
+<body>
+
+The implementation of a thread-safe client connection manager.
+
+<center>
+<img src="doc-files/tsccm-structure.png" alt="Relation Diagram"/>
+</center>
+
+<p>
+The implementation is structured into three areas, as illustrated
+by the diagram above.
+Facing the application is the <i>Manager</i> (green), which internally
+maintains a <i>Pool</i> (yellow) of connections and waiting threads.
+Both Manager and Pool rely on <i>Operations</i> (cyan) to provide the
+actual connections.
+</p>
+<p>
+In order to allow connection garbage collection, it is
+imperative that hard object references between the areas are
+restricted to the relations indicated by arrows in the diagram:
+</p>
+<ul>
+<li>Applications reference only the Manager objects.</li>
+<li>Manager objects reference Pool objects, but not vice versa.</li>
+<li>Operations objects do not reference either Manager or Pool objects.</li>
+</ul>
+
+<p>
+The following table shows a selection of classes and interfaces,
+and their assignment to the three areas.
+</p>
+<center>
+<table border="1">
+<colgroup>
+ <col width="50%"/>
+ <col width="50%"/>
+</colgroup>
+
+<tr>
+<td style="text-align: center; background-color: #00ff00;">
+{@link org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager}
+</td>
+<td style="text-align: center; background-color: #ffff00;">
+{@link org.apache.http.impl.conn.tsccm.AbstractConnPool}
+</td>
+</tr>
+
+<tr>
+<td style="text-align: center; background-color: #00ff00;">
+{@link org.apache.http.impl.conn.tsccm.BasicPooledConnAdapter}
+</td>
+<td style="text-align: center; background-color: #ffff00;">
+{@link org.apache.http.impl.conn.tsccm.ConnPoolByRoute}
+</td>
+</tr>
+
+<!-- appears on both sides! -->
+
+<tr>
+<td style="text-align: right; background-color: #00ff00;">
+{@link org.apache.http.impl.conn.tsccm.BasicPoolEntry}
+</td>
+<td style="text-align: left; background-color: #ffff00;">
+{@link org.apache.http.impl.conn.tsccm.BasicPoolEntry}
+</td>
+</tr>
+
+<!-- ====================== -->
+
+<tr style="border-width: 5px;">
+</tr>
+
+<tr>
+<td colspan="2" style="text-align: center; background-color: #00ffff;">
+{@link org.apache.http.conn.ClientConnectionOperator}
+</td>
+</tr>
+
+<tr>
+<td colspan="2" style="text-align: center; background-color: #00ffff;">
+{@link org.apache.http.conn.OperatedClientConnection}
+</td>
+</tr>
+
+</table>
+</center>
+
+<p>
+The Manager area has implementations for the connection management
+interfaces {@link org.apache.http.conn.ClientConnectionManager}
+and {@link org.apache.http.conn.ManagedClientConnection}.
+The latter is an adapter from managed to operated connections, based on a
+{@link org.apache.http.impl.conn.tsccm.BasicPoolEntry}.
+<br/>
+The Pool area shows an abstract pool class
+{@link org.apache.http.impl.conn.tsccm.AbstractConnPool}
+and a concrete implementation
+{@link org.apache.http.impl.conn.tsccm.ConnPoolByRoute}
+which uses the same basic algorithm as the
+<code>MultiThreadedHttpConnectionManager</code>
+in HttpClient 3.x.
+A pool contains instances of
+{@link org.apache.http.impl.conn.tsccm.BasicPoolEntry}.
+Most other classes in this package also belong to the Pool area.
+<br/>
+In the Operations area, you will find only the interfaces for
+operated connections as defined in the org.apache.http.conn package.
+The connection manager will work with all correct implementations
+of these interfaces. This package therefore does not define anything
+specific to the Operations area.
+</p>
+
+<p>
+As you have surely noticed, the
+{@link org.apache.http.impl.conn.tsccm.BasicPoolEntry}
+appears in both the Manager and Pool areas.
+This is where things get tricky for connection garbage collection.
+<br/>
+A connection pool may start a background thread to implement cleanup.
+In that case, the connection pool will not be garbage collected until
+it is shut down, since the background thread keeps a hard reference
+to the pool. The pool itself keeps hard references to the pooled entries,
+which in turn reference idle connections. Neither of these is subject
+to garbage collection.
+Only the shutdown of the pool will stop the background thread,
+thereby enabling garbage collection of the pool objects.
+<br/>
+A pool entry that is passed to an application by means of a connection
+adapter will move from the Pool area to the Manager area. When the
+connection is released by the application, the manager returns the
+entry back to the pool. With that step, the pool entry moves from
+the Manager area back to the Pool area.
+While the entry is in the Manager area, the pool MUST NOT keep a
+hard reference to it.
+</p>
+
+<p>
+The purpose of connection garbage collection is to detect when an
+application fails to return a connection. In order to achieve this,
+the only hard reference to the pool entry in the Manager area is
+in the connection wrapper. The manager will not keep a hard reference
+to the connection wrapper either, since that wrapper is effectively
+moving to the Application area.
+If the application drops it's reference to the connection wrapper,
+that wrapper will be garbage collected, and with it the pool entry.
+<br/>
+In order to detect garbage collection of pool entries handed out
+to the application, the pool keeps a <i>weak reference</i> to the
+entry. Instances of
+{@link org.apache.http.impl.conn.tsccm.BasicPoolEntryRef}
+combine the weak reference with information about the route for
+which the pool entry was allocated. If one of these entry references
+becomes stale, the pool can accommodate for the lost connection.
+This is triggered either by a background thread waiting for the
+references to be queued by the garbage collector, or by the
+application calling a {@link
+ org.apache.http.conn.ClientConnectionManager#closeIdleConnections cleanup}
+method of the connection manager.
+<br/>
+Basically the same trick is used for detecting garbage collection
+of the connection manager itself. The pool keeps a weak reference
+to the connection manager that created it. However, this will work
+only if there is a background thread to detect when that reference
+is queued by the garbage collector. Otherwise, a finalizer of the
+connection manager will shut down the pool and release it's resources.
+</p>
+
+
+</body>
+</html>