You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2011/07/28 13:42:25 UTC

svn commit: r1151800 - in /httpcomponents/httpcore/trunk/httpcore/src: main/java/org/apache/http/pool/ test/java/org/apache/http/pool/

Author: olegk
Date: Thu Jul 28 11:42:23 2011
New Revision: 1151800

URL: http://svn.apache.org/viewvc?rev=1151800&view=rev
Log:
Migrated connection pooling code from HttpClient to HttpCore with some classes borrowed from HttpAsyncClient

Added:
    httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/pool/
    httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/pool/AbstractConnPool.java   (with props)
    httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/pool/ConnPoolControl.java   (with props)
    httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/pool/PoolEntry.java
      - copied, changed from r1151791, httpcomponents/httpasyncclient/trunk/httpasyncclient/src/main/java/org/apache/http/impl/nio/pool/PoolEntry.java
    httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/pool/PoolEntryFuture.java   (with props)
    httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/pool/PoolStats.java
      - copied, changed from r1151791, httpcomponents/httpasyncclient/trunk/httpasyncclient/src/main/java/org/apache/http/nio/conn/PoolStats.java
    httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/pool/RouteSpecificPool.java   (with props)
    httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/pool/
    httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/pool/TestConnPool.java   (with props)
    httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/pool/TestRouteSpecificPool.java   (with props)

Added: httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/pool/AbstractConnPool.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/pool/AbstractConnPool.java?rev=1151800&view=auto
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/pool/AbstractConnPool.java (added)
+++ httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/pool/AbstractConnPool.java Thu Jul 28 11:42:23 2011
@@ -0,0 +1,407 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.http.pool;
+
+import java.io.IOException;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+public abstract class AbstractConnPool<T, C, E extends PoolEntry<T, C>> implements ConnPoolControl<T> {
+
+    private final Map<T, RouteSpecificPool<T, C, E>> routeToPool;
+    private final Set<E> leased;
+    private final LinkedList<E> available;
+    private final LinkedList<PoolEntryFuture<E>> pending;
+    private final Map<T, Integer> maxPerRoute;
+    private final Lock lock;
+
+    private volatile boolean isShutDown;
+    private volatile int defaultMaxPerRoute;
+    private volatile int maxTotal;
+
+    public AbstractConnPool(
+            int defaultMaxPerRoute,
+            int maxTotal) {
+        super();
+        if (defaultMaxPerRoute <= 0) {
+            throw new IllegalArgumentException("Max per route value may not be negative or zero");
+        }
+        if (maxTotal <= 0) {
+            throw new IllegalArgumentException("Max total value may not be negative or zero");
+        }
+        this.routeToPool = new HashMap<T, RouteSpecificPool<T, C, E>>();
+        this.leased = new HashSet<E>();
+        this.available = new LinkedList<E>();
+        this.pending = new LinkedList<PoolEntryFuture<E>>();
+        this.maxPerRoute = new HashMap<T, Integer>();
+        this.lock = new ReentrantLock();
+        this.defaultMaxPerRoute = defaultMaxPerRoute;
+        this.maxTotal = maxTotal;
+    }
+
+    protected abstract C createConnection(T route) throws IOException;
+
+    protected abstract E createEntry(T route, C conn);
+
+    protected abstract void closeEntry(E entry);
+
+    public boolean isShutdown() {
+        return this.isShutDown;
+    }
+
+    public void shutdown(long waitMs) throws IOException {
+        if (this.isShutDown) {
+            return ;
+        }
+        this.isShutDown = true;
+        this.lock.lock();
+        try {
+            for (E entry: this.available) {
+                closeEntry(entry);
+            }
+            for (E entry: this.leased) {
+                closeEntry(entry);
+            }
+            for (RouteSpecificPool<T, C, E> pool: this.routeToPool.values()) {
+                pool.shutdown();
+            }
+            this.routeToPool.clear();
+            this.leased.clear();
+            this.available.clear();
+        } finally {
+            this.lock.unlock();
+        }
+    }
+
+    private RouteSpecificPool<T, C, E> getPool(final T route) {
+        RouteSpecificPool<T, C, E> pool = this.routeToPool.get(route);
+        if (pool == null) {
+            pool = new RouteSpecificPool<T, C, E>(route) {
+
+                @Override
+                protected E createEntry(C conn) {
+                    return AbstractConnPool.this.createEntry(route, conn);
+                }
+
+                @Override
+                protected void closeEntry(final E entry) {
+                    AbstractConnPool.this.closeEntry(entry);
+                }
+
+            };
+            this.routeToPool.put(route, pool);
+        }
+        return pool;
+    }
+
+    public Future<E> lease(final T route, final Object state) {
+        if (route == null) {
+            throw new IllegalArgumentException("Route may not be null");
+        }
+        if (this.isShutDown) {
+            throw new IllegalStateException("Connection pool shut down");
+        }
+        return new PoolEntryFuture<E>(this.lock) {
+
+            @Override
+            public E getPoolEntry(
+                    long timeout,
+                    TimeUnit tunit)
+                        throws InterruptedException, TimeoutException, IOException {
+                return getPoolEntryBlocking(route, state, timeout, tunit, this);
+            }
+
+        };
+    }
+
+    private E getPoolEntryBlocking(
+            final T route, final Object state,
+            final long timeout, final TimeUnit tunit,
+            final PoolEntryFuture<E> future)
+                throws IOException, InterruptedException, TimeoutException {
+
+        Date deadline = null;
+        if (timeout > 0) {
+            deadline = new Date
+                (System.currentTimeMillis() + tunit.toMillis(timeout));
+        }
+
+        this.lock.lock();
+        try {
+            RouteSpecificPool<T, C, E> pool = getPool(route);
+            E entry = null;
+            while (entry == null) {
+                if (this.isShutDown) {
+                    throw new IllegalStateException("Connection pool shut down");
+                }
+                for (;;) {
+                    entry = pool.getFree(state);
+                    if (entry == null) {
+                        break;
+                    }
+                    if (entry.isExpired(System.currentTimeMillis())) {
+                        closeEntry(entry);
+                        this.available.remove(entry);
+                        pool.free(entry, false);
+                    } else {
+                        break;
+                    }
+                }
+                if (entry != null) {
+                    this.available.remove(entry);
+                    this.leased.add(entry);
+                    return entry;
+                }
+
+                if (pool.getAllocatedCount() < getMaxPerRoute(route)) {
+                    int totalUsed = this.leased.size();
+                    int freeCapacity = Math.max(this.maxTotal - totalUsed, 0);
+                    if (freeCapacity > 0) {
+                        int totalAvailable = this.available.size();
+                        if (totalAvailable > freeCapacity - 1) {
+                            dropLastUsed();
+                        }
+                        C conn = createConnection(route);
+                        entry = pool.add(conn);
+                        this.leased.add(entry);
+                        return entry;
+                    }
+                }
+
+                boolean success = false;
+                try {
+                    pool.queue(future);
+                    this.pending.add(future);
+                    success = future.await(deadline);
+                } finally {
+                    // In case of 'success', we were woken up by the
+                    // connection pool and should now have a connection
+                    // waiting for us, or else we're shutting down.
+                    // Just continue in the loop, both cases are checked.
+                    pool.unqueue(future);
+                    this.pending.remove(future);
+                }
+                // check for spurious wakeup vs. timeout
+                if (!success && (deadline != null) &&
+                    (deadline.getTime() <= System.currentTimeMillis())) {
+                    break;
+                }
+            }
+            throw new TimeoutException("Timeout waiting for connection");
+        } finally {
+            this.lock.unlock();
+        }
+    }
+
+    private void notifyPending(final RouteSpecificPool<T, C, E> pool) {
+        PoolEntryFuture<E> future = pool.nextPending();
+        if (future != null) {
+            this.pending.remove(future);
+        } else {
+            future = this.pending.poll();
+        }
+        if (future != null) {
+            future.wakeup();
+        }
+    }
+
+    public void release(E entry, boolean reusable) {
+        this.lock.lock();
+        try {
+            if (this.leased.remove(entry)) {
+                RouteSpecificPool<T, C, E> pool = getPool(entry.getRoute());
+                pool.free(entry, reusable);
+                if (reusable && !this.isShutDown) {
+                    this.available.add(entry);
+                } else {
+                    closeEntry(entry);
+                }
+                notifyPending(pool);
+            }
+        } finally {
+            this.lock.unlock();
+        }
+    }
+
+    private void dropLastUsed() {
+        if (!this.available.isEmpty()) {
+            E entry = this.available.removeFirst();
+            closeEntry(entry);
+            RouteSpecificPool<T, C, E> pool = getPool(entry.getRoute());
+            pool.remove(entry);
+        }
+    }
+
+    private int getMaxPerRoute(final T route) {
+        Integer v = this.maxPerRoute.get(route);
+        if (v != null) {
+            return v.intValue();
+        } else {
+            return this.defaultMaxPerRoute;
+        }
+    }
+
+    public void setTotalMax(int max) {
+        if (max <= 0) {
+            throw new IllegalArgumentException("Max value may not be negative or zero");
+        }
+        this.lock.lock();
+        try {
+            this.maxTotal = max;
+        } finally {
+            this.lock.unlock();
+        }
+    }
+
+    public void setDefaultMaxPerHost(int max) {
+        if (max <= 0) {
+            throw new IllegalArgumentException("Max value may not be negative or zero");
+        }
+        this.lock.lock();
+        try {
+            this.defaultMaxPerRoute = max;
+        } finally {
+            this.lock.unlock();
+        }
+    }
+
+    public void setMaxPerHost(final T route, int max) {
+        if (route == null) {
+            throw new IllegalArgumentException("Route may not be null");
+        }
+        if (max <= 0) {
+            throw new IllegalArgumentException("Max value may not be negative or zero");
+        }
+        this.lock.lock();
+        try {
+            this.maxPerRoute.put(route, max);
+        } finally {
+            this.lock.unlock();
+        }
+    }
+
+    public PoolStats getTotalStats() {
+        this.lock.lock();
+        try {
+            return new PoolStats(
+                    this.leased.size(),
+                    this.pending.size(),
+                    this.available.size(),
+                    this.maxTotal);
+        } finally {
+            this.lock.unlock();
+        }
+    }
+
+    public PoolStats getStats(final T route) {
+        if (route == null) {
+            throw new IllegalArgumentException("Route may not be null");
+        }
+        this.lock.lock();
+        try {
+            RouteSpecificPool<T, C, E> pool = getPool(route);
+            return new PoolStats(
+                    pool.getLeasedCount(),
+                    pool.getPendingCount(),
+                    pool.getAvailableCount(),
+                    getMaxPerRoute(route));
+        } finally {
+            this.lock.unlock();
+        }
+    }
+
+    public void closeIdle(long idletime, final TimeUnit tunit) {
+        if (tunit == null) {
+            throw new IllegalArgumentException("Time unit must not be null.");
+        }
+        long time = tunit.toMillis(idletime);
+        if (time < 0) {
+            time = 0;
+        }
+        long deadline = System.currentTimeMillis() - time;
+        this.lock.lock();
+        try {
+            Iterator<E> it = this.available.iterator();
+            while (it.hasNext()) {
+                E entry = it.next();
+                if (entry.getUpdated() <= deadline) {
+                    closeEntry(entry);
+                    RouteSpecificPool<T, C, E> pool = getPool(entry.getRoute());
+                    pool.remove(entry);
+                    it.remove();
+                    notifyPending(pool);
+                }
+            }
+        } finally {
+            this.lock.unlock();
+        }
+    }
+
+    public void closeExpired() {
+        long now = System.currentTimeMillis();
+        this.lock.lock();
+        try {
+            Iterator<E> it = this.available.iterator();
+            while (it.hasNext()) {
+                E entry = it.next();
+                if (entry.isExpired(now)) {
+                    closeEntry(entry);
+                    RouteSpecificPool<T, C, E> pool = getPool(entry.getRoute());
+                    pool.remove(entry);
+                    it.remove();
+                    notifyPending(pool);
+                }
+            }
+        } finally {
+            this.lock.unlock();
+        }
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder buffer = new StringBuilder();
+        buffer.append("[leased: ");
+        buffer.append(this.leased);
+        buffer.append("][available: ");
+        buffer.append(this.available);
+        buffer.append("][pending: ");
+        buffer.append(this.pending);
+        buffer.append("]");
+        return buffer.toString();
+    }
+
+}

