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             public void abortRequest() {
286                 poolLock.lock();
287                 try {
288                     aborter.abort();
289                 } finally {
290                     poolLock.unlock();
291                 }
292             }
293 
294             public BasicPoolEntry getPoolEntry(
295                     final long timeout,
296                     final TimeUnit tunit)
297                         throws InterruptedException, ConnectionPoolTimeoutException {
298                 return getEntryBlocking(route, state, timeout, tunit, aborter);
299             }
300 
301         };
302     }
303 
304     /**
305      * Obtains a pool entry with a connection within the given timeout.
306      * If a {@link WaitingThread} is used to block, {@link WaitingThreadAborter#setWaitingThread(WaitingThread)}
307      * must be called before blocking, to allow the thread to be interrupted.
308      *
309      * @param route     the route for which to get the connection
310      * @param timeout   the timeout, 0 or negative for no timeout
311      * @param tunit     the unit for the {@code timeout},
312      *                  may be {@code null} only if there is no timeout
313      * @param aborter   an object which can abort a {@link WaitingThread}.
314      *
315      * @return  pool entry holding a connection for the route
316      *
317      * @throws ConnectionPoolTimeoutException
318      *         if the timeout expired
319      * @throws InterruptedException
320      *         if the calling thread was interrupted
321      */
322     protected BasicPoolEntry getEntryBlocking(
323                                    final HttpRoute route, final Object state,
324                                    final long timeout, final TimeUnit tunit,
325                                    final WaitingThreadAborter aborter)
326         throws ConnectionPoolTimeoutException, InterruptedException {
327 
328         Date deadline = null;
329         if (timeout > 0) {
330             deadline = new Date
331                 (System.currentTimeMillis() + tunit.toMillis(timeout));
332         }
333 
334         BasicPoolEntry entry = null;
335         poolLock.lock();
336         try {
337 
338             RouteSpecificPool rospl = getRoutePool(route, true);
339             WaitingThread waitingThread = null;
340 
341             while (entry == null) {
342                 Asserts.check(!shutdown, "Connection pool shut down");
343 
344                 if (log.isDebugEnabled()) {
345                     log.debug("[" + route + "] total kept alive: " + freeConnections.size() +
346                             ", total issued: " + leasedConnections.size() +
347                             ", total allocated: " + numConnections + " out of " + maxTotalConnections);
348                 }
349 
350                 // the cases to check for:
351                 // - have a free connection for that route
352                 // - allowed to create a free connection for that route
353                 // - can delete and replace a free connection for another route
354                 // - need to wait for one of the things above to come true
355 
356                 entry = getFreeEntry(rospl, state);
357                 if (entry != null) {
358                     break;
359                 }
360 
361                 final boolean hasCapacity = rospl.getCapacity() > 0;
362 
363                 if (log.isDebugEnabled()) {
364                     log.debug("Available capacity: " + rospl.getCapacity()
365                             + " out of " + rospl.getMaxEntries()
366                             + " [" + route + "][" + state + "]");
367                 }
368 
369                 if (hasCapacity && numConnections < maxTotalConnections) {
370 
371                     entry = createEntry(rospl, operator);
372 
373                 } else if (hasCapacity && !freeConnections.isEmpty()) {
374 
375                     deleteLeastUsedEntry();
376                     // if least used entry's route was the same as rospl,
377                     // rospl is now out of date : we preemptively refresh
378                     rospl = getRoutePool(route, true);
379                     entry = createEntry(rospl, operator);
380 
381                 } else {
382 
383                     if (log.isDebugEnabled()) {
384                         log.debug("Need to wait for connection" +
385                                 " [" + route + "][" + state + "]");
386                     }
387 
388                     if (waitingThread == null) {
389                         waitingThread =
390                             newWaitingThread(poolLock.newCondition(), rospl);
391                         aborter.setWaitingThread(waitingThread);
392                     }
393 
394                     boolean success = false;
395                     try {
396                         rospl.queueThread(waitingThread);
397                         waitingThreads.add(waitingThread);
398                         success = waitingThread.await(deadline);
399 
400                     } finally {
401                         // In case of 'success', we were woken up by the
402                         // connection pool and should now have a connection
403                         // waiting for us, or else we're shutting down.
404                         // Just continue in the loop, both cases are checked.
405                         rospl.removeThread(waitingThread);
406                         waitingThreads.remove(waitingThread);
407                     }
408 
409                     // check for spurious wakeup vs. timeout
410                     if (!success && (deadline != null) &&
411                         (deadline.getTime() <= System.currentTimeMillis())) {
412                         throw new ConnectionPoolTimeoutException
413                             ("Timeout waiting for connection from pool");
414                     }
415                 }
416             } // while no entry
417 
418         } finally {
419             poolLock.unlock();
420         }
421         return entry;
422     }
423 
424     @Override
425     public void freeEntry(final BasicPoolEntry entry, final boolean reusable, final long validDuration, final TimeUnit timeUnit) {
426 
427         final HttpRoute route = entry.getPlannedRoute();
428         if (log.isDebugEnabled()) {
429             log.debug("Releasing connection" +
430                     " [" + route + "][" + entry.getState() + "]");
431         }
432 
433         poolLock.lock();
434         try {
435             if (shutdown) {
436                 // the pool is shut down, release the
437                 // connection's resources and get out of here
438                 closeConnection(entry);
439                 return;
440             }
441 
442             // no longer issued, we keep a hard reference now
443             leasedConnections.remove(entry);
444 
445             final RouteSpecificPool rospl = getRoutePool(route, true);
446 
447             if (reusable && rospl.getCapacity() >= 0) {
448                 if (log.isDebugEnabled()) {
449                     final String s;
450                     if (validDuration > 0) {
451                         s = "for " + validDuration + " " + timeUnit;
452                     } else {
453                         s = "indefinitely";
454                     }
455                     log.debug("Pooling connection" +
456                             " [" + route + "][" + entry.getState() + "]; keep alive " + s);
457                 }
458                 rospl.freeEntry(entry);
459                 entry.updateExpiry(validDuration, timeUnit);
460                 freeConnections.add(entry);
461             } else {
462                 closeConnection(entry);
463                 rospl.dropEntry();
464                 numConnections--;
465             }
466 
467             notifyWaitingThread(rospl);
468 
469         } finally {
470             poolLock.unlock();
471         }
472     }
473 
474     /**
475      * If available, get a free pool entry for a route.
476      *
477      * @param rospl       the route-specific pool from which to get an entry
478      *
479      * @return  an available pool entry for the given route, or
480      *          {@code null} if none is available
481      */
482     protected BasicPoolEntry getFreeEntry(final RouteSpecificPool rospl, final Object state) {
483 
484         BasicPoolEntry entry = null;
485         poolLock.lock();
486         try {
487             boolean done = false;
488             while(!done) {
489 
490                 entry = rospl.allocEntry(state);
491 
492                 if (entry != null) {
493                     if (log.isDebugEnabled()) {
494                         log.debug("Getting free connection"
495                                 + " [" + rospl.getRoute() + "][" + state + "]");
496 
497                     }
498                     freeConnections.remove(entry);
499                     if (entry.isExpired(System.currentTimeMillis())) {
500                         // If the free entry isn't valid anymore, get rid of it
501                         // and loop to find another one that might be valid.
502                         if (log.isDebugEnabled()) {
503                             log.debug("Closing expired free connection"
504                                     + " [" + rospl.getRoute() + "][" + state + "]");
505                         }
506                         closeConnection(entry);
507                         // We use dropEntry instead of deleteEntry because the entry
508                         // is no longer "free" (we just allocated it), and deleteEntry
509                         // can only be used to delete free entries.
510                         rospl.dropEntry();
511                         numConnections--;
512                     } else {
513                         leasedConnections.add(entry);
514                         done = true;
515                     }
516 
517                 } else {
518                     done = true;
519                     if (log.isDebugEnabled()) {
520                         log.debug("No free connections"
521                                 + " [" + rospl.getRoute() + "][" + state + "]");
522                     }
523                 }
524             }
525         } finally {
526             poolLock.unlock();
527         }
528         return entry;
529     }
530 
531 
532     /**
533      * Creates a new pool entry.
534      * This method assumes that the new connection will be handed
535      * out immediately.
536      *
537      * @param rospl       the route-specific pool for which to create the entry
538      * @param op        the operator for creating a connection
539      *
540      * @return  the new pool entry for a new connection
541      */
542     protected BasicPoolEntry createEntry(final RouteSpecificPool rospl,
543                                          final ClientConnectionOperator op) {
544 
545         if (log.isDebugEnabled()) {
546             log.debug("Creating new connection [" + rospl.getRoute() + "]");
547         }
548 
549         // the entry will create the connection when needed
550         final BasicPoolEntry entry = new BasicPoolEntry(op, rospl.getRoute(), connTTL, connTTLTimeUnit);
551 
552         poolLock.lock();
553         try {
554             rospl.createdEntry(entry);
555             numConnections++;
556             leasedConnections.add(entry);
557         } finally {
558             poolLock.unlock();
559         }
560 
561         return entry;
562     }
563 
564 
565     /**
566      * Deletes a given pool entry.
567      * This closes the pooled connection and removes all references,
568      * so that it can be GCed.
569      *
570      * <p><b>Note:</b> Does not remove the entry from the freeConnections list.
571      * It is assumed that the caller has already handled this step.</p>
572      * <!-- @@@ is that a good idea? or rather fix it? -->
573      *
574      * @param entry         the pool entry for the connection to delete
575      */
576     protected void deleteEntry(final BasicPoolEntry entry) {
577 
578         final HttpRoute route = entry.getPlannedRoute();
579 
580         if (log.isDebugEnabled()) {
581             log.debug("Deleting connection"
582                     + " [" + route + "][" + entry.getState() + "]");
583         }
584 
585         poolLock.lock();
586         try {
587 
588             closeConnection(entry);
589 
590             final RouteSpecificPool rospl = getRoutePool(route, true);
591             rospl.deleteEntry(entry);
592             numConnections--;
593             if (rospl.isUnused()) {
594                 routeToPool.remove(route);
595             }
596 
597         } finally {
598             poolLock.unlock();
599         }
600     }
601 
602 
603     /**
604      * Delete an old, free pool entry to make room for a new one.
605      * Used to replace pool entries with ones for a different route.
606      */
607     protected void deleteLeastUsedEntry() {
608         poolLock.lock();
609         try {
610 
611             final BasicPoolEntry entry = freeConnections.remove();
612 
613             if (entry != null) {
614                 deleteEntry(entry);
615             } else if (log.isDebugEnabled()) {
616                 log.debug("No free connection to delete");
617             }
618 
619         } finally {
620             poolLock.unlock();
621         }
622     }
623 
624     @Override
625     protected void handleLostEntry(final HttpRoute route) {
626 
627         poolLock.lock();
628         try {
629 
630             final RouteSpecificPool rospl = getRoutePool(route, true);
631             rospl.dropEntry();
632             if (rospl.isUnused()) {
633                 routeToPool.remove(route);
634             }
635 
636             numConnections--;
637             notifyWaitingThread(rospl);
638 
639         } finally {
640             poolLock.unlock();
641         }
642     }
643 
644     /**
645      * Notifies a waiting thread that a connection is available.
646      * This will wake a thread waiting in the specific route pool,
647      * if there is one.
648      * Otherwise, a thread in the connection pool will be notified.
649      *
650      * @param rospl     the pool in which to notify, or {@code null}
651      */
652     protected void notifyWaitingThread(final RouteSpecificPool rospl) {
653 
654         //@@@ while this strategy provides for best connection re-use,
655         //@@@ is it fair? only do this if the connection is open?
656         // Find the thread we are going to notify. We want to ensure that
657         // each waiting thread is only interrupted once, so we will remove
658         // it from all wait queues before interrupting.
659         WaitingThread waitingThread = null;
660 
661         poolLock.lock();
662         try {
663 
664             if ((rospl != null) && rospl.hasThread()) {
665                 if (log.isDebugEnabled()) {
666                     log.debug("Notifying thread waiting on pool" +
667                             " [" + rospl.getRoute() + "]");
668                 }
669                 waitingThread = rospl.nextThread();
670             } else if (!waitingThreads.isEmpty()) {
671                 if (log.isDebugEnabled()) {
672                     log.debug("Notifying thread waiting on any pool");
673                 }
674                 waitingThread = waitingThreads.remove();
675             } else if (log.isDebugEnabled()) {
676                 log.debug("Notifying no-one, there are no waiting threads");
677             }
678 
679             if (waitingThread != null) {
680                 waitingThread.wakeup();
681             }
682 
683         } finally {
684             poolLock.unlock();
685         }
686     }
687 
688 
689     @Override
690     public void deleteClosedConnections() {
691         poolLock.lock();
692         try {
693             final Iterator<BasicPoolEntry>  iter = freeConnections.iterator();
694             while (iter.hasNext()) {
695                 final BasicPoolEntry entry = iter.next();
696                 if (!entry.getConnection().isOpen()) {
697                     iter.remove();
698                     deleteEntry(entry);
699                 }
700             }
701         } finally {
702             poolLock.unlock();
703         }
704     }
705 
706     /**
707      * Closes idle connections.
708      *
709      * @param idletime  the time the connections should have been idle
710      *                  in order to be closed now
711      * @param tunit     the unit for the {@code idletime}
712      */
713     @Override
714     public void closeIdleConnections(final long idletime, final TimeUnit tunit) {
715         Args.notNull(tunit, "Time unit");
716         final long t = idletime > 0 ? idletime : 0;
717         if (log.isDebugEnabled()) {
718             log.debug("Closing connections idle longer than "  + t + " " + tunit);
719         }
720         // the latest time for which connections will be closed
721         final long deadline = System.currentTimeMillis() - tunit.toMillis(t);
722         poolLock.lock();
723         try {
724             final Iterator<BasicPoolEntry>  iter = freeConnections.iterator();
725             while (iter.hasNext()) {
726                 final BasicPoolEntry entry = iter.next();
727                 if (entry.getUpdated() <= deadline) {
728                     if (log.isDebugEnabled()) {
729                         log.debug("Closing connection last used @ " + new Date(entry.getUpdated()));
730                     }
731                     iter.remove();
732                     deleteEntry(entry);
733                 }
734             }
735         } finally {
736             poolLock.unlock();
737         }
738     }
739 
740     @Override
741     public void closeExpiredConnections() {
742         log.debug("Closing expired connections");
743         final long now = System.currentTimeMillis();
744 
745         poolLock.lock();
746         try {
747             final Iterator<BasicPoolEntry>  iter = freeConnections.iterator();
748             while (iter.hasNext()) {
749                 final BasicPoolEntry entry = iter.next();
750                 if (entry.isExpired(now)) {
751                     if (log.isDebugEnabled()) {
752                         log.debug("Closing connection expired @ " + new Date(entry.getExpiry()));
753                     }
754                     iter.remove();
755                     deleteEntry(entry);
756                 }
757             }
758         } finally {
759             poolLock.unlock();
760         }
761     }
762 
763     @Override
764     public void shutdown() {
765         poolLock.lock();
766         try {
767             if (shutdown) {
768                 return;
769             }
770             shutdown = true;
771 
772             // close all connections that are issued to an application
773             final Iterator<BasicPoolEntry> iter1 = leasedConnections.iterator();
774             while (iter1.hasNext()) {
775                 final BasicPoolEntry entry = iter1.next();
776                 iter1.remove();
777                 closeConnection(entry);
778             }
779 
780             // close all free connections
781             final Iterator<BasicPoolEntry> iter2 = freeConnections.iterator();
782             while (iter2.hasNext()) {
783                 final BasicPoolEntry entry = iter2.next();
784                 iter2.remove();
785 
786                 if (log.isDebugEnabled()) {
787                     log.debug("Closing connection"
788                             + " [" + entry.getPlannedRoute() + "][" + entry.getState() + "]");
789                 }
790                 closeConnection(entry);
791             }
792 
793             // wake up all waiting threads
794             final Iterator<WaitingThread> iwth = waitingThreads.iterator();
795             while (iwth.hasNext()) {
796                 final WaitingThread waiter = iwth.next();
797                 iwth.remove();
798                 waiter.wakeup();
799             }
800 
801             routeToPool.clear();
802 
803         } finally {
804             poolLock.unlock();
805         }
806     }
807 
808     /**
809      * since 4.1
810      */
811     public void setMaxTotalConnections(final int max) {
812         poolLock.lock();
813         try {
814             maxTotalConnections = max;
815         } finally {
816             poolLock.unlock();
817         }
818     }
819 
820 
821     /**
822      * since 4.1
823      */
824     public int getMaxTotalConnections() {
825         return maxTotalConnections;
826     }
827 
828 }
829