1/*
2 * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/AbstractConnPool.java $
3 * $Revision: 673450 $
4 * $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 Jul 2008) $
5 *
6 * ====================================================================
7 *
8 *  Licensed to the Apache Software Foundation (ASF) under one or more
9 *  contributor license agreements.  See the NOTICE file distributed with
10 *  this work for additional information regarding copyright ownership.
11 *  The ASF licenses this file to You under the Apache License, Version 2.0
12 *  (the "License"); you may not use this file except in compliance with
13 *  the License.  You may obtain a copy of the License at
14 *
15 *      http://www.apache.org/licenses/LICENSE-2.0
16 *
17 *  Unless required by applicable law or agreed to in writing, software
18 *  distributed under the License is distributed on an "AS IS" BASIS,
19 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 *  See the License for the specific language governing permissions and
21 *  limitations under the License.
22 * ====================================================================
23 *
24 * This software consists of voluntary contributions made by many
25 * individuals on behalf of the Apache Software Foundation.  For more
26 * information on the Apache Software Foundation, please see
27 * <http://www.apache.org/>.
28 *
29 */
30
31package org.apache.http.impl.conn.tsccm;
32
33import java.io.IOException;
34import java.lang.ref.Reference;
35import java.lang.ref.ReferenceQueue;
36import java.util.Set;
37import java.util.HashSet;
38import java.util.Iterator;
39import java.util.concurrent.TimeUnit;
40import java.util.concurrent.locks.Lock;
41import java.util.concurrent.locks.ReentrantLock;
42
43import org.apache.commons.logging.Log;
44import org.apache.commons.logging.LogFactory;
45import org.apache.http.conn.ConnectionPoolTimeoutException;
46import org.apache.http.conn.OperatedClientConnection;
47import org.apache.http.conn.routing.HttpRoute;
48import org.apache.http.impl.conn.IdleConnectionHandler;
49
50
51/**
52 * An abstract connection pool.
53 * It is used by the {@link ThreadSafeClientConnManager}.
54 * The abstract pool includes a {@link #poolLock}, which is used to
55 * synchronize access to the internal pool datastructures.
56 * Don't use <code>synchronized</code> for that purpose!
57 */
58public abstract class AbstractConnPool implements RefQueueHandler {
59
60    private final Log log = LogFactory.getLog(getClass());
61
62    /**
63     * The global lock for this pool.
64     */
65    protected final Lock poolLock;
66
67
68    /**
69     * References to issued connections.
70     * Objects in this set are of class
71     * {@link BasicPoolEntryRef BasicPoolEntryRef},
72     * and point to the pool entry for the issued connection.
73     * GCed connections are detected by the missing pool entries.
74     */
75    protected Set<BasicPoolEntryRef> issuedConnections;
76
77    /** The handler for idle connections. */
78    protected IdleConnectionHandler idleConnHandler;
79
80    /** The current total number of connections. */
81    protected int numConnections;
82
83    /**
84     * A reference queue to track loss of pool entries to GC.
85     * The same queue is used to track loss of the connection manager,
86     * so we cannot specialize the type.
87     */
88    protected ReferenceQueue<Object> refQueue;
89
90    /** A worker (thread) to track loss of pool entries to GC. */
91    private RefQueueWorker refWorker;
92
93
94    /** Indicates whether this pool is shut down. */
95    protected volatile boolean isShutDown;
96
97    /**
98     * Creates a new connection pool.
99     */
100    protected AbstractConnPool() {
101        issuedConnections = new HashSet<BasicPoolEntryRef>();
102        idleConnHandler = new IdleConnectionHandler();
103
104        boolean fair = false; //@@@ check parameters to decide
105        poolLock = new ReentrantLock(fair);
106    }
107
108
109    /**
110     * Enables connection garbage collection (GC).
111     * This method must be called immediately after creating the
112     * connection pool. It is not possible to enable connection GC
113     * after pool entries have been created. Neither is it possible
114     * to disable connection GC.
115     *
116     * @throws IllegalStateException
117     *         if connection GC is already enabled, or if it cannot be
118     *         enabled because there already are pool entries
119     */
120    public void enableConnectionGC()
121        throws IllegalStateException {
122
123        if (refQueue != null) {
124            throw new IllegalStateException("Connection GC already enabled.");
125        }
126        poolLock.lock();
127        try {
128            if (numConnections > 0) { //@@@ is this check sufficient?
129                throw new IllegalStateException("Pool already in use.");
130            }
131        } finally {
132            poolLock.unlock();
133        }
134
135        refQueue  = new ReferenceQueue<Object>();
136        refWorker = new RefQueueWorker(refQueue, this);
137        Thread t = new Thread(refWorker); //@@@ use a thread factory
138        t.setDaemon(true);
139        t.setName("RefQueueWorker@" + this);
140        t.start();
141    }
142
143
144    /**
145     * Obtains a pool entry with a connection within the given timeout.
146     *
147     * @param route     the route for which to get the connection
148     * @param timeout   the timeout, 0 or negative for no timeout
149     * @param tunit     the unit for the <code>timeout</code>,
150     *                  may be <code>null</code> only if there is no timeout
151     *
152     * @return  pool entry holding a connection for the route
153     *
154     * @throws ConnectionPoolTimeoutException
155     *         if the timeout expired
156     * @throws InterruptedException
157     *         if the calling thread was interrupted
158     */
159    public final
160        BasicPoolEntry getEntry(
161                HttpRoute route,
162                Object state,
163                long timeout,
164                TimeUnit tunit)
165                    throws ConnectionPoolTimeoutException, InterruptedException {
166        return requestPoolEntry(route, state).getPoolEntry(timeout, tunit);
167    }
168
169    /**
170     * Returns a new {@link PoolEntryRequest}, from which a {@link BasicPoolEntry}
171     * can be obtained, or the request can be aborted.
172     */
173    public abstract PoolEntryRequest requestPoolEntry(HttpRoute route, Object state);
174
175
176    /**
177     * Returns an entry into the pool.
178     * The connection of the entry is expected to be in a suitable state,
179     * either open and re-usable, or closed. The pool will not make any
180     * attempt to determine whether it can be re-used or not.
181     *
182     * @param entry     the entry for the connection to release
183     * @param reusable  <code>true</code> if the entry is deemed
184     *                  reusable, <code>false</code> otherwise.
185     * @param validDuration The duration that the entry should remain free and reusable.
186     * @param timeUnit The unit of time the duration is measured in.
187     */
188    public abstract void freeEntry(BasicPoolEntry entry, boolean reusable, long validDuration, TimeUnit timeUnit)
189        ;
190
191
192
193    // non-javadoc, see interface RefQueueHandler
194// BEGIN android-changed
195    public void handleReference(Reference ref) {
196// END android-changed
197        poolLock.lock();
198        try {
199
200            if (ref instanceof BasicPoolEntryRef) {
201                // check if the GCed pool entry was still in use
202                //@@@ find a way to detect this without lookup
203                //@@@ flag in the BasicPoolEntryRef, to be reset when freed?
204                final boolean lost = issuedConnections.remove(ref);
205                if (lost) {
206                    final HttpRoute route =
207                        ((BasicPoolEntryRef)ref).getRoute();
208                    if (log.isDebugEnabled()) {
209                        log.debug("Connection garbage collected. " + route);
210                    }
211                    handleLostEntry(route);
212                }
213            }
214
215        } finally {
216            poolLock.unlock();
217        }
218    }
219
220
221    /**
222     * Handles cleaning up for a lost pool entry with the given route.
223     * A lost pool entry corresponds to a connection that was
224     * garbage collected instead of being properly released.
225     *
226     * @param route     the route of the pool entry that was lost
227     */
228    protected abstract void handleLostEntry(HttpRoute route)
229        ;
230
231
232    /**
233     * Closes idle connections.
234     *
235     * @param idletime  the time the connections should have been idle
236     *                  in order to be closed now
237     * @param tunit     the unit for the <code>idletime</code>
238     */
239    public void closeIdleConnections(long idletime, TimeUnit tunit) {
240
241        // idletime can be 0 or negative, no problem there
242        if (tunit == null) {
243            throw new IllegalArgumentException("Time unit must not be null.");
244        }
245
246        poolLock.lock();
247        try {
248            idleConnHandler.closeIdleConnections(tunit.toMillis(idletime));
249        } finally {
250            poolLock.unlock();
251        }
252    }
253
254    public void closeExpiredConnections() {
255        poolLock.lock();
256        try {
257            idleConnHandler.closeExpiredConnections();
258        } finally {
259            poolLock.unlock();
260        }
261    }
262
263
264    //@@@ revise this cleanup stuff (closeIdle+deleteClosed), it's not good
265
266    /**
267     * Deletes all entries for closed connections.
268     */
269    public abstract void deleteClosedConnections()
270        ;
271
272
273    /**
274     * Shuts down this pool and all associated resources.
275     * Overriding methods MUST call the implementation here!
276     */
277    public void shutdown() {
278
279        poolLock.lock();
280        try {
281
282            if (isShutDown)
283                return;
284
285            // no point in monitoring GC anymore
286            if (refWorker != null)
287                refWorker.shutdown();
288
289            // close all connections that are issued to an application
290            Iterator<BasicPoolEntryRef> iter = issuedConnections.iterator();
291            while (iter.hasNext()) {
292                BasicPoolEntryRef per = iter.next();
293                iter.remove();
294                BasicPoolEntry entry = per.get();
295                if (entry != null) {
296                    closeConnection(entry.getConnection());
297                }
298            }
299
300            // remove all references to connections
301            //@@@ use this for shutting them down instead?
302            idleConnHandler.removeAll();
303
304            isShutDown = true;
305
306        } finally {
307            poolLock.unlock();
308        }
309    }
310
311
312    /**
313     * Closes a connection from this pool.
314     *
315     * @param conn      the connection to close, or <code>null</code>
316     */
317    protected void closeConnection(final OperatedClientConnection conn) {
318        if (conn != null) {
319            try {
320                conn.close();
321            } catch (IOException ex) {
322                log.debug("I/O error closing connection", ex);
323            }
324        }
325    }
326
327
328
329
330
331} // class AbstractConnPool
332
333