Propchange: httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/pool/AbstractConnPool.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/pool/AbstractConnPool.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/pool/AbstractConnPool.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/pool/ConnPoolControl.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/pool/ConnPoolControl.java?rev=1151800&view=auto
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/pool/ConnPoolControl.java (added)
+++ httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/pool/ConnPoolControl.java Thu Jul 28 11:42:23 2011
@@ -0,0 +1,41 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.http.pool;
+
+public interface ConnPoolControl<T> {
+
+    void setTotalMax(int max);
+
+    void setDefaultMaxPerHost(int max);
+
+    void setMaxPerHost(final T route, int max);
+
+    PoolStats getTotalStats();
+
+    PoolStats getStats(final T route);
+
+}

Propchange: httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/pool/ConnPoolControl.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/pool/ConnPoolControl.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/pool/ConnPoolControl.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Copied: httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/pool/PoolEntry.java (from r1151791, httpcomponents/httpasyncclient/trunk/httpasyncclient/src/main/java/org/apache/http/impl/nio/pool/PoolEntry.java)
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/pool/PoolEntry.java?p2=httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/pool/PoolEntry.java&p1=httpcomponents/httpasyncclient/trunk/httpasyncclient/src/main/java/org/apache/http/impl/nio/pool/PoolEntry.java&r1=1151791&r2=1151800&rev=1151800&view=diff
==============================================================================
--- httpcomponents/httpasyncclient/trunk/httpasyncclient/src/main/java/org/apache/http/impl/nio/pool/PoolEntry.java (original)
+++ httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/pool/PoolEntry.java Thu Jul 28 11:42:23 2011
@@ -24,20 +24,18 @@
  * <http://www.apache.org/>.
  *
  */
-package org.apache.http.impl.nio.pool;
+package org.apache.http.pool;
 
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicLong;
 
