View Javadoc

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