View Javadoc

1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  package org.apache.http.impl.conn.tsccm;
28  
29  import java.io.IOException;
30  import java.util.Date;
31  import java.util.HashMap;
32  import java.util.Iterator;
33  import java.util.LinkedList;
34  import java.util.Map;
35  import java.util.Queue;
36  import java.util.Set;
37  import java.util.concurrent.TimeUnit;
38  import java.util.concurrent.locks.Condition;
39  import java.util.concurrent.locks.Lock;
40  
41  import org.apache.commons.logging.Log;
42  import org.apache.commons.logging.LogFactory;
43  import org.apache.http.conn.ClientConnectionOperator;
44  import org.apache.http.conn.ConnectionPoolTimeoutException;
45  import org.apache.http.conn.OperatedClientConnection;
46  import org.apache.http.conn.params.ConnManagerParams;
47  import org.apache.http.conn.params.ConnPerRoute;
48  import org.apache.http.conn.routing.HttpRoute;
49  import org.apache.http.params.HttpParams;
50  import org.apache.http.util.Args;
51  import org.apache.http.util.Asserts;
52  
53  /**
54   * A connection pool that maintains connections by route.
55   * This class is derived from {@code MultiThreadedHttpConnectionManager}
56   * in HttpClient 3.x, see there for original authors. It implements the same
57   * algorithm for connection re-use and connection-per-host enforcement:
58   * <ul>
59   * <li>connections are re-used only for the exact same route</li>
60   * <li>connection limits are enforced per route rather than per host</li>
61   * </ul>
62   * Note that access to the pool data structures is synchronized via the
63   * {@link AbstractConnPool#poolLock poolLock} in the base class,
64   * not via {@code synchronized} methods.
65   *
66   * @since 4.0
67   *
68   * @deprecated (4.2)  use {@link org.apache.http.pool.AbstractConnPool}
69   */
70  @Deprecated
71  public class ConnPoolByRoute extends AbstractConnPool {
72  
73      private final Log log = LogFactory.getLog(getClass());
74  
75      private final Lock poolLock;
76  
77      /** Connection operator for this pool */
78      protected final ClientConnectionOperator operator;
79  
80      /** Connections per route lookup */
81      protected final ConnPerRoute connPerRoute;
82  
83      /** References to issued connections */
84      protected final Set<BasicPoolEntry> leasedConnections;
85  
86      /** The list of free connections */
87      protected final Queue<BasicPoolEntry> freeConnections;
88  
89      /** The list of WaitingThreads waiting for a connection */
90      protected final Queue<WaitingThread> waitingThreads;
91  
92      /** Map of route-specific pools */
93      protected final Map<HttpRoute, RouteSpecificPool> routeToPool;
94  
95      private final long connTTL;
96  
97      private final TimeUnit connTTLTimeUnit;
98  
99      protected volatile boolean shutdown;
100 
101     protected volatile int maxTotalConnections;
102 
103     protected volatile int numConnections;
104 
105     /**
106      * Creates a new connection pool, managed by route.
107      *
108      * @since 4.1
109      */
110     public ConnPoolByRoute(
111             final ClientConnectionOperator operator,
112             final ConnPerRoute connPerRoute,
113             final int maxTotalConnections) {
114         this(operator, connPerRoute, maxTotalConnections, -1, TimeUnit.MILLISECONDS);
115     }
116 
117     /**
118      * @since 4.1
119      */
120     public ConnPoolByRoute(
121             final ClientConnectionOperator operator,
122             final ConnPerRoute connPerRoute,
123             final int maxTotalConnections,
124             final long connTTL,
125             final TimeUnit connTTLTimeUnit) {
126         super();
127         Args.notNull(operator, "Connection operator");
128         Args.notNull(connPerRoute, "Connections per route");
129         this.poolLock = super.poolLock;
130         this.leasedConnections = super.leasedConnections;
131         this.operator = operator;
132         this.connPerRoute = connPerRoute;
133         this.maxTotalConnections = maxTotalConnections;
134         this.freeConnections = createFreeConnQueue();
135         this.waitingThreads  = createWaitingThreadQueue();
136         this.routeToPool     = createRouteToPoolMap();
137         this.connTTL = connTTL;
138         this.connTTLTimeUnit = connTTLTimeUnit;
139     }
140 
141     protected Lock getLock() {
142         return this.poolLock;
143     }
144 
145     /**
146      * Creates a new connection pool, managed by route.
147      *
148      * @deprecated (4.1)  use {@link ConnPoolByRoute#ConnPoolByRoute(ClientConnectionOperator, ConnPerRoute, int)}
149      */
150     @Deprecated
151     public ConnPoolByRoute(final ClientConnectionOperator operator, final HttpParams params) {
152         this(operator,
153                 ConnManagerParams.getMaxConnectionsPerRoute(params),
154                 ConnManagerParams.getMaxTotalConnections(params));
155     }
156 
157     /**
158      * Creates the queue for {@link #freeConnections}.
159      * Called once by the constructor.
160      *
161      * @return  a queue
162      */
163     protected Queue<BasicPoolEntry> createFreeConnQueue() {
164         return new LinkedList<BasicPoolEntry>();
165     }
166 
167     /**
168      * Creates the queue for {@link #waitingThreads}.
169      * Called once by the constructor.
170      *
171      * @return  a queue
172      */
173     protected Queue<WaitingThread> createWaitingThreadQueue() {
174         return new LinkedList<WaitingThread>();
175     }
176 
177     /**
178      * Creates the map for {@link #routeToPool}.
179      * Called once by the constructor.
180      *
181      * @return  a map
182      */
183     protected Map<HttpRoute, RouteSpecificPool> createRouteToPoolMap() {
184         return new HashMap<HttpRoute, RouteSpecificPool>();
185     }
186 
187 
188     /**
189      * Creates a new route-specific pool.
190      * Called by {@link #getRoutePool} when necessary.
191      *
192      * @param route     the route
193      *
194      * @return  the new pool
195      */
196     protected RouteSpecificPool newRouteSpecificPool(final HttpRoute route) {
197         return new RouteSpecificPool(route, this.connPerRoute);
198     }
199 
200 
201     /**
202      * Creates a new waiting thread.
203      * Called by {@link #getRoutePool} when necessary.
204      *
205      * @param cond      the condition to wait for
206      * @param rospl     the route specific pool, or {@code null}
207      *
208      * @return  a waiting thread representation
209      */
210     protected WaitingThread newWaitingThread(final Condition cond,
211                                              final RouteSpecificPool rospl) {
212         return new WaitingThread(cond, rospl);
213     }
214 
215     private void closeConnection(final BasicPoolEntry entry) {
216         final OperatedClientConnection conn = entry.getConnection();
217         if (conn != null) {
218             try {
219                 conn.close();
220             } catch (final IOException ex) {
221                 log.debug("I/O error closing connection", ex);
222             }
223         }
224     }
225 
226     /**
227      * Get a route-specific pool of available connections.
228      *
229      * @param route   the route
230      * @param create    whether to create the pool if it doesn't exist
231      *
232      * @return  the pool for the argument route,
233      *     never {@code null} if {@code create} is {@code true}
234      */
235     protected RouteSpecificPool getRoutePool(final HttpRoute route,
236                                              final boolean create) {
237         RouteSpecificPool rospl = null;
238         poolLock.lock();
239         try {
240 
241             rospl = routeToPool.get(route);
242             if ((rospl == null) && create) {
243                 // no pool for this route yet (or anymore)
244                 rospl = newRouteSpecificPool(route);
245                 routeToPool.put(route, rospl);
246             }
247 
248         } finally {
249             poolLock.unlock();
250         }
251 
252         return rospl;
253     }
254 
255     public int getConnectionsInPool(final HttpRoute route) {
256         poolLock.lock();
257         try {
258             // don't allow a pool to be created here!
259             final RouteSpecificPool rospl = getRoutePool(route, false);
260             return (rospl != null) ? rospl.getEntryCount() : 0;
261 
262         } finally {
263             poolLock.unlock();
264         }
265     }
266 
267     public int getConnectionsInPool() {
268         poolLock.lock();
269         try {
270             return numConnections;
271         } finally {
272             poolLock.unlock();
273         }
274     }
275 
276     @Override
277     public PoolEntryRequest requestPoolEntry(
278             final HttpRoute route,
279             final Object state) {
280 
281         final WaitingThreadAborter aborter = new WaitingThreadAborter();
282 
283         return new PoolEntryRequest() {
284 
285             @Override
286             public void abortRequest() {
287                 poolLock.lock();
288                 try {
289                     aborter.abort();
290                 } finally {
291                     poolLock.unlock();
292                 }
293             }
294 
295             @Override
296             public BasicPoolEntry getPoolEntry(
297                     final long timeout,
298                     final TimeUnit tunit)
299                         throws InterruptedException, ConnectionPoolTimeoutException {
300                 return getEntryBlocking(route, state, timeout, tunit, aborter);
301             }
302 
303         };
304     }
305 
306     /**
307      * Obtains a pool entry with a connection within the given timeout.
308      * If a {@link WaitingThread} is used to block, {@link WaitingThreadAborter#setWaitingThread(WaitingThread)}
309      * must be called before blocking, to allow the thread to be interrupted.
310      *
311      * @param route     the route for which to get the connection
312      * @param timeout   the timeout, 0 or negative for no timeout
313      * @param tunit     the unit for the {@code timeout},
314      *                  may be {@code null} only if there is no timeout
315      * @param aborter   an object which can abort a {@link WaitingThread}.
316      *
317      * @return  pool entry holding a connection for the route
318      *
319      * @throws ConnectionPoolTimeoutException
320      *         if the timeout expired
321      * @throws InterruptedException
322      *         if the calling thread was interrupted
323      */
324     protected BasicPoolEntry getEntryBlocking(
325                                    final HttpRoute route, final Object state,
326                                    final long timeout, final TimeUnit tunit,
327                                    final WaitingThreadAborter aborter)
328         throws ConnectionPoolTimeoutException, InterruptedException {
329 
330         Date deadline = null;
331         if (timeout > 0) {
332             deadline = new Date
333                 (System.currentTimeMillis() + tunit.toMillis(timeout));
334         }
335 
336         BasicPoolEntry entry = null;
337         poolLock.lock();
338         try {
339 
340             RouteSpecificPool rospl = getRoutePool(route, true);
341             WaitingThread waitingThread = null;
342 
343             while (entry == null) {
344                 Asserts.check(!shutdown, "Connection pool shut down");
345 
346                 if (log.isDebugEnabled()) {
347                     log.debug("[" + route + "] total kept alive: " + freeConnections.size() +
348                             ", total issued: " + leasedConnections.size() +
349                             ", total allocated: " + numConnections + " out of " + maxTotalConnections);
350                 }
351 
352                 // the cases to check for:
353                 // - have a free connection for that route
354                 // - allowed to create a free connection for that route
355                 // - can delete and replace a free connection for another route
356                 // - need to wait for one of the things above to come true
357 
358                 entry = getFreeEntry(rospl, state);
359                 if (entry != null) {
360                     break;
361                 }
362 
363                 final boolean hasCapacity = rospl.getCapacity() > 0;
364 
365                 if (log.isDebugEnabled()) {
366                     log.debug("Available capacity: " + rospl.getCapacity()
367                             + " out of " + rospl.getMaxEntries()
368                             + " [" + route + "][" + state + "]");
369                 }
370 
371                 if (hasCapacity && numConnections < maxTotalConnections) {
372 
373                     entry = createEntry(rospl, operator);
374 
375                 } else if (hasCapacity && !freeConnections.isEmpty()) {
376 
377                     deleteLeastUsedEntry();
378                     // if least used entry's route was the same as rospl,
379                     // rospl is now out of date : we preemptively refresh
380                     rospl = getRoutePool(route, true);
381                     entry = createEntry(rospl, operator);
382 
383                 } else {
384 
385                     if (log.isDebugEnabled()) {
386                         log.debug("Need to wait for connection" +
387                                 " [" + route + "][" + state + "]");
388                     }
389 
390                     if (waitingThread == null) {
391                         waitingThread =
392                             newWaitingThread(poolLock.newCondition(), rospl);
393                         aborter.setWaitingThread(waitingThread);
394                     }
395 
396                     boolean success = false;
397                     try {
398                         rospl.queueThread(waitingThread);
399                         waitingThreads.add(waitingThread);
400                         success = waitingThread.await(deadline);
401 
402                     } finally {
403                         // In case of 'success', we were woken up by the
404                         // connection pool and should now have a connection
405                         // waiting for us, or else we're shutting down.
406                         // Just continue in the loop, both cases are checked.
407                         rospl.removeThread(waitingThread);
408                         waitingThreads.remove(waitingThread);
409                     }
410 
411                     // check for spurious wakeup vs. timeout
412                     if (!success && (deadline != null) &&
413                         (deadline.getTime() <= System.currentTimeMillis())) {
414                         throw new ConnectionPoolTimeoutException
415                             ("Timeout waiting for connection from pool");
416                     }
417                 }
418             } // while no entry
419 
420         } finally {
421             poolLock.unlock();
422         }
423         return entry;
424     }
425 
426     @Override
427     public void freeEntry(final BasicPoolEntry entry, final boolean reusable, final long validDuration, final TimeUnit timeUnit) {
428 
429         final HttpRoute route = entry.getPlannedRoute();
430         if (log.isDebugEnabled()) {
431             log.debug("Releasing connection" +
432                     " [" + route + "][" + entry.getState() + "]");
433         }
434 
435         poolLock.lock();
436         try {
437             if (shutdown) {
438                 // the pool is shut down, release the
439                 // connection's resources and get out of here
440                 closeConnection(entry);
441                 return;
442             }
443 
444             // no longer issued, we keep a hard reference now
445             leasedConnections.remove(entry);
446 
447             final RouteSpecificPool rospl = getRoutePool(route, true);
448 
449             if (reusable && rospl.getCapacity() >= 0) {
450                 if (log.isDebugEnabled()) {
451                     final String s;
452                     if (validDuration > 0) {
453                         s = "for " + validDuration + " " + timeUnit;
454                     } else {
455                         s = "indefinitely";
456                     }
457                     log.debug("Pooling connection" +
458                             " [" + route + "][" + entry.getState() + "]; keep alive " + s);
459                 }
460                 rospl.freeEntry(entry);
461                 entry.updateExpiry(validDuration, timeUnit);
462                 freeConnections.add(entry);
463             } else {
464                 closeConnection(entry);
465                 rospl.dropEntry();
466                 numConnections--;
467             }
468 
469             notifyWaitingThread(rospl);
470 
471         } finally {
472             poolLock.unlock();
473         }
474     }
475 
476     /**
477      * If available, get a free pool entry for a route.
478      *
479      * @param rospl       the route-specific pool from which to get an entry
480      *
481      * @return  an available pool entry for the given route, or
482      *          {@code null} if none is available
483      */
484     protected BasicPoolEntry getFreeEntry(final RouteSpecificPool rospl, final Object state) {
485 
486         BasicPoolEntry entry = null;
487         poolLock.lock();
488         try {
489             boolean done = false;
490             while(!done) {
491 
492                 entry = rospl.allocEntry(state);
493 
494                 if (entry != null) {
495                     if (log.isDebugEnabled()) {
496                         log.debug("Getting free connection"
497                                 + " [" + rospl.getRoute() + "][" + state + "]");
498 
499                     }
500                     freeConnections.remove(entry);
501                     if (entry.isExpired(System.currentTimeMillis())) {
502                         // If the free entry isn't valid anymore, get rid of it
503                         // and loop to find another one that might be valid.
504                         if (log.isDebugEnabled()) {
505                             log.debug("Closing expired free connection"
506                                     + " [" + rospl.getRoute() + "][" + state + "]");
507                         }
508                         closeConnection(entry);
509                         // We use dropEntry instead of deleteEntry because the entry
510                         // is no longer "free" (we just allocated it), and deleteEntry
511                         // can only be used to delete free entries.
512                         rospl.dropEntry();
513                         numConnections--;
514                     } else {
515                         leasedConnections.add(entry);
516                         done = true;
517                     }
518 
519                 } else {
520                     done = true;
521                     if (log.isDebugEnabled()) {
522                         log.debug("No free connections"
523                                 + " [" + rospl.getRoute() + "][" + state + "]");
524                     }
525                 }
526             }
527         } finally {
528             poolLock.unlock();
529         }
530         return entry;
531     }
532 
533 
534     /**
535      * Creates a new pool entry.
536      * This method assumes that the new connection will be handed
537      * out immediately.
538      *
539      * @param rospl       the route-specific pool for which to create the entry
540      * @param op        the operator for creating a connection
541      *
542      * @return  the new pool entry for a new connection
543      */
544     protected BasicPoolEntry createEntry(final RouteSpecificPool rospl,
545                                          final ClientConnectionOperator op) {
546 
547         if (log.isDebugEnabled()) {
548             log.debug("Creating new connection [" + rospl.getRoute() + "]");
549         }
550 
551         // the entry will create the connection when needed
552         final BasicPoolEntry entry = new BasicPoolEntry(op, rospl.getRoute(), connTTL, connTTLTimeUnit);
553 
554         poolLock.lock();
555         try {
556             rospl.createdEntry(entry);
557             numConnections++;
558             leasedConnections.add(entry);
559         } finally {
560             poolLock.unlock();
561         }
562 
563         return entry;
564     }
565 
566 
567     /**
568      * Deletes a given pool entry.
569      * This closes the pooled connection and removes all references,
570      * so that it can be GCed.
571      *
572      * <p><b>Note:</b> Does not remove the entry from the freeConnections list.
573      * It is assumed that the caller has already handled this step.</p>
574      * <!-- @@@ is that a good idea? or rather fix it? -->
575      *
576      * @param entry         the pool entry for the connection to delete
577      */
578     protected void deleteEntry(final BasicPoolEntry entry) {
579 
580         final HttpRoute route = entry.getPlannedRoute();
581 
582         if (log.isDebugEnabled()) {
583             log.debug("Deleting connection"
584                     + " [" + route + "][" + entry.getState() + "]");
585         }
586 
587         poolLock.lock();
588         try {
589 
590             closeConnection(entry);
591 
592             final RouteSpecificPool rospl = getRoutePool(route, true);
593             rospl.deleteEntry(entry);
594             numConnections--;
595             if (rospl.isUnused()) {
596                 routeToPool.remove(route);
597             }
598 
599         } finally {
600             poolLock.unlock();
601         }
602     }
603 
604 
605     /**
606      * Delete an old, free pool entry to make room for a new one.
607      * Used to replace pool entries with ones for a different route.
608      */
609     protected void deleteLeastUsedEntry() {
610         poolLock.lock();
611         try {
612 
613             final BasicPoolEntry entry = freeConnections.remove();
614 
615             if (entry != null) {
616                 deleteEntry(entry);
617             } else if (log.isDebugEnabled()) {
618                 log.debug("No free connection to delete");
619             }
620 
621         } finally {
622             poolLock.unlock();
623         }
624     }
625 
626     @Override
627     protected void handleLostEntry(final HttpRoute route) {
628 
629         poolLock.lock();
630         try {
631 
632             final RouteSpecificPool rospl = getRoutePool(route, true);
633             rospl.dropEntry();
634             if (rospl.isUnused()) {
635                 routeToPool.remove(route);
636             }
637 
638             numConnections--;
639             notifyWaitingThread(rospl);
640 
641         } finally {
642             poolLock.unlock();
643         }
644     }
645 
646     /**
647      * Notifies a waiting thread that a connection is available.
648      * This will wake a thread waiting in the specific route pool,
649      * if there is one.
650      * Otherwise, a thread in the connection pool will be notified.
651      *
652      * @param rospl     the pool in which to notify, or {@code null}
653      */
654     protected void notifyWaitingThread(final RouteSpecificPool rospl) {
655 
656         //@@@ while this strategy provides for best connection re-use,
657         //@@@ is it fair? only do this if the connection is open?
658         // Find the thread we are going to notify. We want to ensure that
659         // each waiting thread is only interrupted once, so we will remove
660         // it from all wait queues before interrupting.
661         WaitingThread waitingThread = null;
662 
663         poolLock.lock();
664         try {
665 
666             if ((rospl != null) && rospl.hasThread()) {
667                 if (log.isDebugEnabled()) {
668                     log.debug("Notifying thread waiting on pool" +
669                             " [" + rospl.getRoute() + "]");
670                 }
671                 waitingThread = rospl.nextThread();
672             } else if (!waitingThreads.isEmpty()) {
673                 if (log.isDebugEnabled()) {
674                     log.debug("Notifying thread waiting on any pool");
675                 }
676                 waitingThread = waitingThreads.remove();
677             } else if (log.isDebugEnabled()) {
678                 log.debug("Notifying no-one, there are no waiting threads");
679             }
680 
681             if (waitingThread != null) {
682                 waitingThread.wakeup();
683             }
684 
685         } finally {
686             poolLock.unlock();
687         }
688     }
689 
690 
691     @Override
692     public void deleteClosedConnections() {
693         poolLock.lock();
694         try {
695             final Iterator<BasicPoolEntry>  iter = freeConnections.iterator();
696             while (iter.hasNext()) {
697                 final BasicPoolEntry entry = iter.next();
698                 if (!entry.getConnection().isOpen()) {
699                     iter.remove();
700                     deleteEntry(entry);
701                 }
702             }
703         } finally {
704             poolLock.unlock();
705         }
706     }
707 
708     /**
709      * Closes idle connections.
710      *
711      * @param idletime  the time the connections should have been idle
712      *                  in order to be closed now
713      * @param tunit     the unit for the {@code idletime}
714      */
715     @Override
716     public void closeIdleConnections(final long idletime, final TimeUnit tunit) {
717         Args.notNull(tunit, "Time unit");
718         final long t = idletime > 0 ? idletime : 0;
719         if (log.isDebugEnabled()) {
720             log.debug("Closing connections idle longer than "  + t + " " + tunit);
721         }
722         // the latest time for which connections will be closed
723         final long deadline = System.currentTimeMillis() - tunit.toMillis(t);
724         poolLock.lock();
725         try {
726             final Iterator<BasicPoolEntry>  iter = freeConnections.iterator();
727             while (iter.hasNext()) {
728                 final BasicPoolEntry entry = iter.next();
729                 if (entry.getUpdated() <= deadline) {
730                     if (log.isDebugEnabled()) {
731                         log.debug("Closing connection last used @ " + new Date(entry.getUpdated()));
732                     }
733                     iter.remove();
734                     deleteEntry(entry);
735                 }
736             }
737         } finally {
738             poolLock.unlock();
739         }
740     }
741 
742     @Override
743     public void closeExpiredConnections() {
744         log.debug("Closing expired connections");
745         final long now = System.currentTimeMillis();
746 
747         poolLock.lock();
748         try {
749             final Iterator<BasicPoolEntry>  iter = freeConnections.iterator();
750             while (iter.hasNext()) {
751                 final BasicPoolEntry entry = iter.next();
752                 if (entry.isExpired(now)) {
753                     if (log.isDebugEnabled()) {
754                         log.debug("Closing connection expired @ " + new Date(entry.getExpiry()));
755                     }
756                     iter.remove();
757                     deleteEntry(entry);
758                 }
759             }
760         } finally {
761             poolLock.unlock();
762         }
763     }
764 
765     @Override
766     public void shutdown() {
767         poolLock.lock();
768         try {
769             if (shutdown) {
770                 return;
771             }
772             shutdown = true;
773 
774             // close all connections that are issued to an application
775             final Iterator<BasicPoolEntry> iter1 = leasedConnections.iterator();
776             while (iter1.hasNext()) {
777                 final BasicPoolEntry entry = iter1.next();
778                 iter1.remove();
779                 closeConnection(entry);
780             }
781 
782             // close all free connections
783             final Iterator<BasicPoolEntry> iter2 = freeConnections.iterator();
784             while (iter2.hasNext()) {
785                 final BasicPoolEntry entry = iter2.next();
786                 iter2.remove();
787 
788                 if (log.isDebugEnabled()) {
789                     log.debug("Closing connection"
790                             + " [" + entry.getPlannedRoute() + "][" + entry.getState() + "]");
791                 }
792                 closeConnection(entry);
793             }
794 
795             // wake up all waiting threads
796             final Iterator<WaitingThread> iwth = waitingThreads.iterator();
797             while (iwth.hasNext()) {
798                 final WaitingThread waiter = iwth.next();
799                 iwth.remove();
800                 waiter.wakeup();
801             }
802 
803             routeToPool.clear();
804 
805         } finally {
806             poolLock.unlock();
807         }
808     }
809 
810     /**
811      * since 4.1
812      */
813     public void setMaxTotalConnections(final int max) {
814         poolLock.lock();
815         try {
816             maxTotalConnections = max;
817         } finally {
818             poolLock.unlock();
819         }
820     }
821 
822 
823     /**
824      * since 4.1
825      */
826     public int getMaxTotalConnections() {
827         return maxTotalConnections;
828     }
829 
830 }
831