-import org.apache.http.nio.reactor.IOSession;
-
-public class PoolEntry<T> {
+public class PoolEntry<T, C> {
 
     private static AtomicLong COUNTER = new AtomicLong();
 
     private final long id;
     private final T route;
-    private final IOSession session;
+    private final C conn;
     private final long created;
     private final long validUnit;
 
@@ -45,20 +43,20 @@ public class PoolEntry<T> {
     private long updated;
     private long expiry;
 
-    public PoolEntry(final T route, final IOSession session,
+    public PoolEntry(final T route, final C conn,
             final long timeToLive, final TimeUnit tunit) {
         super();
         if (route == null) {
             throw new IllegalArgumentException("Route may not be null");
         }
-        if (session == null) {
-            throw new IllegalArgumentException("I/O session may not be null");
+        if (conn == null) {
+            throw new IllegalArgumentException("Connection may not be null");
         }
         if (tunit == null) {
             throw new IllegalArgumentException("Time unit may not be null");
         }
         this.route = route;
-        this.session = session;
+        this.conn = conn;
         this.id = COUNTER.incrementAndGet();
         this.created = System.currentTimeMillis();
         if (timeToLive > 0) {
@@ -69,12 +67,16 @@ public class PoolEntry<T> {
         this.expiry = this.validUnit;
     }
 
+    public PoolEntry(final T route, final C conn) {
+        this(route, conn, 0, TimeUnit.MILLISECONDS);
+    }
+
     protected T getRoute() {
         return this.route;
     }
 
-    protected IOSession getIOSession() {
-        return this.session;
+    protected C getConnection() {
+        return this.conn;
     }
 
     public long getCreated() {

Added: httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/pool/PoolEntryFuture.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/pool/PoolEntryFuture.java?rev=1151800&view=auto
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/pool/PoolEntryFuture.java (added)
+++ httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/pool/PoolEntryFuture.java Thu Jul 28 11:42:23 2011
@@ -0,0 +1,138 @@
+/*
+ * ====================================================================
+ *
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.pool;
+
+import java.io.IOException;
+import java.util.Date;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+
+abstract class PoolEntryFuture<T> implements Future<T> {
+
+    private final Lock lock;
+    private final Condition condition;
+    private volatile boolean cancelled;
+    private volatile boolean completed;
+    private T result;
+
+    PoolEntryFuture(final Lock lock) {
+        super();
+        this.lock = lock;
+        this.condition = lock.newCondition();
+    }
+
+    public boolean cancel(boolean mayInterruptIfRunning) {
+        this.lock.lock();
+        try {
+            if (this.completed) {
+                return false;
+            }
+            this.completed = true;
+            this.cancelled = true;
+            this.condition.signalAll();
+            return true;
+        } finally {
+            this.lock.unlock();
+        }
+    }
+
+    public boolean isCancelled() {
+        return this.cancelled;
+    }
+
+    public boolean isDone() {
+        return this.completed;
+    }
+
+    public T get() throws InterruptedException, ExecutionException {
+        try {
+            return get(0, TimeUnit.MILLISECONDS);
+        } catch (TimeoutException ex) {
+            throw new ExecutionException(ex);
+        }
+    }
+
+    public T get(
+            long timeout,
+            final TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
+        this.lock.lock();
+        try {
+            if (this.completed) {
+                return this.result;
+            }
+            this.result = getPoolEntry(timeout, unit);
+            this.completed = true;
+            return result;
+        } catch (IOException ex) {
+            this.completed = true;
+            this.result = null;
+            throw new ExecutionException(ex);
+        } finally {
+            this.lock.unlock();
+        }
+    }
+
+    protected abstract T getPoolEntry(
+            long timeout, TimeUnit unit) throws IOException, InterruptedException, TimeoutException;
+
+    public boolean await(final Date deadline) throws InterruptedException {
+        this.lock.lock();
+        try {
+            if (this.cancelled) {
+                throw new InterruptedException("Operation interrupted");
+            }
+            boolean success = false;
+            if (deadline != null) {
+                success = this.condition.awaitUntil(deadline);
+            } else {
+                this.condition.await();
+                success = true;
+            }
+            if (this.cancelled) {
+                throw new InterruptedException("Operation interrupted");
+            }
+            return success;
+        } finally {
+            this.lock.unlock();
+        }
+
+    }
+
+    public void wakeup() {
+        this.lock.lock();
+        try {
+            this.condition.signalAll();
+        } finally {
+            this.lock.unlock();
+        }
+    }
+
+}

Propchange: httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/pool/PoolEntryFuture.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/pool/PoolEntryFuture.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/pool/PoolEntryFuture.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Copied: httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/pool/PoolStats.java (from r1151791, httpcomponents/httpasyncclient/trunk/httpasyncclient/src/main/java/org/apache/http/nio/conn/PoolStats.java)
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/pool/PoolStats.java?p2=httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/pool/PoolStats.java&p1=httpcomponents/httpasyncclient/trunk/httpasyncclient/src/main/java/org/apache/http/nio/conn/PoolStats.java&r1=1151791&r2=1151800&rev=1151800&view=diff
==============================================================================
--- httpcomponents/httpasyncclient/trunk/httpasyncclient/src/main/java/org/apache/http/nio/conn/PoolStats.java (original)
+++ httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/pool/PoolStats.java Thu Jul 28 11:42:23 2011
@@ -24,7 +24,7 @@
  * <http://www.apache.org/>.
  *
  */
-package org.apache.http.nio.conn;
+package org.apache.http.pool;
 
 public class PoolStats {
 

Added: httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/pool/RouteSpecificPool.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/pool/RouteSpecificPool.java?rev=1151800&view=auto
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/pool/RouteSpecificPool.java (added)
+++ httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/pool/RouteSpecificPool.java Thu Jul 28 11:42:23 2011
@@ -0,0 +1,182 @@
+/*
+ * ====================================================================
+ *
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.pool;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Set;
+
+import org.apache.http.annotation.NotThreadSafe;
+
+@NotThreadSafe
+abstract class RouteSpecificPool<T, C, E extends PoolEntry<T, C>> {
+
+    private final T route;
+    private final Set<E> leased;
+    private final LinkedList<E> available;
+    private final LinkedList<PoolEntryFuture<E>> pending;
+
+    RouteSpecificPool(final T route) {
+        super();
+        this.route = route;
+        this.leased = new HashSet<E>();
+        this.available = new LinkedList<E>();
+        this.pending = new LinkedList<PoolEntryFuture<E>>();
+    }
+
+    protected abstract E createEntry(C conn);
+
+    protected abstract void closeEntry(E entry);
+
+    public final T getRoute() {
+        return route;
+    }
+
+    public int getLeasedCount() {
+        return this.leased.size();
+    }
+
+    public int getPendingCount() {
+        return this.pending.size();
+    }
+
+    public int getAvailableCount() {
+        return this.available.size();
+    }
+
+    public int getAllocatedCount() {
+        return this.available.size() + this.leased.size();
+    }
+
+    public E getFree(final Object state) {
+        if (!this.available.isEmpty()) {
+            if (state != null) {
+                Iterator<E> it = this.available.iterator();
+                while (it.hasNext()) {
+                    E entry = it.next();
+                    if (state.equals(entry.getState())) {
+                        it.remove();
+                        this.leased.add(entry);
+                        return entry;
+                    }
+                }
+            }
+            Iterator<E> it = this.available.iterator();
+            while (it.hasNext()) {
+                E entry = it.next();
+                if (entry.getState() == null) {
+                    it.remove();
+                    this.leased.add(entry);
+                    return entry;
+                }
+            }
+        }
+        return null;
+    }
+
+    public boolean remove(final E entry) {
+        if (entry == null) {
+            throw new IllegalArgumentException("Pool entry may not be null");
+        }
+        if (!this.available.remove(entry)) {
+            if (!this.leased.remove(entry)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public void free(final E entry, boolean reusable) {
+        if (entry == null) {
+            throw new IllegalArgumentException("Pool entry may not be null");
+        }
+        boolean found = this.leased.remove(entry);
+        if (!found) {
+            throw new IllegalStateException("Entry " + entry +
+                    " has not been leased from this pool");
+        }
+        if (reusable) {
+            this.available.add(entry);
+        }
+    }
+
+    public E add(final C conn) {
+        E entry = createEntry(conn);
+        this.leased.add(entry);
+        return entry;
+    }
+
+    public void queue(final PoolEntryFuture<E> future) {
+        if (future == null) {
+            return;
+        }
+        this.pending.add(future);
+    }
+
+    public PoolEntryFuture<E> nextPending() {
+        return this.pending.poll();
+    }
+
+    public void unqueue(final PoolEntryFuture<E> future) {
+        if (future == null)
+            return;
+
+        this.pending.remove(future);
+    }
+
+    public void shutdown() {
+        for (PoolEntryFuture<E> future: this.pending) {
+            future.cancel(true);
+        }
+        this.pending.clear();
+        for (E entry: this.available) {
+            closeEntry(entry);
+        }
+        this.available.clear();
+        for (E entry: this.leased) {
+            closeEntry(entry);
+        }
+        this.leased.clear();
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder buffer = new StringBuilder();
+        buffer.append("[route: ");
+        buffer.append(this.route);
+        buffer.append("][leased: ");
+        buffer.append(this.leased.size());
+        buffer.append("][available: ");
+        buffer.append(this.available.size());
+        buffer.append("][pending: ");
+        buffer.append(this.pending.size());
+        buffer.append("]");
+        return buffer.toString();
+    }
+
+}

Propchange: httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/pool/RouteSpecificPool.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/pool/RouteSpecificPool.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/pool/RouteSpecificPool.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/pool/TestConnPool.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/pool/TestConnPool.java?rev=1151800&view=auto
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/pool/TestConnPool.java (added)
+++ httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/pool/TestConnPool.java Thu Jul 28 11:42:23 2011
@@ -0,0 +1,729 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.http.pool;
+
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import junit.framework.Assert;
+
+import org.apache.http.HttpConnection;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class TestConnPool {
+
+    private static final int GRACE_PERIOD = 10000;
+
+    interface HttpConnectionFactory {
+
+        HttpConnection create(String route) throws IOException;
+
+    }
+
+    static class LocalPoolEntry extends PoolEntry<String, HttpConnection> {
+
+        public LocalPoolEntry(final String route, final HttpConnection conn) {
+            super(route, conn);
+        }
+
+    }
+
+    static class LocalConnPool extends AbstractConnPool<String, HttpConnection, LocalPoolEntry> {
+
+        private final HttpConnectionFactory connFactory;
+
+        public LocalConnPool(
+                final HttpConnectionFactory connFactory,
+                int defaultMaxPerRoute, int maxTotal) {
+            super(defaultMaxPerRoute, maxTotal);
+            this.connFactory = connFactory;
+        }
+
+        @Override
+        protected HttpConnection createConnection(final String route) throws IOException {
+            return this.connFactory.create(route);
+        }
+
+        @Override
+        protected LocalPoolEntry createEntry(final String route, final HttpConnection conn) {
+            return new LocalPoolEntry(route, conn);
+        }
+
+        @Override
+        protected void closeEntry(final LocalPoolEntry entry) {
+            HttpConnection conn = entry.getConnection();
+            try {
+                conn.close();
+            } catch (IOException ignore) {
+            }
+        }
+
+    }
+
+    @Test
+    public void testEmptyPool() throws Exception {
+        HttpConnectionFactory connFactory = Mockito.mock(HttpConnectionFactory.class);
+        LocalConnPool pool = new LocalConnPool(connFactory, 2, 10);
+        pool.setDefaultMaxPerHost(5);
+        pool.setMaxPerHost("somehost", 3);
+        PoolStats totals = pool.getTotalStats();
+        Assert.assertEquals(0, totals.getAvailable());
+        Assert.assertEquals(0, totals.getLeased());
+        Assert.assertEquals(0, totals.getPending());
+        Assert.assertEquals(10, totals.getMax());
+        PoolStats stats = pool.getStats("somehost");
+        Assert.assertEquals(0, stats.getAvailable());
+        Assert.assertEquals(0, stats.getLeased());
+        Assert.assertEquals(0, stats.getPending());
+        Assert.assertEquals(3, stats.getMax());
+        Assert.assertEquals("[leased: []][available: []][pending: []]", pool.toString());
+    }
+
+    @Test
+    public void testInvalidConstruction() throws Exception {
+        HttpConnectionFactory connFactory = Mockito.mock(HttpConnectionFactory.class);
+        try {
+            new LocalConnPool(connFactory, -1, 1);
+            Assert.fail("IllegalArgumentException should have been thrown");
+        } catch (IllegalArgumentException expected) {
+        }
+        try {
+            new LocalConnPool(connFactory, 1, -1);
+            Assert.fail("IllegalArgumentException should have been thrown");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
+    public void testLeaseRelease() throws Exception {
+        HttpConnection conn1 = Mockito.mock(HttpConnection.class);
+        HttpConnection conn2 = Mockito.mock(HttpConnection.class);
+
+        HttpConnectionFactory connFactory = Mockito.mock(HttpConnectionFactory.class);
+        Mockito.when(connFactory.create(Mockito.eq("somehost"))).thenReturn(conn1);
+        Mockito.when(connFactory.create(Mockito.eq("otherhost"))).thenReturn(conn2);
+
+        LocalConnPool pool = new LocalConnPool(connFactory, 2, 10);
+        Future<LocalPoolEntry> future1 = pool.lease("somehost", null);
+        LocalPoolEntry entry1 = future1.get(1, TimeUnit.SECONDS);
+        Assert.assertNotNull(entry1);
+        Future<LocalPoolEntry> future2 = pool.lease("somehost", null);
+        LocalPoolEntry entry2 = future2.get(1, TimeUnit.SECONDS);
+        Assert.assertNotNull(entry2);
+        Future<LocalPoolEntry> future3 = pool.lease("otherhost", null);
+        LocalPoolEntry entry3 = future3.get(1, TimeUnit.SECONDS);
+        Assert.assertNotNull(entry3);
+
+        PoolStats totals = pool.getTotalStats();
+        Assert.assertEquals(0, totals.getAvailable());
+        Assert.assertEquals(3, totals.getLeased());
+        Assert.assertEquals(0, totals.getPending());
+
+        LocalPoolEntry entry = future1.get();
+        Assert.assertSame(entry1, entry);
+
+        pool.release(entry1, true);
+        pool.release(entry2, true);
+        pool.release(entry3, false);
+        Mockito.verify(conn1, Mockito.never()).close();
+        Mockito.verify(conn2, Mockito.times(1)).close();
+
+        totals = pool.getTotalStats();
+        Assert.assertEquals(2, totals.getAvailable());
+        Assert.assertEquals(0, totals.getLeased());
+        Assert.assertEquals(0, totals.getPending());
+
+    }
+
+    @Test
+    public void testLeaseIllegal() throws Exception {
+        HttpConnectionFactory connFactory = Mockito.mock(HttpConnectionFactory.class);
+        LocalConnPool pool = new LocalConnPool(connFactory, 2, 10);
+        try {
+            pool.lease(null, null);
+            Assert.fail("IllegalArgumentException should have been thrown");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
+    public void testReleaseUnknownEntry() throws Exception {
+        HttpConnectionFactory connFactory = Mockito.mock(HttpConnectionFactory.class);
+        LocalConnPool pool = new LocalConnPool(connFactory, 2, 10);
+        pool.release(new LocalPoolEntry("somehost", Mockito.mock(HttpConnection.class)), true);
+    }
+
+    static class GetPoolEntryThread extends Thread {
+
+        private final Future<LocalPoolEntry> future;
+        private final long time;
+        private final TimeUnit tunit;
+
+        private volatile LocalPoolEntry entry;
+        private volatile Exception ex;
+
+        GetPoolEntryThread(final Future<LocalPoolEntry> future, final long time, final TimeUnit tunit) {
+            super();
+            this.future = future;
+            this.time = time;
+            this.tunit = tunit;
+            setDaemon(true);
+        }
+
+        GetPoolEntryThread(final Future<LocalPoolEntry> future) {
+            this(future, 1000, TimeUnit.SECONDS);
+        }
+
+        @Override
+        public void run() {
+            try {
+                this.entry = this.future.get(this.time, this.tunit);
+            } catch (Exception ex) {
+                this.ex = ex;
+            }
+        }
+
+        public boolean isDone() {
+            return this.future.isDone();
+        }
+
+        public LocalPoolEntry getEntry() {
+            return this.entry;
+        }
+
+        public Exception getException() {
+            return this.ex;
+        }
+
+    }
+
+    @Test
+    public void testMaxLimits() throws Exception {
+        HttpConnectionFactory connFactory = Mockito.mock(HttpConnectionFactory.class);
+
+        HttpConnection conn1 = Mockito.mock(HttpConnection.class);
+        Mockito.when(connFactory.create(Mockito.eq("somehost"))).thenReturn(conn1);
+
+        HttpConnection conn2 = Mockito.mock(HttpConnection.class);
+        Mockito.when(connFactory.create(Mockito.eq("otherhost"))).thenReturn(conn2);
+
+        LocalConnPool pool = new LocalConnPool(connFactory, 2, 10);
+        pool.setMaxPerHost("somehost", 2);
+        pool.setMaxPerHost("otherhost", 1);
+        pool.setTotalMax(3);
+
+        Future<LocalPoolEntry> future1 = pool.lease("somehost", null);
+        GetPoolEntryThread t1 = new GetPoolEntryThread(future1);
+        t1.start();
+        Future<LocalPoolEntry> future2 = pool.lease("somehost", null);
+        GetPoolEntryThread t2 = new GetPoolEntryThread(future2);
+        t2.start();
+        Future<LocalPoolEntry> future3 = pool.lease("otherhost", null);
+        GetPoolEntryThread t3 = new GetPoolEntryThread(future3);
+        t3.start();
+
+        t1.join(GRACE_PERIOD);
+        Assert.assertTrue(future1.isDone());
+        LocalPoolEntry entry1 = t1.getEntry();
+        Assert.assertNotNull(entry1);
+        t2.join(GRACE_PERIOD);
+        Assert.assertTrue(future2.isDone());
+        LocalPoolEntry entry2 = t2.getEntry();
+        Assert.assertNotNull(entry2);
+        t3.join(GRACE_PERIOD);
+        Assert.assertTrue(future3.isDone());
+        LocalPoolEntry entry3 = t3.getEntry();
+        Assert.assertNotNull(entry3);
+
+        pool.release(entry1, true);
+        pool.release(entry2, true);
+        pool.release(entry3, true);
+
+        PoolStats totals = pool.getTotalStats();
+        Assert.assertEquals(3, totals.getAvailable());
+        Assert.assertEquals(0, totals.getLeased());
+        Assert.assertEquals(0, totals.getPending());
+
+        Future<LocalPoolEntry> future4 = pool.lease("somehost", null);
+        GetPoolEntryThread t4 = new GetPoolEntryThread(future4);
+        t4.start();
+        Future<LocalPoolEntry> future5 = pool.lease("somehost", null);
+        GetPoolEntryThread t5 = new GetPoolEntryThread(future5);
+        t5.start();
+        Future<LocalPoolEntry> future6 = pool.lease("otherhost", null);
+        GetPoolEntryThread t6 = new GetPoolEntryThread(future6);
+        t6.start();
+
+        t4.join(GRACE_PERIOD);
+        Assert.assertTrue(future4.isDone());
+        LocalPoolEntry entry4 = t4.getEntry();
+        Assert.assertNotNull(entry4);
+        t5.join(GRACE_PERIOD);
+        Assert.assertTrue(future5.isDone());
+        LocalPoolEntry entry5 = t5.getEntry();
+        Assert.assertNotNull(entry5);
+        t6.join(GRACE_PERIOD);
+        Assert.assertTrue(future6.isDone());
+        LocalPoolEntry entry6 = t6.getEntry();
+        Assert.assertNotNull(entry6);
+
+        Future<LocalPoolEntry> future7 = pool.lease("somehost", null);
+        GetPoolEntryThread t7 = new GetPoolEntryThread(future7);
+        t7.start();
+        Future<LocalPoolEntry> future8 = pool.lease("somehost", null);
+        GetPoolEntryThread t8 = new GetPoolEntryThread(future8);
+        t8.start();
+        Future<LocalPoolEntry> future9 = pool.lease("otherhost", null);
+        GetPoolEntryThread t9 = new GetPoolEntryThread(future9);
+        t9.start();
+
+        Assert.assertFalse(t7.isDone());
+        Assert.assertFalse(t8.isDone());
+        Assert.assertFalse(t9.isDone());
+
+        Mockito.verify(connFactory, Mockito.times(3)).create(Mockito.any(String.class));
+
+        pool.release(entry4, true);
+        pool.release(entry5, false);
+        pool.release(entry6, true);
+
+        t7.join();
+        Assert.assertTrue(future7.isDone());
+        t8.join();
+        Assert.assertTrue(future8.isDone());
+        t9.join();
+        Assert.assertTrue(future9.isDone());
+
+        Mockito.verify(connFactory, Mockito.times(4)).create(Mockito.any(String.class));
+    }
+
+    @Test
+    public void testConnectionRedistributionOnTotalMaxLimit() throws Exception {
+        HttpConnectionFactory connFactory = Mockito.mock(HttpConnectionFactory.class);
+
+        HttpConnection conn1 = Mockito.mock(HttpConnection.class);
+        HttpConnection conn2 = Mockito.mock(HttpConnection.class);
+        HttpConnection conn3 = Mockito.mock(HttpConnection.class);
+        Mockito.when(connFactory.create(Mockito.eq("somehost"))).thenReturn(conn1, conn2, conn3);
+
+        HttpConnection conn4 = Mockito.mock(HttpConnection.class);
+        HttpConnection conn5 = Mockito.mock(HttpConnection.class);
+        Mockito.when(connFactory.create(Mockito.eq("otherhost"))).thenReturn(conn4, conn5);
+
+        LocalConnPool pool = new LocalConnPool(connFactory, 2, 10);
+        pool.setMaxPerHost("somehost", 2);
+        pool.setMaxPerHost("otherhost", 2);
+        pool.setTotalMax(2);
+
+        Future<LocalPoolEntry> future1 = pool.lease("somehost", null);
+        GetPoolEntryThread t1 = new GetPoolEntryThread(future1);
+        t1.start();
+        Future<LocalPoolEntry> future2 = pool.lease("somehost", null);
+        GetPoolEntryThread t2 = new GetPoolEntryThread(future2);
+        t2.start();
+
+        t1.join(GRACE_PERIOD);
+        Assert.assertTrue(future1.isDone());
+        LocalPoolEntry entry1 = t1.getEntry();
+        Assert.assertNotNull(entry1);
+        t2.join(GRACE_PERIOD);
+        Assert.assertTrue(future2.isDone());
+        LocalPoolEntry entry2 = t2.getEntry();
+        Assert.assertNotNull(entry2);
+
+        Future<LocalPoolEntry> future3 = pool.lease("otherhost", null);
+        GetPoolEntryThread t3 = new GetPoolEntryThread(future3);
+        t3.start();
+        Future<LocalPoolEntry> future4 = pool.lease("otherhost", null);
+        GetPoolEntryThread t4 = new GetPoolEntryThread(future4);
+        t4.start();
+
+        Assert.assertFalse(t3.isDone());
+        Assert.assertFalse(t4.isDone());
+
+        Mockito.verify(connFactory, Mockito.times(2)).create(Mockito.eq("somehost"));
+        Mockito.verify(connFactory, Mockito.never()).create(Mockito.eq("otherhost"));
+
+        PoolStats totals = pool.getTotalStats();
+        Assert.assertEquals(0, totals.getAvailable());
+        Assert.assertEquals(2, totals.getLeased());
+        Assert.assertEquals(2, totals.getPending());
+
+        pool.release(entry1, true);
+        pool.release(entry2, true);
+
+        t3.join(GRACE_PERIOD);
+        Assert.assertTrue(future3.isDone());
+        LocalPoolEntry entry3 = t3.getEntry();
+        Assert.assertNotNull(entry3);
+        t4.join(GRACE_PERIOD);
+        Assert.assertTrue(future4.isDone());
+        LocalPoolEntry entry4 = t4.getEntry();
+        Assert.assertNotNull(entry4);
+
+        Mockito.verify(connFactory, Mockito.times(2)).create(Mockito.eq("somehost"));
+        Mockito.verify(connFactory, Mockito.times(2)).create(Mockito.eq("otherhost"));
+
+        totals = pool.getTotalStats();
+        Assert.assertEquals(0, totals.getAvailable());
+        Assert.assertEquals(2, totals.getLeased());
+        Assert.assertEquals(0, totals.getPending());
+
+        Future<LocalPoolEntry> future5 = pool.lease("somehost", null);
+        GetPoolEntryThread t5 = new GetPoolEntryThread(future5);
+        t5.start();
+        Future<LocalPoolEntry> future6 = pool.lease("otherhost", null);
+        GetPoolEntryThread t6 = new GetPoolEntryThread(future6);
+        t6.start();
+
+        pool.release(entry3, true);
+        pool.release(entry4, true);
+
+        t5.join(GRACE_PERIOD);
+        Assert.assertTrue(future5.isDone());
+        LocalPoolEntry entry5 = t5.getEntry();
+        Assert.assertNotNull(entry5);
+        t6.join(GRACE_PERIOD);
+        Assert.assertTrue(future6.isDone());
+        LocalPoolEntry entry6 = t6.getEntry();
+        Assert.assertNotNull(entry6);
+
+        Mockito.verify(connFactory, Mockito.times(3)).create(Mockito.eq("somehost"));
+        Mockito.verify(connFactory, Mockito.times(2)).create(Mockito.eq("otherhost"));
+
+        totals = pool.getTotalStats();
+        Assert.assertEquals(0, totals.getAvailable());
+        Assert.assertEquals(2, totals.getLeased());
+        Assert.assertEquals(0, totals.getPending());
+
+        pool.release(entry5, true);
+        pool.release(entry6, true);
+
+        totals = pool.getTotalStats();
+        Assert.assertEquals(2, totals.getAvailable());
+        Assert.assertEquals(0, totals.getLeased());
+        Assert.assertEquals(0, totals.getPending());
+    }
+
+    @Test
+    public void testCreateNewIfExpired() throws Exception {
+        HttpConnectionFactory connFactory = Mockito.mock(HttpConnectionFactory.class);
+
+        HttpConnection conn1 = Mockito.mock(HttpConnection.class);
+        Mockito.when(connFactory.create(Mockito.eq("somehost"))).thenReturn(conn1);
+
+        LocalConnPool pool = new LocalConnPool(connFactory, 2, 2);
+
+        Future<LocalPoolEntry> future1 = pool.lease("somehost", null);
+        LocalPoolEntry entry1 = future1.get(1, TimeUnit.SECONDS);
+        Assert.assertNotNull(entry1);
+
+        Mockito.verify(connFactory, Mockito.times(1)).create(Mockito.eq("somehost"));
+
+        entry1.updateExpiry(1, TimeUnit.MILLISECONDS);
+        pool.release(entry1, true);
+
+        Thread.sleep(200L);
+
+        Future<LocalPoolEntry> future2 = pool.lease("somehost", null);
+        LocalPoolEntry entry2 = future2.get(1, TimeUnit.SECONDS);
+        Assert.assertNotNull(entry2);
+
+        Mockito.verify(connFactory, Mockito.times(2)).create(Mockito.eq("somehost"));
+
+        PoolStats totals = pool.getTotalStats();
+        Assert.assertEquals(0, totals.getAvailable());
+        Assert.assertEquals(1, totals.getLeased());
+        Assert.assertEquals(0, totals.getPending());
+        PoolStats stats = pool.getStats("somehost");
+        Assert.assertEquals(0, stats.getAvailable());
+        Assert.assertEquals(1, stats.getLeased());
+        Assert.assertEquals(0, stats.getPending());
+    }
+
+    @Test
+    public void testCloseExpired() throws Exception {
+        HttpConnectionFactory connFactory = Mockito.mock(HttpConnectionFactory.class);
+
+        HttpConnection conn1 = Mockito.mock(HttpConnection.class);
+        Mockito.when(conn1.isOpen()).thenReturn(Boolean.FALSE);
+        HttpConnection conn2 = Mockito.mock(HttpConnection.class);
+        Mockito.when(conn2.isOpen()).thenReturn(Boolean.TRUE);
+
+        Mockito.when(connFactory.create(Mockito.eq("somehost"))).thenReturn(conn1, conn2);
+
+        LocalConnPool pool = new LocalConnPool(connFactory, 2, 2);
+
+        Future<LocalPoolEntry> future1 = pool.lease("somehost", null);
+        LocalPoolEntry entry1 = future1.get(1, TimeUnit.SECONDS);
+        Assert.assertNotNull(entry1);
+        Future<LocalPoolEntry> future2 = pool.lease("somehost", null);
+        LocalPoolEntry entry2 = future2.get(1, TimeUnit.SECONDS);
+        Assert.assertNotNull(entry2);
+
+        entry1.updateExpiry(1, TimeUnit.MILLISECONDS);
+        pool.release(entry1, true);
+
+        Thread.sleep(200);
+
+        entry2.updateExpiry(1000, TimeUnit.SECONDS);
+        pool.release(entry2, true);
+
+        pool.closeExpired();
+
+        Mockito.verify(conn1).close();
+        Mockito.verify(conn2, Mockito.never()).close();
+
+        PoolStats totals = pool.getTotalStats();
+        Assert.assertEquals(1, totals.getAvailable());
+        Assert.assertEquals(0, totals.getLeased());
+        Assert.assertEquals(0, totals.getPending());
+        PoolStats stats = pool.getStats("somehost");
+        Assert.assertEquals(1, stats.getAvailable());
+        Assert.assertEquals(0, stats.getLeased());
+        Assert.assertEquals(0, stats.getPending());
+    }
+
+    @Test
+    public void testLeaseTimeout() throws Exception {
+        HttpConnectionFactory connFactory = Mockito.mock(HttpConnectionFactory.class);
+
+        HttpConnection conn1 = Mockito.mock(HttpConnection.class);
+        Mockito.when(connFactory.create(Mockito.eq("somehost"))).thenReturn(conn1);
+
+        LocalConnPool pool = new LocalConnPool(connFactory, 1, 1);
+
+        Future<LocalPoolEntry> future1 = pool.lease("somehost", null);
+        GetPoolEntryThread t1 = new GetPoolEntryThread(future1);
+        t1.start();
+        Future<LocalPoolEntry> future2 = pool.lease("somehost", null);
+        GetPoolEntryThread t2 = new GetPoolEntryThread(future2, 50, TimeUnit.MICROSECONDS);
+        t2.start();
+
+        t1.join(GRACE_PERIOD);
+        Assert.assertTrue(future1.isDone());
+        LocalPoolEntry entry1 = t1.getEntry();
+        Assert.assertNotNull(entry1);
+        t2.join(GRACE_PERIOD);
+        Assert.assertTrue(t2.getException() instanceof TimeoutException);
+        Assert.assertFalse(future2.isDone());
+        Assert.assertFalse(future2.isCancelled());
+    }
+
+    @Test
+    public void testLeaseIOException() throws Exception {
+        HttpConnectionFactory connFactory = Mockito.mock(HttpConnectionFactory.class);
+        Mockito.doThrow(new IOException("Oppsie")).when(connFactory).create("somehost");
+
+        LocalConnPool pool = new LocalConnPool(connFactory, 2, 10);
+
+        Future<LocalPoolEntry> future1 = pool.lease("somehost", null);
+        GetPoolEntryThread t1 = new GetPoolEntryThread(future1);
+        t1.start();
+
+        t1.join(GRACE_PERIOD);
+        Assert.assertTrue(future1.isDone());
+        Assert.assertTrue(t1.getException() instanceof ExecutionException);
+        Assert.assertTrue(t1.getException().getCause() instanceof IOException);
+        Assert.assertFalse(future1.isCancelled());
+    }
+
+    @Test
+    public void testLeaseCacnel() throws Exception {
+        HttpConnectionFactory connFactory = Mockito.mock(HttpConnectionFactory.class);
+
+        HttpConnection conn1 = Mockito.mock(HttpConnection.class);
+        Mockito.when(connFactory.create(Mockito.eq("somehost"))).thenReturn(conn1);
+
+        LocalConnPool pool = new LocalConnPool(connFactory, 1, 1);
+
+        Future<LocalPoolEntry> future1 = pool.lease("somehost", null);
+        GetPoolEntryThread t1 = new GetPoolEntryThread(future1);
+        t1.start();
+
+        t1.join(GRACE_PERIOD);
+        Assert.assertTrue(future1.isDone());
+        LocalPoolEntry entry1 = t1.getEntry();
+        Assert.assertNotNull(entry1);
+
+        Future<LocalPoolEntry> future2 = pool.lease("somehost", null);
+        GetPoolEntryThread t2 = new GetPoolEntryThread(future2);
+        t2.start();
+
+        Thread.sleep(5);
+
+        Assert.assertFalse(future2.isDone());
+        Assert.assertFalse(future2.isCancelled());
+
+        future2.cancel(true);
+        t2.join(GRACE_PERIOD);
+        Assert.assertTrue(future2.isDone());
+        Assert.assertTrue(future2.isCancelled());
+        future2.cancel(true);
+        future2.cancel(true);
+    }
+
+    @Test
+    public void testCloseIdle() throws Exception {
+        HttpConnectionFactory connFactory = Mockito.mock(HttpConnectionFactory.class);
+
+        HttpConnection conn1 = Mockito.mock(HttpConnection.class);
+        HttpConnection conn2 = Mockito.mock(HttpConnection.class);
+
+        Mockito.when(connFactory.create(Mockito.eq("somehost"))).thenReturn(conn1, conn2);
+
+        LocalConnPool pool = new LocalConnPool(connFactory, 2, 2);
+
+        Future<LocalPoolEntry> future1 = pool.lease("somehost", null);
+        LocalPoolEntry entry1 = future1.get(1, TimeUnit.SECONDS);
+        Assert.assertNotNull(entry1);
+        Future<LocalPoolEntry> future2 = pool.lease("somehost", null);
+        LocalPoolEntry entry2 = future2.get(1, TimeUnit.SECONDS);
+        Assert.assertNotNull(entry2);
+
+        entry1.updateExpiry(0, TimeUnit.MILLISECONDS);
+        pool.release(entry1, true);
+
+        Thread.sleep(200L);
+
+        entry2.updateExpiry(0, TimeUnit.MILLISECONDS);
+        pool.release(entry2, true);
+
+        pool.closeIdle(50, TimeUnit.MILLISECONDS);
+
+        Mockito.verify(conn1).close();
+        Mockito.verify(conn2, Mockito.never()).close();
+
+        PoolStats totals = pool.getTotalStats();
+        Assert.assertEquals(1, totals.getAvailable());
+        Assert.assertEquals(0, totals.getLeased());
+        Assert.assertEquals(0, totals.getPending());
+        PoolStats stats = pool.getStats("somehost");
+        Assert.assertEquals(1, stats.getAvailable());
+        Assert.assertEquals(0, stats.getLeased());
+        Assert.assertEquals(0, stats.getPending());
+
+        pool.closeIdle(-1, TimeUnit.MILLISECONDS);
+
+        Mockito.verify(conn2).close();
+
+        totals = pool.getTotalStats();
+        Assert.assertEquals(0, totals.getAvailable());
+        Assert.assertEquals(0, totals.getLeased());
+        Assert.assertEquals(0, totals.getPending());
+        stats = pool.getStats("somehost");
+        Assert.assertEquals(0, stats.getAvailable());
+        Assert.assertEquals(0, stats.getLeased());
+        Assert.assertEquals(0, stats.getPending());
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void testCloseIdleInvalid() throws Exception {
+        HttpConnectionFactory connFactory = Mockito.mock(HttpConnectionFactory.class);
+        LocalConnPool pool = new LocalConnPool(connFactory, 2, 2);
+        pool.closeIdle(50, null);
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void testGetStatsInvalid() throws Exception {
+        HttpConnectionFactory connFactory = Mockito.mock(HttpConnectionFactory.class);
+        LocalConnPool pool = new LocalConnPool(connFactory, 2, 2);
+        pool.getStats(null);
+    }
+
+    @Test
+    public void testSetMaxInvalid() throws Exception {
+        HttpConnectionFactory connFactory = Mockito.mock(HttpConnectionFactory.class);
+        LocalConnPool pool = new LocalConnPool(connFactory, 2, 2);
+        try {
+            pool.setTotalMax(-1);
+            Assert.fail("IllegalArgumentException should have been thrown");
+        } catch (IllegalArgumentException expected) {
+        }
+        try {
+            pool.setMaxPerHost(null, 1);
+            Assert.fail("IllegalArgumentException should have been thrown");
+        } catch (IllegalArgumentException expected) {
+        }
+        try {
+            pool.setMaxPerHost("somehost", -1);
+            Assert.fail("IllegalArgumentException should have been thrown");
+        } catch (IllegalArgumentException expected) {
+        }
+        try {
+            pool.setDefaultMaxPerHost(-1);
+            Assert.fail("IllegalArgumentException should have been thrown");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
+    public void testShutdown() throws Exception {
+        HttpConnectionFactory connFactory = Mockito.mock(HttpConnectionFactory.class);
+
+        HttpConnection conn1 = Mockito.mock(HttpConnection.class);
+        Mockito.when(connFactory.create(Mockito.eq("somehost"))).thenReturn(conn1);
+        HttpConnection conn2 = Mockito.mock(HttpConnection.class);
+        Mockito.when(connFactory.create(Mockito.eq("otherhost"))).thenReturn(conn2);
+
+        LocalConnPool pool = new LocalConnPool(connFactory, 2, 2);
+        Future<LocalPoolEntry> future1 = pool.lease("somehost", null);
+        LocalPoolEntry entry1 = future1.get(1, TimeUnit.SECONDS);
+        Assert.assertNotNull(entry1);
+        Future<LocalPoolEntry> future2 = pool.lease("otherhost", null);
+        LocalPoolEntry entry2 = future2.get(1, TimeUnit.SECONDS);
+        Assert.assertNotNull(entry2);
+
+        pool.release(entry2, true);
+
+        PoolStats totals = pool.getTotalStats();
+        Assert.assertEquals(1, totals.getAvailable());
+        Assert.assertEquals(1, totals.getLeased());
+        Assert.assertEquals(0, totals.getPending());
+
+        pool.shutdown(1000);
+        Assert.assertTrue(pool.isShutdown());
+        pool.shutdown(1000);
+        pool.shutdown(1000);
+
+        Mockito.verify(conn1, Mockito.atLeastOnce()).close();
+        Mockito.verify(conn2, Mockito.atLeastOnce()).close();
+
+        try {
+            pool.lease("somehost", null);
+            Assert.fail("IllegalStateException should have been thrown");
+        } catch (IllegalStateException expected) {
+        }
+        // Ignored if shut down
+        pool.release(new LocalPoolEntry("somehost", Mockito.mock(HttpConnection.class)), true);
+    }
+
+}

Propchange: httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/pool/TestConnPool.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/pool/TestConnPool.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/pool/TestConnPool.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/pool/TestRouteSpecificPool.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/pool/TestRouteSpecificPool.java?rev=1151800&view=auto
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/pool/TestRouteSpecificPool.java (added)
+++ httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/pool/TestRouteSpecificPool.java Thu Jul 28 11:42:23 2011
@@ -0,0 +1,299 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+package org.apache.http.pool;
+
+import java.io.IOException;
+
+import junit.framework.Assert;
+
+import org.apache.http.HttpConnection;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class TestRouteSpecificPool {
+
+    private static final String ROUTE = "whatever";
+
+    static class LocalPoolEntry extends PoolEntry<String, HttpConnection> {
+
+        public LocalPoolEntry(final String route, final HttpConnection conn) {
+            super(route, conn);
+        }
+
+    }
+
+    static class LocalRoutePool extends RouteSpecificPool<String, HttpConnection, LocalPoolEntry> {
+
+        public LocalRoutePool() {
+            super(ROUTE);
+        }
+
+        @Override
+        protected LocalPoolEntry createEntry(final HttpConnection conn) {
+            return new LocalPoolEntry(getRoute(), conn);
+        }
+
+        @Override
+        protected void closeEntry(LocalPoolEntry entry) {
+            HttpConnection conn = entry.getConnection();
+            try {
+                conn.close();
+            } catch (IOException ignore) {
+            }
+        }
+
+    };
+
+    @Test
+    public void testEmptyPool() throws Exception {
+        LocalRoutePool pool = new LocalRoutePool();
+        Assert.assertEquals(ROUTE, pool.getRoute());
+        Assert.assertEquals(0, pool.getAllocatedCount());
+        Assert.assertEquals(0, pool.getAvailableCount());
+        Assert.assertEquals(0, pool.getLeasedCount());
+        Assert.assertEquals(0, pool.getPendingCount());
+        Assert.assertEquals("[route: whatever][leased: 0][available: 0][pending: 0]", pool.toString());
+    }
+
+    @Test
+    public void testAdd() throws Exception {
+        LocalRoutePool pool = new LocalRoutePool();
+        HttpConnection conn = Mockito.mock(HttpConnection.class);
+        PoolEntry<String, HttpConnection> entry = pool.add(conn);
+        Assert.assertEquals(1, pool.getAllocatedCount());
+        Assert.assertEquals(0, pool.getAvailableCount());
+        Assert.assertEquals(1, pool.getLeasedCount());
+        Assert.assertEquals(0, pool.getPendingCount());
+        Assert.assertNotNull(entry);
+        Assert.assertSame(conn, entry.getConnection());
+    }
+
+    @Test
+    public void testLeaseRelease() throws Exception {
+        LocalRoutePool pool = new LocalRoutePool();
+        HttpConnection conn1 = Mockito.mock(HttpConnection.class);
+        LocalPoolEntry entry1 = pool.add(conn1);
+        HttpConnection conn2 = Mockito.mock(HttpConnection.class);
+        LocalPoolEntry entry2 = pool.add(conn2);
+        HttpConnection conn3 = Mockito.mock(HttpConnection.class);
+        LocalPoolEntry entry3 = pool.add(conn3);
+
+        Assert.assertNotNull(entry1);
+        Assert.assertNotNull(entry2);
+        Assert.assertNotNull(entry3);
+
+        Assert.assertEquals(3, pool.getAllocatedCount());
+        Assert.assertEquals(0, pool.getAvailableCount());
+        Assert.assertEquals(3, pool.getLeasedCount());
+        Assert.assertEquals(0, pool.getPendingCount());
+
+        pool.free(entry1, true);
+        pool.free(entry2, false);
+        pool.free(entry3, true);
+
+        Assert.assertEquals(2, pool.getAllocatedCount());
+        Assert.assertEquals(2, pool.getAvailableCount());
+        Assert.assertEquals(0, pool.getLeasedCount());
+        Assert.assertEquals(0, pool.getPendingCount());
+
+        Assert.assertNotNull(pool.getFree(null));
+        Assert.assertNotNull(pool.getFree(null));
+        Assert.assertNull(pool.getFree(null));
+
+        Assert.assertEquals(2, pool.getAllocatedCount());
+        Assert.assertEquals(0, pool.getAvailableCount());
+        Assert.assertEquals(2, pool.getLeasedCount());
+        Assert.assertEquals(0, pool.getPendingCount());
+    }
+
+    @Test
+    public void testLeaseReleaseStateful() throws Exception {
+        LocalRoutePool pool = new LocalRoutePool();
+        HttpConnection conn1 = Mockito.mock(HttpConnection.class);
+        LocalPoolEntry entry1 = pool.add(conn1);
+        HttpConnection conn2 = Mockito.mock(HttpConnection.class);
+        LocalPoolEntry entry2 = pool.add(conn2);
+        HttpConnection conn3 = Mockito.mock(HttpConnection.class);
+        LocalPoolEntry entry3 = pool.add(conn3);
+
+        Assert.assertNotNull(entry1);
+        Assert.assertNotNull(entry2);
+        Assert.assertNotNull(entry3);
+
+        Assert.assertEquals(3, pool.getAllocatedCount());
+        Assert.assertEquals(0, pool.getAvailableCount());
+        Assert.assertEquals(3, pool.getLeasedCount());
+        Assert.assertEquals(0, pool.getPendingCount());
+
+        entry2.setState(Boolean.FALSE);
+        pool.free(entry1, true);
+        pool.free(entry2, true);
+        pool.free(entry3, true);
+
+        Assert.assertEquals(entry2, pool.getFree(Boolean.FALSE));
+        Assert.assertEquals(entry1, pool.getFree(Boolean.FALSE));
+        Assert.assertEquals(entry3, pool.getFree(null));
+        Assert.assertEquals(null, pool.getFree(null));
+
+        entry1.setState(Boolean.TRUE);
+        entry2.setState(Boolean.FALSE);
+        entry3.setState(Boolean.TRUE);
+        pool.free(entry1, true);
+        pool.free(entry2, true);
+        pool.free(entry3, true);
+
+        Assert.assertEquals(null, pool.getFree(null));
+        Assert.assertEquals(entry2, pool.getFree(Boolean.FALSE));
+        Assert.assertEquals(null, pool.getFree(Boolean.FALSE));
+        Assert.assertEquals(entry1, pool.getFree(Boolean.TRUE));
+        Assert.assertEquals(entry3, pool.getFree(Boolean.TRUE));
+        Assert.assertEquals(null, pool.getFree(Boolean.TRUE));
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void testReleaseInvalidEntry() throws Exception {
+        LocalRoutePool pool = new LocalRoutePool();
+        HttpConnection conn = Mockito.mock(HttpConnection.class);
+        LocalPoolEntry entry = new LocalPoolEntry(ROUTE, conn);
+        pool.free(entry, true);
+    }
+
+    @Test
+    public void testRemove() throws Exception {
+        LocalRoutePool pool = new LocalRoutePool();
+        HttpConnection conn1 = Mockito.mock(HttpConnection.class);
+        LocalPoolEntry entry1 = pool.add(conn1);
+        HttpConnection conn2 = Mockito.mock(HttpConnection.class);
+        LocalPoolEntry entry2 = pool.add(conn2);
+        HttpConnection conn3 = Mockito.mock(HttpConnection.class);
+        LocalPoolEntry entry3 = pool.add(conn3);
+
+        Assert.assertNotNull(entry1);
+        Assert.assertNotNull(entry2);
+        Assert.assertNotNull(entry3);
+
+        Assert.assertEquals(3, pool.getAllocatedCount());
+        Assert.assertEquals(0, pool.getAvailableCount());
+        Assert.assertEquals(3, pool.getLeasedCount());
+        Assert.assertEquals(0, pool.getPendingCount());
+
+        Assert.assertTrue(pool.remove(entry2));
+        Assert.assertFalse(pool.remove(entry2));
+
+        Assert.assertEquals(2, pool.getAllocatedCount());
+        Assert.assertEquals(0, pool.getAvailableCount());
+        Assert.assertEquals(2, pool.getLeasedCount());
+        Assert.assertEquals(0, pool.getPendingCount());
+
+        pool.free(entry1, true);
+        pool.free(entry3, true);
+
+        Assert.assertEquals(2, pool.getAllocatedCount());
+        Assert.assertEquals(2, pool.getAvailableCount());
+        Assert.assertEquals(0, pool.getLeasedCount());
+        Assert.assertEquals(0, pool.getPendingCount());
+
+        Assert.assertTrue(pool.remove(entry1));
+        Assert.assertTrue(pool.remove(entry3));
+
+        Assert.assertEquals(0, pool.getAllocatedCount());
+        Assert.assertEquals(0, pool.getAvailableCount());
+        Assert.assertEquals(0, pool.getLeasedCount());
+        Assert.assertEquals(0, pool.getPendingCount());
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void testReleaseInvalid() throws Exception {
+        LocalRoutePool pool = new LocalRoutePool();
+        pool.free(null, true);
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void testRemoveInvalid() throws Exception {
+        LocalRoutePool pool = new LocalRoutePool();
+        pool.remove(null);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testWaitingThreadQueuing() throws Exception {
+        LocalRoutePool pool = new LocalRoutePool();
+        PoolEntryFuture<LocalPoolEntry> future1 = Mockito.mock(PoolEntryFuture.class);
+        PoolEntryFuture<LocalPoolEntry> future2 = Mockito.mock(PoolEntryFuture.class);
+
+        Assert.assertEquals(0, pool.getPendingCount());
+        pool.queue(future1);
+        Assert.assertEquals(1, pool.getPendingCount());
+        pool.queue(null);
+        Assert.assertEquals(1, pool.getPendingCount());
+        pool.queue(future2);
+        Assert.assertEquals(2, pool.getPendingCount());
+        Assert.assertSame(future1, pool.nextPending());
+        pool.unqueue(future1);
+        Assert.assertEquals(1, pool.getPendingCount());
+        Assert.assertSame(future2, pool.nextPending());
+        pool.unqueue(null);
+        Assert.assertEquals(0, pool.getPendingCount());
+        pool.unqueue(future2);
+        Assert.assertNull(pool.nextPending());
+    }
+
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testShutdown() throws Exception {
+        LocalRoutePool pool = new LocalRoutePool();
+        HttpConnection conn1 = Mockito.mock(HttpConnection.class);
+        LocalPoolEntry entry1 = pool.add(conn1);
+        HttpConnection conn2 = Mockito.mock(HttpConnection.class);
+        LocalPoolEntry entry2 = pool.add(conn2);
+
+        PoolEntryFuture<LocalPoolEntry> future1 = Mockito.mock(PoolEntryFuture.class);
+        pool.queue(future1);
+
+        Assert.assertNotNull(entry1);
+        Assert.assertNotNull(entry2);
+
+        pool.free(entry1, true);
+
+        Assert.assertEquals(2, pool.getAllocatedCount());
+        Assert.assertEquals(1, pool.getAvailableCount());
+        Assert.assertEquals(1, pool.getLeasedCount());
+        Assert.assertEquals(1, pool.getPendingCount());
+
+        pool.shutdown();
+
+        Assert.assertEquals(0, pool.getAllocatedCount());
+        Assert.assertEquals(0, pool.getAvailableCount());
+        Assert.assertEquals(0, pool.getLeasedCount());
+        Assert.assertEquals(0, pool.getPendingCount());
+
+        Mockito.verify(future1).cancel(true);
+        Mockito.verify(conn2).close();
+        Mockito.verify(conn1).close();
+    }
+
+}

Propchange: httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/pool/TestRouteSpecificPool.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/pool/TestRouteSpecificPool.java
------------------------------------------------------------------------------
    svn:keywords = Date Revision

Propchange: httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/pool/TestRouteSpecificPool.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain