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 2017/08/20 14:25:04 UTC

httpcomponents-core git commit: HTTPCORE-390: Connection pool implementation with higher concurrency characteristics and lax total and per route max guarantees.

Repository: httpcomponents-core
Updated Branches:
  refs/heads/master 9d603d4d4 -> 41d642f1a


HTTPCORE-390: Connection pool implementation with higher concurrency characteristics and lax total and per route max guarantees.


Project: http://git-wip-us.apache.org/repos/asf/httpcomponents-core/repo
Commit: http://git-wip-us.apache.org/repos/asf/httpcomponents-core/commit/41d642f1
Tree: http://git-wip-us.apache.org/repos/asf/httpcomponents-core/tree/41d642f1
Diff: http://git-wip-us.apache.org/repos/asf/httpcomponents-core/diff/41d642f1

Branch: refs/heads/master
Commit: 41d642f1ad9dd6e826708ef38095b5b3a6300753
Parents: 9d603d4
Author: Oleg Kalnichevski <ol...@apache.org>
Authored: Sun Aug 20 15:10:51 2017 +0200
Committer: Oleg Kalnichevski <ol...@apache.org>
Committed: Sun Aug 20 15:35:04 2017 +0200

----------------------------------------------------------------------
 .../org/apache/hc/core5/pool/LaxConnPool.java   | 573 +++++++++++++++++++
 .../org/apache/hc/core5/pool/LeaseRequest.java  | 120 ----
 .../org/apache/hc/core5/pool/RoutePool.java     | 149 -----
 .../apache/hc/core5/pool/StrictConnPool.java    | 225 +++++++-
 .../apache/hc/core5/pool/TestLaxConnPool.java   | 423 ++++++++++++++
 .../hc/core5/pool/TestRouteSpecificPool.java    | 274 ---------
 .../hc/core5/pool/TestStrictConnPool.java       |   2 +-
 7 files changed, 1204 insertions(+), 562 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/httpcomponents-core/blob/41d642f1/httpcore5/src/main/java/org/apache/hc/core5/pool/LaxConnPool.java
----------------------------------------------------------------------
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/pool/LaxConnPool.java b/httpcore5/src/main/java/org/apache/hc/core5/pool/LaxConnPool.java
new file mode 100644
index 0000000..11149d0
--- /dev/null
+++ b/httpcore5/src/main/java/org/apache/hc/core5/pool/LaxConnPool.java
@@ -0,0 +1,573 @@
+/*
+ * ====================================================================
+ * 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.hc.core5.pool;
+
+import java.util.Deque;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedDeque;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.hc.core5.annotation.Contract;
+import org.apache.hc.core5.annotation.Experimental;
+import org.apache.hc.core5.annotation.ThreadingBehavior;
+import org.apache.hc.core5.concurrent.BasicFuture;
+import org.apache.hc.core5.concurrent.Cancellable;
+import org.apache.hc.core5.concurrent.FutureCallback;
+import org.apache.hc.core5.function.Callback;
+import org.apache.hc.core5.io.GracefullyCloseable;
+import org.apache.hc.core5.io.ShutdownType;
+import org.apache.hc.core5.util.Args;
+import org.apache.hc.core5.util.Asserts;
+import org.apache.hc.core5.util.LangUtils;
+import org.apache.hc.core5.util.TimeValue;
+import org.apache.hc.core5.util.Timeout;
+
+/**
+ * Connection pool with higher concurrency but with lax connection limit guarantees.
+ *
+ * @param <T> route
+ * @param <C> connection object
+ *
+ * @since 5.0
+ */
+@Contract(threading = ThreadingBehavior.SAFE)
+@Experimental
+public class LaxConnPool<T, C extends GracefullyCloseable> implements ControlledConnPool<T, C> {
+
+    private final TimeValue timeToLive;
+    private final ConnPoolListener<T> connPoolListener;
+    private final ConnPoolPolicy policy;
+    private final ConcurrentMap<T, PerRoutePool<T, C>> routeToPool;
+    private final AtomicBoolean isShutDown;
+
+    private volatile int defaultMaxPerRoute;
+
+    /**
+     * @since 5.0
+     */
+    public LaxConnPool(
+            final int defaultMaxPerRoute,
+            final int maxTotal,
+            final TimeValue timeToLive,
+            final ConnPoolPolicy policy,
+            final ConnPoolListener<T> connPoolListener) {
+        super();
+        Args.positive(defaultMaxPerRoute, "Max per route value");
+        Args.positive(maxTotal, "Max total value");
+        this.timeToLive = TimeValue.defaultsToNegativeOneMillisecond(timeToLive);
+        this.connPoolListener = connPoolListener;
+        this.policy = policy != null ? policy : ConnPoolPolicy.LIFO;
+        this.routeToPool = new ConcurrentHashMap<>();
+        this.isShutDown = new AtomicBoolean(false);
+        this.defaultMaxPerRoute = defaultMaxPerRoute;
+    }
+
+    public LaxConnPool(final int defaultMaxPerRoute, final int maxTotal) {
+        this(defaultMaxPerRoute, maxTotal, TimeValue.NEG_ONE_MILLISECONDS, ConnPoolPolicy.LIFO, null);
+    }
+
+    public boolean isShutdown() {
+        return isShutDown.get();
+    }
+
+    @Override
+    public void shutdown(final ShutdownType shutdownType) {
+        if (isShutDown.compareAndSet(false, true)) {
+            for (final Iterator<PerRoutePool<T, C>> it = routeToPool.values().iterator(); it.hasNext(); ) {
+                final PerRoutePool<T, C> routePool = it.next();
+                routePool.shutdown(shutdownType);
+            }
+            routeToPool.clear();
+        }
+    }
+
+    @Override
+    public void close() {
+        shutdown(ShutdownType.GRACEFUL);
+    }
+
+    private PerRoutePool<T, C> getPool(final T route) {
+        PerRoutePool<T, C> routePool = routeToPool.get(route);
+        if (routePool == null) {
+            final PerRoutePool<T, C> newRoutePool = new PerRoutePool<>(
+                    route,
+                    defaultMaxPerRoute,
+                    timeToLive,
+                    policy,
+                    this,
+                    connPoolListener);
+            routePool = routeToPool.putIfAbsent(route, newRoutePool);
+            if (routePool == null) {
+                routePool = newRoutePool;
+            }
+        }
+        return routePool;
+    }
+
+    public Future<PoolEntry<T, C>> lease(
+            final T route, final Object state,
+            final Timeout requestTimeout,
+            final FutureCallback<PoolEntry<T, C>> callback) {
+        Args.notNull(route, "Route");
+        Args.notNull(requestTimeout, "Request timeout");
+        Asserts.check(!isShutDown.get(), "Connection pool shut down");
+        final PerRoutePool<T, C> routePool = getPool(route);
+        return routePool.lease(state, requestTimeout, callback);
+    }
+
+    @Override
+    public Future<PoolEntry<T, C>> lease(final T route, final Object state, final FutureCallback<PoolEntry<T, C>> callback) {
+        return lease(route, state, Timeout.DISABLED, callback);
+    }
+
+    public Future<PoolEntry<T, C>> lease(final T route, final Object state) {
+        return lease(route, state, Timeout.DISABLED, null);
+    }
+
+    @Override
+    public void release(final PoolEntry<T, C> entry, final boolean reusable) {
+        if (entry == null) {
+            return;
+        }
+        if (isShutDown.get()) {
+            return;
+        }
+        final PerRoutePool<T, C> routePool = getPool(entry.getRoute());
+        if (connPoolListener != null) {
+            connPoolListener.onLease(entry.getRoute(), this);
+        }
+        routePool.release(entry, reusable);
+    }
+
+    public void validatePendingRequests() {
+        for (final PerRoutePool<T, C> routePool : routeToPool.values()) {
+            routePool.validatePendingRequests();
+        }
+    }
+
+    @Override
+    public void setMaxTotal(final int max) {
+    }
+
+    @Override
+    public int getMaxTotal() {
+        return 0;
+    }
+
+    @Override
+    public void setDefaultMaxPerRoute(final int max) {
+        Args.positive(max, "Max value");
+        defaultMaxPerRoute = max;
+    }
+
+    @Override
+    public int getDefaultMaxPerRoute() {
+        return defaultMaxPerRoute;
+    }
+
+    @Override
+    public void setMaxPerRoute(final T route, final int max) {
+        Args.notNull(route, "Route");
+        Args.positive(max, "Max value");
+        final PerRoutePool<T, C> routePool = getPool(route);
+        routePool.setMax(max);
+    }
+
+    @Override
+    public int getMaxPerRoute(final T route) {
+        Args.notNull(route, "Route");
+        final PerRoutePool<T, C> routePool = getPool(route);
+        return routePool.getMax();
+    }
+
+    @Override
+    public PoolStats getTotalStats() {
+        int leasedTotal = 0;
+        int pendingTotal = 0;
+        int availableTotal = 0;
+        int maxTotal = 0;
+        for (final PerRoutePool<T, C> routePool : routeToPool.values()) {
+            leasedTotal += routePool.getLeasedCount();
+            pendingTotal += routePool.getPendingCount();
+            availableTotal += routePool.getAvailableCount();
+            maxTotal += routePool.getMax();
+        }
+        return new PoolStats(leasedTotal, pendingTotal, availableTotal, maxTotal);
+    }
+
+    @Override
+    public PoolStats getStats(final T route) {
+        Args.notNull(route, "Route");
+        final PerRoutePool<T, C> routePool = getPool(route);
+        return new PoolStats(
+                routePool.getLeasedCount(),
+                routePool.getPendingCount(),
+                routePool.getAvailableCount(),
+                routePool.getMax());
+    }
+
+    public Set<T> getRoutes() {
+        return new HashSet<>(routeToPool.keySet());
+    }
+
+    public void enumAvailable(final Callback<PoolEntry<T, C>> callback) {
+        for (final PerRoutePool<T, C> routePool : routeToPool.values()) {
+            routePool.enumAvailable(callback);
+        }
+    }
+
+    public void enumLeased(final Callback<PoolEntry<T, C>> callback) {
+        for (final PerRoutePool<T, C> routePool : routeToPool.values()) {
+            routePool.enumLeased(callback);
+        }
+    }
+
+    @Override
+    public void closeIdle(final TimeValue idleTime) {
+        final long deadline = System.currentTimeMillis() - (TimeValue.isPositive(idleTime) ? idleTime.toMillis() : 0);
+        enumAvailable(new Callback<PoolEntry<T, C>>() {
+
+            @Override
+            public void execute(final PoolEntry<T, C> entry) {
+                if (entry.getUpdated() <= deadline) {
+                    entry.discardConnection(ShutdownType.GRACEFUL);
+                }
+            }
+
+        });
+    }
+
+    @Override
+    public void closeExpired() {
+        final long now = System.currentTimeMillis();
+        enumAvailable(new Callback<PoolEntry<T, C>>() {
+
+            @Override
+            public void execute(final PoolEntry<T, C> entry) {
+                if (entry.getExpiry() < now) {
+                    entry.discardConnection(ShutdownType.GRACEFUL);
+                }
+            }
+
+        });
+    }
+
+    @Override
+    public String toString() {
+        final PoolStats totalStats = getTotalStats();
+        final StringBuilder buffer = new StringBuilder();
+        buffer.append("[leased: ");
+        buffer.append(totalStats.getLeased());
+        buffer.append("][available: ");
+        buffer.append(totalStats.getAvailable());
+        buffer.append("][pending: ");
+        buffer.append(totalStats.getPending());
+        buffer.append("]");
+        return buffer.toString();
+    }
+
+    static class LeaseRequest<T, C extends GracefullyCloseable> implements Cancellable {
+
+        private final Object state;
+        private final long deadline;
+        private final BasicFuture<PoolEntry<T, C>> future;
+
+        LeaseRequest(
+                final Object state,
+                final TimeValue requestTimeout,
+                final BasicFuture<PoolEntry<T, C>> future) {
+            super();
+            this.state = state;
+            this.deadline = TimeValue.calculateDeadline(System.currentTimeMillis(), requestTimeout);
+            this.future = future;
+        }
+
+        BasicFuture<PoolEntry<T, C>> getFuture() {
+            return this.future;
+        }
+
+        public Object getState() {
+            return this.state;
+        }
+
+        public long getDeadline() {
+            return this.deadline;
+        }
+
+        public boolean isDone() {
+            return this.future.isDone();
+        }
+
+        public void completed(final PoolEntry<T, C> result) {
+            future.completed(result);
+        }
+
+        public void failed(final Exception ex) {
+            future.failed(ex);
+        }
+
+        @Override
+        public boolean cancel() {
+            return future.cancel();
+        }
+
+    }
+
+    static class PerRoutePool<T, C extends GracefullyCloseable> {
+
+        private final T route;
+        private final TimeValue timeToLive;
+        private final ConnPoolPolicy policy;
+        private final ConnPoolStats<T> connPoolStats;
+        private final ConnPoolListener<T> connPoolListener;
+        private final ConcurrentMap<PoolEntry<T, C>, Boolean> leased;
+        private final Deque<PoolEntry<T, C>> available;
+        private final Deque<LeaseRequest<T, C>> pending;
+        private final AtomicBoolean terminated;
+
+        private volatile int max;
+
+        PerRoutePool(
+                final T route,
+                final int max,
+                final TimeValue timeToLive,
+                final ConnPoolPolicy policy,
+                final ConnPoolStats<T> connPoolStats,
+                final ConnPoolListener<T> connPoolListener) {
+            super();
+            this.route = route;
+            this.timeToLive = timeToLive;
+            this.policy = policy;
+            this.connPoolStats = connPoolStats;
+            this.connPoolListener = connPoolListener;
+            this.leased = new ConcurrentHashMap<>();
+            this.available = new ConcurrentLinkedDeque<>();
+            this.pending = new ConcurrentLinkedDeque<>();
+            this.terminated = new AtomicBoolean(false);
+            this.max = max;
+        }
+
+        public void shutdown(final ShutdownType shutdownType) {
+            if (terminated.compareAndSet(false, true)) {
+                PoolEntry<T, C> availableEntry;
+                while ((availableEntry = available.poll()) != null) {
+                    availableEntry.discardConnection(shutdownType);
+                }
+                for (final PoolEntry<T, C> entry : leased.keySet()) {
+                    entry.discardConnection(shutdownType);
+                }
+                leased.clear();
+                LeaseRequest<T, C> leaseRequest;
+                while ((leaseRequest = pending.poll()) != null) {
+                    leaseRequest.cancel();
+                }
+            }
+        }
+
+        private void addLeased(final PoolEntry<T, C> entry) {
+            if (leased.putIfAbsent(entry, Boolean.TRUE) != null) {
+                throw new IllegalStateException("Pool entry already present in the set of leased entries");
+            } else if (connPoolListener != null) {
+                connPoolListener.onLease(route, connPoolStats);
+            }
+        }
+
+        private void removeLeased(final PoolEntry<T, C> entry) {
+            if (connPoolListener != null) {
+                connPoolListener.onRelease(route, connPoolStats);
+            }
+            if (!leased.remove(entry, Boolean.TRUE)) {
+                throw new IllegalStateException("Pool entry is not present in the set of leased entries");
+            }
+        }
+
+        private PoolEntry<T, C> getAvailableEntry(final Object state) {
+            final PoolEntry<T, C> entry = available.poll();
+            if (entry != null) {
+                if (entry.getExpiry() < System.currentTimeMillis()) {
+                    entry.discardConnection(ShutdownType.GRACEFUL);
+                }
+                if (!LangUtils.equals(entry.getState(), state)) {
+                    entry.discardConnection(ShutdownType.GRACEFUL);
+                }
+            }
+            return entry;
+        }
+
+        public Future<PoolEntry<T, C>> lease(
+                final Object state,
+                final TimeValue requestTimeout,
+                final FutureCallback<PoolEntry<T, C>> callback) {
+            Asserts.check(!terminated.get(), "Connection pool shut down");
+            final BasicFuture<PoolEntry<T, C>> future = new BasicFuture<>(callback);
+            final PoolEntry<T, C> availableEntry = getAvailableEntry(state);
+            if (availableEntry != null) {
+                addLeased(availableEntry);
+                future.completed(availableEntry);
+            } else {
+                if (pending.isEmpty() && leased.size() < max) {
+                    final PoolEntry<T, C> entry = new PoolEntry<>(route, timeToLive);
+                    addLeased(entry);
+                    future.completed(entry);
+                } else {
+                    pending.add(new LeaseRequest<>(state, requestTimeout, future));
+                }
+            }
+            return future;
+        }
+
+        public void release(final PoolEntry<T, C> releasedEntry, final boolean reusable) {
+            removeLeased(releasedEntry);
+            if (!reusable || releasedEntry.getExpiry() < System.currentTimeMillis()) {
+                releasedEntry.discardConnection(ShutdownType.GRACEFUL);
+            }
+            if (releasedEntry.hasConnection()) {
+                switch (policy) {
+                    case LIFO:
+                        available.addFirst(releasedEntry);
+                        break;
+                    case FIFO:
+                        available.addLast(releasedEntry);
+                        break;
+                    default:
+                        throw new IllegalStateException("Unexpected ConnPoolPolicy value: " + policy);
+                }
+            }
+            LeaseRequest<T, C> leaseRequest;
+            while ((leaseRequest = pending.poll()) != null) {
+                if (leaseRequest.isDone()) {
+                    continue;
+                }
+                final Object state = leaseRequest.getState();
+                final long deadline = leaseRequest.getDeadline();
+
+                final long now = System.currentTimeMillis();
+                if (now > deadline) {
+                    leaseRequest.failed(new TimeoutException());
+                } else {
+                    final PoolEntry<T, C> availableEntry = getAvailableEntry(state);
+                    if (availableEntry != null) {
+                        addLeased(releasedEntry);
+                        leaseRequest.completed(availableEntry);
+                    } else if (leased.size() < max) {
+                        final PoolEntry<T, C> newEntry = new PoolEntry<>(route, timeToLive);
+                        addLeased(newEntry);
+                        leaseRequest.completed(newEntry);
+                    }
+                    break;
+                }
+            }
+        }
+
+        public void validatePendingRequests() {
+            final long now = System.currentTimeMillis();
+            final Iterator<LeaseRequest<T, C>> it = pending.iterator();
+            while (it.hasNext()) {
+                final LeaseRequest<T, C> request = it.next();
+                final BasicFuture<PoolEntry<T, C>> future = request.getFuture();
+                if (future.isCancelled() && !request.isDone()) {
+                    it.remove();
+                } else {
+                    final long deadline = request.getDeadline();
+                    if (now > deadline) {
+                        request.failed(new TimeoutException());
+                    }
+                    if (request.isDone()) {
+                        it.remove();
+                    }
+                }
+            }
+        }
+
+        public final T getRoute() {
+            return route;
+        }
+
+        public int getMax() {
+            return max;
+        }
+
+        public void setMax(final int max) {
+            this.max = max;
+        }
+
+        public int getPendingCount() {
+            return pending.size();
+        }
+
+        public int getLeasedCount() {
+            return leased.size();
+        }
+
+        public int getAvailableCount() {
+            return available.size();
+        }
+
+        public void enumAvailable(final Callback<PoolEntry<T, C>> callback) {
+            for (final Iterator<PoolEntry<T, C>> it = available.iterator(); it.hasNext(); ) {
+                final PoolEntry<T, C> entry = it.next();
+                callback.execute(entry);
+                if (!entry.hasConnection()) {
+                    it.remove();
+                }
+            }
+        }
+
+        public void enumLeased(final Callback<PoolEntry<T, C>> callback) {
+            for (final Iterator<PoolEntry<T, C>> it = leased.keySet().iterator(); it.hasNext(); ) {
+                final PoolEntry<T, C> entry = it.next();
+                callback.execute(entry);
+                if (!entry.hasConnection()) {
+                    it.remove();
+                }
+            }
+        }
+
+        @Override
+        public String toString() {
+            final StringBuilder buffer = new StringBuilder();
+            buffer.append("[route: ");
+            buffer.append(route);
+            buffer.append("][leased: ");
+            buffer.append(leased.size());
+            buffer.append("][available: ");
+            buffer.append(available.size());
+            buffer.append("][pending: ");
+            buffer.append(pending.size());
+            buffer.append("]");
+            return buffer.toString();
+        }
+
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/httpcomponents-core/blob/41d642f1/httpcore5/src/main/java/org/apache/hc/core5/pool/LeaseRequest.java
----------------------------------------------------------------------
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/pool/LeaseRequest.java b/httpcore5/src/main/java/org/apache/hc/core5/pool/LeaseRequest.java
deleted file mode 100644
index 42977fb..0000000
--- a/httpcore5/src/main/java/org/apache/hc/core5/pool/LeaseRequest.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * ====================================================================
- * 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.hc.core5.pool;
-
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.apache.hc.core5.annotation.Contract;
-import org.apache.hc.core5.annotation.ThreadingBehavior;
-import org.apache.hc.core5.concurrent.BasicFuture;
-import org.apache.hc.core5.io.GracefullyCloseable;
-import org.apache.hc.core5.util.Timeout;
-
-@Contract(threading = ThreadingBehavior.SAFE_CONDITIONAL)
-class LeaseRequest<T, C extends GracefullyCloseable> {
-
-    private final T route;
-    private final Object state;
-    private final long deadline;
-    private final BasicFuture<PoolEntry<T, C>> future;
-    private final AtomicBoolean completed;
-    private volatile PoolEntry<T, C> result;
-    private volatile Exception ex;
-
-    /**
-     * Constructor
-     *
-     * @param route route
-     * @param state state
-     * @param requestTimeout timeout to wait in a request queue until kicked off
-     * @param future future callback
-     */
-    public LeaseRequest(
-            final T route,
-            final Object state,
-            final Timeout requestTimeout,
-            final BasicFuture<PoolEntry<T, C>> future) {
-        super();
-        this.route = route;
-        this.state = state;
-        this.deadline = Timeout.calculateDeadline(System.currentTimeMillis(), requestTimeout);
-        this.future = future;
-        this.completed = new AtomicBoolean(false);
-    }
-
-    public T getRoute() {
-        return this.route;
-    }
-
-    public Object getState() {
-        return this.state;
-    }
-
-    public long getDeadline() {
-        return this.deadline;
-    }
-
-    public boolean isDone() {
-        return this.completed.get();
-    }
-
-    public void failed(final Exception ex) {
-        if (this.completed.compareAndSet(false, true)) {
-            this.ex = ex;
-        }
-    }
-
-    public void completed(final PoolEntry<T, C> result) {
-        if (this.completed.compareAndSet(false, true)) {
-            this.result = result;
-        }
-    }
-
-    public BasicFuture<PoolEntry<T, C>> getFuture() {
-        return this.future;
-    }
-
-    public PoolEntry<T, C> getResult() {
-        return this.result;
-    }
-
-    public Exception getException() {
-        return this.ex;
-    }
-
-    @Override
-    public String toString() {
-        final StringBuilder buffer = new StringBuilder();
-        buffer.append("[");
-        buffer.append(this.route);
-        buffer.append("][");
-        buffer.append(this.state);
-        buffer.append("]");
-        return buffer.toString();
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/httpcomponents-core/blob/41d642f1/httpcore5/src/main/java/org/apache/hc/core5/pool/RoutePool.java
----------------------------------------------------------------------
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/pool/RoutePool.java b/httpcore5/src/main/java/org/apache/hc/core5/pool/RoutePool.java
deleted file mode 100644
index 67981fa..0000000
--- a/httpcore5/src/main/java/org/apache/hc/core5/pool/RoutePool.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * ====================================================================
- * 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.hc.core5.pool;
-
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.Set;
-
-import org.apache.hc.core5.io.GracefullyCloseable;
-import org.apache.hc.core5.io.ShutdownType;
-import org.apache.hc.core5.util.Args;
-import org.apache.hc.core5.util.Asserts;
-import org.apache.hc.core5.util.TimeValue;
-
-final class RoutePool<T, C extends GracefullyCloseable> {
-
-    private final T route;
-    private final Set<PoolEntry<T, C>> leased;
-    private final LinkedList<PoolEntry<T, C>> available;
-
-    RoutePool(final T route) {
-        super();
-        this.route = route;
-        this.leased = new HashSet<>();
-        this.available = new LinkedList<>();
-    }
-
-    public final T getRoute() {
-        return route;
-    }
-
-    public int getLeasedCount() {
-        return this.leased.size();
-    }
-
-    public int getAvailableCount() {
-        return this.available.size();
-    }
-
-    public int getAllocatedCount() {
-        return this.available.size() + this.leased.size();
-    }
-
-    public PoolEntry<T, C> getFree(final Object state) {
-        if (!this.available.isEmpty()) {
-            if (state != null) {
-                final Iterator<PoolEntry<T, C>> it = this.available.iterator();
-                while (it.hasNext()) {
-                    final PoolEntry<T, C> entry = it.next();
-                    if (state.equals(entry.getState())) {
-                        it.remove();
-                        this.leased.add(entry);
-                        return entry;
-                    }
-                }
-            }
-            final Iterator<PoolEntry<T, C>> it = this.available.iterator();
-            while (it.hasNext()) {
-                final PoolEntry<T, C> entry = it.next();
-                if (entry.getState() == null) {
-                    it.remove();
-                    this.leased.add(entry);
-                    return entry;
-                }
-            }
-        }
-        return null;
-    }
-
-    public PoolEntry<T, C> getLastUsed() {
-        if (!this.available.isEmpty()) {
-            return this.available.getLast();
-        }
-        return null;
-    }
-
-    public boolean remove(final PoolEntry<T, C> entry) {
-        Args.notNull(entry, "Pool entry");
-        if (!this.available.remove(entry) && !this.leased.remove(entry)) {
-            return false;
-        }
-        return true;
-    }
-
-    public void free(final PoolEntry<T, C> entry, final boolean reusable) {
-        Args.notNull(entry, "Pool entry");
-        final boolean found = this.leased.remove(entry);
-        Asserts.check(found, "Entry %s has not been leased from this pool", entry);
-        if (reusable) {
-            this.available.addFirst(entry);
-        }
-    }
-
-    public PoolEntry<T, C> createEntry(final TimeValue timeToLive) {
-        final PoolEntry<T, C> entry = new PoolEntry<>(this.route, timeToLive);
-        this.leased.add(entry);
-        return entry;
-    }
-
-    public void shutdown(final ShutdownType shutdownType) {
-        for (final PoolEntry<T, C> entry: this.available) {
-            entry.discardConnection(shutdownType);
-        }
-        this.available.clear();
-        for (final PoolEntry<T, C> entry: this.leased) {
-            entry.discardConnection(shutdownType);
-        }
-        this.leased.clear();
-    }
-
-    @Override
-    public String toString() {
-        final 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("]");
-        return buffer.toString();
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/httpcomponents-core/blob/41d642f1/httpcore5/src/main/java/org/apache/hc/core5/pool/StrictConnPool.java
----------------------------------------------------------------------
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/pool/StrictConnPool.java b/httpcore5/src/main/java/org/apache/hc/core5/pool/StrictConnPool.java
index 32faa13..329ca0c 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/pool/StrictConnPool.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/pool/StrictConnPool.java
@@ -54,20 +54,20 @@ import org.apache.hc.core5.util.TimeValue;
 import org.apache.hc.core5.util.Timeout;
 
 /**
- * Connection pool with strict max connection limit guarantees.
+ * Connection pool with strict connection limit guarantees.
  *
  * @param <T> route
  * @param <C> connection object
  *
  * @since 4.2
  */
-@Contract(threading = ThreadingBehavior.SAFE_CONDITIONAL)
+@Contract(threading = ThreadingBehavior.SAFE)
 public class StrictConnPool<T, C extends GracefullyCloseable> implements ControlledConnPool<T, C> {
 
     private final TimeValue timeToLive;
     private final ConnPoolListener<T> connPoolListener;
     private final ConnPoolPolicy policy;
-    private final Map<T, RoutePool<T, C>> routeToPool;
+    private final Map<T, PerRoutePool<T, C>> routeToPool;
     private final LinkedList<LeaseRequest<T, C>> leasingRequests;
     private final Set<PoolEntry<T, C>> leased;
     private final LinkedList<PoolEntry<T, C>> available;
@@ -120,7 +120,7 @@ public class StrictConnPool<T, C extends GracefullyCloseable> implements Control
             fireCallbacks();
             this.lock.lock();
             try {
-                for (final RoutePool<T, C> pool: this.routeToPool.values()) {
+                for (final PerRoutePool<T, C> pool: this.routeToPool.values()) {
                     pool.shutdown(shutdownType);
                 }
                 this.routeToPool.clear();
@@ -138,10 +138,10 @@ public class StrictConnPool<T, C extends GracefullyCloseable> implements Control
         shutdown(ShutdownType.GRACEFUL);
     }
 
-    private RoutePool<T, C> getPool(final T route) {
-        RoutePool<T, C> pool = this.routeToPool.get(route);
+    private PerRoutePool<T, C> getPool(final T route) {
+        PerRoutePool<T, C> pool = this.routeToPool.get(route);
         if (pool == null) {
-            pool = new RoutePool<>(route);
+            pool = new PerRoutePool<>(route);
             this.routeToPool.put(route, pool);
         }
         return pool;
@@ -195,7 +195,10 @@ public class StrictConnPool<T, C extends GracefullyCloseable> implements Control
         this.lock.lock();
         try {
             if (this.leased.remove(entry)) {
-                final RoutePool<T, C> pool = getPool(entry.getRoute());
+                if (this.connPoolListener != null) {
+                    this.connPoolListener.onRelease(entry.getRoute(), this);
+                }
+                final PerRoutePool<T, C> pool = getPool(entry.getRoute());
                 final boolean keepAlive = entry.hasConnection() && reusable;
                 pool.free(entry, keepAlive);
                 if (keepAlive) {
@@ -209,13 +212,12 @@ public class StrictConnPool<T, C extends GracefullyCloseable> implements Control
                         default:
                             throw new IllegalStateException("Unexpected ConnPoolPolicy value: " + policy);
                     }
-                    if (this.connPoolListener != null) {
-                        this.connPoolListener.onRelease(entry.getRoute(), this);
-                    }
                 } else {
                     entry.discardConnection(ShutdownType.GRACEFUL);
                 }
                 processNextPendingRequest();
+            } else {
+                throw new IllegalStateException("Pool entry is not present in the set of leased entries");
             }
         } finally {
             this.lock.unlock();
@@ -275,7 +277,7 @@ public class StrictConnPool<T, C extends GracefullyCloseable> implements Control
             return false;
         }
 
-        final RoutePool<T, C> pool = getPool(route);
+        final PerRoutePool<T, C> pool = getPool(route);
         PoolEntry<T, C> entry;
         for (;;) {
             entry = pool.getFree(state);
@@ -326,7 +328,7 @@ public class StrictConnPool<T, C extends GracefullyCloseable> implements Control
                 if (!this.available.isEmpty()) {
                     final PoolEntry<T, C> lastUsed = this.available.removeLast();
                     lastUsed.discardConnection(ShutdownType.GRACEFUL);
-                    final RoutePool<T, C> otherpool = getPool(lastUsed.getRoute());
+                    final PerRoutePool<T, C> otherpool = getPool(lastUsed.getRoute());
                     otherpool.remove(lastUsed);
                 }
             }
@@ -483,7 +485,7 @@ public class StrictConnPool<T, C extends GracefullyCloseable> implements Control
         Args.notNull(route, "Route");
         this.lock.lock();
         try {
-            final RoutePool<T, C> pool = getPool(route);
+            final PerRoutePool<T, C> pool = getPool(route);
             int pendingCount = 0;
             for (final LeaseRequest<T, C> request: leasingRequests) {
                 if (LangUtils.equals(route, request.getRoute())) {
@@ -527,7 +529,7 @@ public class StrictConnPool<T, C extends GracefullyCloseable> implements Control
                 final PoolEntry<T, C> entry = it.next();
                 callback.execute(entry);
                 if (!entry.hasConnection()) {
-                    final RoutePool<T, C> pool = getPool(entry.getRoute());
+                    final PerRoutePool<T, C> pool = getPool(entry.getRoute());
                     pool.remove(entry);
                     it.remove();
                 }
@@ -559,10 +561,10 @@ public class StrictConnPool<T, C extends GracefullyCloseable> implements Control
     }
 
     private void purgePoolMap() {
-        final Iterator<Map.Entry<T, RoutePool<T, C>>> it = this.routeToPool.entrySet().iterator();
+        final Iterator<Map.Entry<T, PerRoutePool<T, C>>> it = this.routeToPool.entrySet().iterator();
         while (it.hasNext()) {
-            final Map.Entry<T, RoutePool<T, C>> entry = it.next();
-            final RoutePool<T, C> pool = entry.getValue();
+            final Map.Entry<T, PerRoutePool<T, C>> entry = it.next();
+            final PerRoutePool<T, C> pool = entry.getValue();
             if (pool.getAllocatedCount() == 0) {
                 it.remove();
             }
@@ -612,4 +614,191 @@ public class StrictConnPool<T, C extends GracefullyCloseable> implements Control
         return buffer.toString();
     }
 
+
+    static class LeaseRequest<T, C extends GracefullyCloseable> {
+
+        private final T route;
+        private final Object state;
+        private final long deadline;
+        private final BasicFuture<PoolEntry<T, C>> future;
+        private final AtomicBoolean completed;
+        private volatile PoolEntry<T, C> result;
+        private volatile Exception ex;
+
+        /**
+         * Constructor
+         *
+         * @param route route
+         * @param state state
+         * @param requestTimeout timeout to wait in a request queue until kicked off
+         * @param future future callback
+         */
+        public LeaseRequest(
+                final T route,
+                final Object state,
+                final Timeout requestTimeout,
+                final BasicFuture<PoolEntry<T, C>> future) {
+            super();
+            this.route = route;
+            this.state = state;
+            this.deadline = Timeout.calculateDeadline(System.currentTimeMillis(), requestTimeout);
+            this.future = future;
+            this.completed = new AtomicBoolean(false);
+        }
+
+        public T getRoute() {
+            return this.route;
+        }
+
+        public Object getState() {
+            return this.state;
+        }
+
+        public long getDeadline() {
+            return this.deadline;
+        }
+
+        public boolean isDone() {
+            return this.completed.get();
+        }
+
+        public void failed(final Exception ex) {
+            if (this.completed.compareAndSet(false, true)) {
+                this.ex = ex;
+            }
+        }
+
+        public void completed(final PoolEntry<T, C> result) {
+            if (this.completed.compareAndSet(false, true)) {
+                this.result = result;
+            }
+        }
+
+        public BasicFuture<PoolEntry<T, C>> getFuture() {
+            return this.future;
+        }
+
+        public PoolEntry<T, C> getResult() {
+            return this.result;
+        }
+
+        public Exception getException() {
+            return this.ex;
+        }
+
+        @Override
+        public String toString() {
+            final StringBuilder buffer = new StringBuilder();
+            buffer.append("[");
+            buffer.append(this.route);
+            buffer.append("][");
+            buffer.append(this.state);
+            buffer.append("]");
+            return buffer.toString();
+        }
+
+    }
+
+    static class PerRoutePool<T, C extends GracefullyCloseable> {
+
+        private final T route;
+        private final Set<PoolEntry<T, C>> leased;
+        private final LinkedList<PoolEntry<T, C>> available;
+
+        PerRoutePool(final T route) {
+            super();
+            this.route = route;
+            this.leased = new HashSet<>();
+            this.available = new LinkedList<>();
+        }
+
+        public final T getRoute() {
+            return route;
+        }
+
+        public int getLeasedCount() {
+            return this.leased.size();
+        }
+
+        public int getAvailableCount() {
+            return this.available.size();
+        }
+
+        public int getAllocatedCount() {
+            return this.available.size() + this.leased.size();
+        }
+
+        public PoolEntry<T, C> getFree(final Object state) {
+            if (!this.available.isEmpty()) {
+                if (state != null) {
+                    final Iterator<PoolEntry<T, C>> it = this.available.iterator();
+                    while (it.hasNext()) {
+                        final PoolEntry<T, C> entry = it.next();
+                        if (state.equals(entry.getState())) {
+                            it.remove();
+                            this.leased.add(entry);
+                            return entry;
+                        }
+                    }
+                }
+                final Iterator<PoolEntry<T, C>> it = this.available.iterator();
+                while (it.hasNext()) {
+                    final PoolEntry<T, C> entry = it.next();
+                    if (entry.getState() == null) {
+                        it.remove();
+                        this.leased.add(entry);
+                        return entry;
+                    }
+                }
+            }
+            return null;
+        }
+
+        public PoolEntry<T, C> getLastUsed() {
+            return this.available.peekLast();
+        }
+
+        public boolean remove(final PoolEntry<T, C> entry) {
+            return this.available.remove(entry) || this.leased.remove(entry);
+        }
+
+        public void free(final PoolEntry<T, C> entry, final boolean reusable) {
+            final boolean found = this.leased.remove(entry);
+            Asserts.check(found, "Entry %s has not been leased from this pool", entry);
+            if (reusable) {
+                this.available.addFirst(entry);
+            }
+        }
+
+        public PoolEntry<T, C> createEntry(final TimeValue timeToLive) {
+            final PoolEntry<T, C> entry = new PoolEntry<>(this.route, timeToLive);
+            this.leased.add(entry);
+            return entry;
+        }
+
+        public void shutdown(final ShutdownType shutdownType) {
+            PoolEntry<T, C> availableEntry;
+            while ((availableEntry = available.poll()) != null) {
+                availableEntry.discardConnection(shutdownType);
+            }
+            for (final PoolEntry<T, C> entry: this.leased) {
+                entry.discardConnection(shutdownType);
+            }
+            this.leased.clear();
+        }
+
+        @Override
+        public String toString() {
+            final 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("]");
+            return buffer.toString();
+        }
+
+    }
 }

http://git-wip-us.apache.org/repos/asf/httpcomponents-core/blob/41d642f1/httpcore5/src/test/java/org/apache/hc/core5/pool/TestLaxConnPool.java
----------------------------------------------------------------------
diff --git a/httpcore5/src/test/java/org/apache/hc/core5/pool/TestLaxConnPool.java b/httpcore5/src/test/java/org/apache/hc/core5/pool/TestLaxConnPool.java
new file mode 100644
index 0000000..5cfc6ea
--- /dev/null
+++ b/httpcore5/src/test/java/org/apache/hc/core5/pool/TestLaxConnPool.java
@@ -0,0 +1,423 @@
+/*
+ * ====================================================================
+ * 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.hc.core5.pool;
+
+import java.util.Collections;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.hc.core5.http.HttpConnection;
+import org.apache.hc.core5.io.ShutdownType;
+import org.apache.hc.core5.util.TimeValue;
+import org.apache.hc.core5.util.Timeout;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class TestLaxConnPool {
+
+    @Test
+    public void testEmptyPool() throws Exception {
+        final LaxConnPool<String, HttpConnection> pool = new LaxConnPool<>(2, 10);
+        final PoolStats totals = pool.getTotalStats();
+        Assert.assertEquals(0, totals.getAvailable());
+        Assert.assertEquals(0, totals.getLeased());
+        Assert.assertEquals(0, totals.getPending());
+        Assert.assertEquals(0, totals.getMax());
+        Assert.assertEquals(Collections.emptySet(), pool.getRoutes());
+        final PoolStats stats = pool.getStats("somehost");
+        Assert.assertEquals(0, stats.getAvailable());
+        Assert.assertEquals(0, stats.getLeased());
+        Assert.assertEquals(0, stats.getPending());
+        Assert.assertEquals(2, stats.getMax());
+        Assert.assertEquals("[leased: 0][available: 0][pending: 0]", pool.toString());
+    }
+
+    @Test
+    public void testInvalidConstruction() throws Exception {
+        try {
+            new LaxConnPool<String, HttpConnection>(-1, 1);
+            Assert.fail("IllegalArgumentException should have been thrown");
+        } catch (final IllegalArgumentException expected) {
+        }
+        try {
+            new LaxConnPool<String, HttpConnection>(1, -1);
+            Assert.fail("IllegalArgumentException should have been thrown");
+        } catch (final IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
+    public void testLeaseRelease() throws Exception {
+        final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
+        final HttpConnection conn2 = Mockito.mock(HttpConnection.class);
+        final HttpConnection conn3 = Mockito.mock(HttpConnection.class);
+
+        final LaxConnPool<String, HttpConnection> pool = new LaxConnPool<>(2, 10);
+        final Future<PoolEntry<String, HttpConnection>> future1 = pool.lease("somehost", null);
+        final Future<PoolEntry<String, HttpConnection>> future2 = pool.lease("somehost", null);
+        final Future<PoolEntry<String, HttpConnection>> future3 = pool.lease("otherhost", null);
+
+        final PoolEntry<String, HttpConnection> entry1 = future1.get();
+        Assert.assertNotNull(entry1);
+        entry1.assignConnection(conn1);
+        final PoolEntry<String, HttpConnection> entry2 = future2.get();
+        Assert.assertNotNull(entry2);
+        entry2.assignConnection(conn2);
+        final PoolEntry<String, HttpConnection> entry3 = future3.get();
+        Assert.assertNotNull(entry3);
+        entry3.assignConnection(conn3);
+
+        pool.release(entry1, true);
+        pool.release(entry2, true);
+        pool.release(entry3, false);
+        Mockito.verify(conn1, Mockito.never()).shutdown(Mockito.<ShutdownType>any());
+        Mockito.verify(conn2, Mockito.never()).shutdown(Mockito.<ShutdownType>any());
+        Mockito.verify(conn3, Mockito.times(1)).shutdown(ShutdownType.GRACEFUL);
+
+        final PoolStats totals = pool.getTotalStats();
+        Assert.assertEquals(2, totals.getAvailable());
+        Assert.assertEquals(0, totals.getLeased());
+        Assert.assertEquals(0, totals.getPending());
+    }
+
+    @Test
+    public void testLeaseIllegal() throws Exception {
+        final LaxConnPool<String, HttpConnection> pool = new LaxConnPool<>(2, 10);
+        try {
+            pool.lease(null, null, Timeout.ZERO_MILLISECONDS, null);
+            Assert.fail("IllegalArgumentException should have been thrown");
+        } catch (final IllegalArgumentException expected) {
+        }
+        try {
+            pool.lease("somehost", null, null, null);
+            Assert.fail("IllegalArgumentException should have been thrown");
+        } catch (final IllegalArgumentException expected) {
+        }
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testReleaseUnknownEntry() throws Exception {
+        final LaxConnPool<String, HttpConnection> pool = new LaxConnPool<>(2, 2);
+        pool.release(new PoolEntry<String, HttpConnection>("somehost"), true);
+    }
+
+    @Test
+    public void testMaxLimits() throws Exception {
+        final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
+        final HttpConnection conn2 = Mockito.mock(HttpConnection.class);
+        final HttpConnection conn3 = Mockito.mock(HttpConnection.class);
+
+        final LaxConnPool<String, HttpConnection> pool = new LaxConnPool<>(2, 10);
+        pool.setMaxPerRoute("somehost", 2);
+        pool.setMaxPerRoute("otherhost", 1);
+
+        final Future<PoolEntry<String, HttpConnection>> future1 = pool.lease("somehost", null);
+        final Future<PoolEntry<String, HttpConnection>> future2 = pool.lease("somehost", null);
+        final Future<PoolEntry<String, HttpConnection>> future3 = pool.lease("otherhost", null);
+
+        final PoolEntry<String, HttpConnection> entry1 = future1.get();
+        Assert.assertNotNull(entry1);
+        entry1.assignConnection(conn1);
+        final PoolEntry<String, HttpConnection> entry2 = future2.get();
+        Assert.assertNotNull(entry2);
+        entry2.assignConnection(conn2);
+        final PoolEntry<String, HttpConnection> entry3 = future3.get();
+        Assert.assertNotNull(entry3);
+        entry3.assignConnection(conn3);
+
+        pool.release(entry1, true);
+        pool.release(entry2, true);
+        pool.release(entry3, true);
+
+        final PoolStats totals = pool.getTotalStats();
+        Assert.assertEquals(3, totals.getAvailable());
+        Assert.assertEquals(0, totals.getLeased());
+        Assert.assertEquals(0, totals.getPending());
+
+        final Future<PoolEntry<String, HttpConnection>> future4 = pool.lease("somehost", null);
+        final Future<PoolEntry<String, HttpConnection>> future5 = pool.lease("somehost", null);
+        final Future<PoolEntry<String, HttpConnection>> future6 = pool.lease("otherhost", null);
+        final Future<PoolEntry<String, HttpConnection>> future7 = pool.lease("somehost", null);
+        final Future<PoolEntry<String, HttpConnection>> future8 = pool.lease("somehost", null);
+        final Future<PoolEntry<String, HttpConnection>> future9 = pool.lease("otherhost", null);
+
+        Assert.assertTrue(future4.isDone());
+        final PoolEntry<String, HttpConnection> entry4 = future4.get();
+        Assert.assertNotNull(entry4);
+        Assert.assertSame(conn2, entry4.getConnection());
+
+        Assert.assertTrue(future5.isDone());
+        final PoolEntry<String, HttpConnection> entry5 = future5.get();
+        Assert.assertNotNull(entry5);
+        Assert.assertSame(conn1, entry5.getConnection());
+
+        Assert.assertTrue(future6.isDone());
+        final PoolEntry<String, HttpConnection> entry6 = future6.get();
+        Assert.assertNotNull(entry6);
+        Assert.assertSame(conn3, entry6.getConnection());
+
+        Assert.assertFalse(future7.isDone());
+        Assert.assertFalse(future8.isDone());
+        Assert.assertFalse(future9.isDone());
+
+        pool.release(entry4, true);
+        pool.release(entry5, false);
+        pool.release(entry6, true);
+
+        Assert.assertTrue(future7.isDone());
+        final PoolEntry<String, HttpConnection> entry7 = future7.get();
+        Assert.assertNotNull(entry7);
+        Assert.assertSame(conn2, entry7.getConnection());
+
+        Assert.assertTrue(future8.isDone());
+        final PoolEntry<String, HttpConnection> entry8 = future8.get();
+        Assert.assertNotNull(entry8);
+        Assert.assertEquals(null, entry8.getConnection());
+
+        Assert.assertTrue(future9.isDone());
+        final PoolEntry<String, HttpConnection> entry9 = future9.get();
+        Assert.assertNotNull(entry9);
+        Assert.assertSame(conn3, entry9.getConnection());
+    }
+
+    @Test
+    public void testCreateNewIfExpired() throws Exception {
+        final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
+
+        final LaxConnPool<String, HttpConnection> pool = new LaxConnPool<>(2, 2);
+
+        final Future<PoolEntry<String, HttpConnection>> future1 = pool.lease("somehost", null);
+
+        Assert.assertTrue(future1.isDone());
+        final PoolEntry<String, HttpConnection> entry1 = future1.get();
+        Assert.assertNotNull(entry1);
+        entry1.assignConnection(conn1);
+
+        entry1.updateExpiry(TimeValue.of(1, TimeUnit.MILLISECONDS));
+        pool.release(entry1, true);
+
+        Thread.sleep(200L);
+
+        final Future<PoolEntry<String, HttpConnection>> future2 = pool.lease("somehost", null);
+
+        Assert.assertTrue(future2.isDone());
+
+        Mockito.verify(conn1).shutdown(ShutdownType.GRACEFUL);
+
+        final PoolStats totals = pool.getTotalStats();
+        Assert.assertEquals(0, totals.getAvailable());
+        Assert.assertEquals(1, totals.getLeased());
+        Assert.assertEquals(Collections.singleton("somehost"), pool.getRoutes());
+        final PoolStats stats = pool.getStats("somehost");
+        Assert.assertEquals(0, stats.getAvailable());
+        Assert.assertEquals(1, stats.getLeased());
+    }
+
+    @Test
+    public void testCloseExpired() throws Exception {
+        final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
+        final HttpConnection conn2 = Mockito.mock(HttpConnection.class);
+
+        final LaxConnPool<String, HttpConnection> pool = new LaxConnPool<>(2, 2);
+
+        final Future<PoolEntry<String, HttpConnection>> future1 = pool.lease("somehost", null);
+        final Future<PoolEntry<String, HttpConnection>> future2 = pool.lease("somehost", null);
+
+        Assert.assertTrue(future1.isDone());
+        final PoolEntry<String, HttpConnection> entry1 = future1.get();
+        Assert.assertNotNull(entry1);
+        entry1.assignConnection(conn1);
+        Assert.assertTrue(future2.isDone());
+        final PoolEntry<String, HttpConnection> entry2 = future2.get();
+        Assert.assertNotNull(entry2);
+        entry2.assignConnection(conn2);
+
+        entry1.updateExpiry(TimeValue.of(1, TimeUnit.MILLISECONDS));
+        pool.release(entry1, true);
+
+        Thread.sleep(200);
+
+        entry2.updateExpiry(TimeValue.of(1000, TimeUnit.SECONDS));
+        pool.release(entry2, true);
+
+        pool.closeExpired();
+
+        Mockito.verify(conn1).shutdown(ShutdownType.GRACEFUL);
+        Mockito.verify(conn2, Mockito.never()).shutdown(Mockito.<ShutdownType>any());
+
+        final PoolStats totals = pool.getTotalStats();
+        Assert.assertEquals(1, totals.getAvailable());
+        Assert.assertEquals(0, totals.getLeased());
+        Assert.assertEquals(0, totals.getPending());
+        final PoolStats stats = pool.getStats("somehost");
+        Assert.assertEquals(1, stats.getAvailable());
+        Assert.assertEquals(0, stats.getLeased());
+        Assert.assertEquals(0, stats.getPending());
+    }
+
+    @Test
+    public void testCloseIdle() throws Exception {
+        final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
+        final HttpConnection conn2 = Mockito.mock(HttpConnection.class);
+
+        final LaxConnPool<String, HttpConnection> pool = new LaxConnPool<>(2, 2);
+
+        final Future<PoolEntry<String, HttpConnection>> future1 = pool.lease("somehost", null);
+        final Future<PoolEntry<String, HttpConnection>> future2 = pool.lease("somehost", null);
+
+        Assert.assertTrue(future1.isDone());
+        final PoolEntry<String, HttpConnection> entry1 = future1.get();
+        Assert.assertNotNull(entry1);
+        entry1.assignConnection(conn1);
+        Assert.assertTrue(future2.isDone());
+        final PoolEntry<String, HttpConnection> entry2 = future2.get();
+        Assert.assertNotNull(entry2);
+        entry2.assignConnection(conn2);
+
+        entry1.updateState(null);
+        pool.release(entry1, true);
+
+        Thread.sleep(200L);
+
+        entry2.updateState(null);
+        pool.release(entry2, true);
+
+        pool.closeIdle(TimeValue.of(50, TimeUnit.MILLISECONDS));
+
+        Mockito.verify(conn1).shutdown(ShutdownType.GRACEFUL);
+        Mockito.verify(conn2, Mockito.never()).shutdown(Mockito.<ShutdownType>any());
+
+        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(TimeValue.of(-1, TimeUnit.MILLISECONDS));
+
+        Mockito.verify(conn2).shutdown(ShutdownType.GRACEFUL);
+
+        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
+    public void testLeaseRequestTimeout() throws Exception {
+        final HttpConnection conn1 = Mockito.mock(HttpConnection.class);
+
+        final LaxConnPool<String, HttpConnection> pool = new LaxConnPool<>(1, 1);
+
+        final Future<PoolEntry<String, HttpConnection>> future1 = pool.lease("somehost", null, Timeout.ofMillis(0), null);
+        final Future<PoolEntry<String, HttpConnection>> future2 = pool.lease("somehost", null, Timeout.ofMillis(0), null);
+        final Future<PoolEntry<String, HttpConnection>> future3 = pool.lease("somehost", null, Timeout.ofMillis(10), null);
+
+        Assert.assertTrue(future1.isDone());
+        final PoolEntry<String, HttpConnection> entry1 = future1.get();
+        Assert.assertNotNull(entry1);
+        entry1.assignConnection(conn1);
+        Assert.assertFalse(future2.isDone());
+        Assert.assertFalse(future3.isDone());
+
+        Thread.sleep(100);
+
+        pool.validatePendingRequests();
+
+        Assert.assertFalse(future2.isDone());
+        Assert.assertTrue(future3.isDone());
+    }
+
+    @Test
+    public void testLeaseRequestCanceled() throws Exception {
+        final LaxConnPool<String, HttpConnection> pool = new LaxConnPool<>(1, 1);
+
+        final Future<PoolEntry<String, HttpConnection>> future1 = pool.lease("somehost", null, Timeout.ofMillis(0), null);
+
+        Assert.assertTrue(future1.isDone());
+        final PoolEntry<String, HttpConnection> entry1 = future1.get();
+        Assert.assertNotNull(entry1);
+        entry1.assignConnection(Mockito.mock(HttpConnection.class));
+
+        final Future<PoolEntry<String, HttpConnection>> future2 = pool.lease("somehost", null, Timeout.ofMillis(0), null);
+        future2.cancel(true);
+
+        pool.release(entry1, true);
+
+        final PoolStats totals = pool.getTotalStats();
+        Assert.assertEquals(1, totals.getAvailable());
+        Assert.assertEquals(0, totals.getLeased());
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void testGetStatsInvalid() throws Exception {
+        final LaxConnPool<String, HttpConnection> pool = new LaxConnPool<>(2, 2);
+        pool.getStats(null);
+    }
+
+    @Test
+    public void testSetMaxInvalid() throws Exception {
+        final LaxConnPool<String, HttpConnection> pool = new LaxConnPool<>(2, 2);
+        try {
+            pool.setMaxPerRoute(null, 1);
+            Assert.fail("IllegalArgumentException should have been thrown");
+        } catch (final IllegalArgumentException expected) {
+        }
+        try {
+            pool.setMaxPerRoute("somehost", -1);
+            Assert.fail("IllegalArgumentException should have been thrown");
+        } catch (final IllegalArgumentException expected) {
+        }
+        try {
+            pool.setDefaultMaxPerRoute(-1);
+            Assert.fail("IllegalArgumentException should have been thrown");
+        } catch (final IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
+    public void testShutdown() throws Exception {
+        final LaxConnPool<String, HttpConnection> pool = new LaxConnPool<>(2, 2);
+        pool.shutdown(ShutdownType.GRACEFUL);
+        try {
+            pool.lease("somehost", null);
+            Assert.fail("IllegalStateException should have been thrown");
+        } catch (final IllegalStateException expected) {
+        }
+        // Ignored if shut down
+        pool.release(new PoolEntry<String, HttpConnection>("somehost"), true);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/httpcomponents-core/blob/41d642f1/httpcore5/src/test/java/org/apache/hc/core5/pool/TestRouteSpecificPool.java
----------------------------------------------------------------------
diff --git a/httpcore5/src/test/java/org/apache/hc/core5/pool/TestRouteSpecificPool.java b/httpcore5/src/test/java/org/apache/hc/core5/pool/TestRouteSpecificPool.java
deleted file mode 100644
index c238f40..0000000
--- a/httpcore5/src/test/java/org/apache/hc/core5/pool/TestRouteSpecificPool.java
+++ /dev/null
@@ -1,274 +0,0 @@
-/*
- * ====================================================================
- * 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.hc.core5.pool;
-
-import org.apache.hc.core5.io.GracefullyCloseable;
-import org.apache.hc.core5.io.ShutdownType;
-import org.apache.hc.core5.util.TimeValue;
-import org.junit.Assert;
-import org.junit.Test;
-import org.mockito.Mockito;
-
-public class TestRouteSpecificPool {
-
-    private static final String ROUTE = "whatever";
-
-    @Test
-    public void testEmptyPool() throws Exception {
-        final RoutePool<String, GracefullyCloseable> pool = new RoutePool<>("whatever");
-        Assert.assertEquals(ROUTE, pool.getRoute());
-        Assert.assertEquals(0, pool.getAllocatedCount());
-        Assert.assertEquals(0, pool.getAvailableCount());
-        Assert.assertEquals(0, pool.getLeasedCount());
-        Assert.assertNull(pool.getLastUsed());
-        Assert.assertEquals("[route: whatever][leased: 0][available: 0]", pool.toString());
-    }
-
-    @Test
-    public void testAdd() throws Exception {
-        final RoutePool<String, GracefullyCloseable> pool = new RoutePool<>("whatever");
-        final GracefullyCloseable conn = Mockito.mock(GracefullyCloseable.class);
-        final PoolEntry<String, GracefullyCloseable> entry = pool.createEntry(TimeValue.ZERO_MILLISECONDS);
-        entry.assignConnection(conn);
-        Assert.assertEquals(1, pool.getAllocatedCount());
-        Assert.assertEquals(0, pool.getAvailableCount());
-        Assert.assertEquals(1, pool.getLeasedCount());
-        Assert.assertNotNull(entry);
-        Assert.assertSame(conn, entry.getConnection());
-    }
-
-    @Test
-    public void testLeaseRelease() throws Exception {
-        final RoutePool<String, GracefullyCloseable> pool = new RoutePool<>("whatever");
-        final GracefullyCloseable conn1 = Mockito.mock(GracefullyCloseable.class);
-        final PoolEntry<String, GracefullyCloseable> entry1 = pool.createEntry(TimeValue.ZERO_MILLISECONDS);
-        entry1.assignConnection(conn1);
-        final GracefullyCloseable conn2 = Mockito.mock(GracefullyCloseable.class);
-        final PoolEntry<String, GracefullyCloseable> entry2 = pool.createEntry(TimeValue.ZERO_MILLISECONDS);
-        entry2.assignConnection(conn2);
-        final GracefullyCloseable conn3 = Mockito.mock(GracefullyCloseable.class);
-        final PoolEntry<String, GracefullyCloseable> entry3 = pool.createEntry(TimeValue.ZERO_MILLISECONDS);
-        entry3.assignConnection(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());
-
-        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.assertSame(entry1, pool.getLastUsed());
-
-        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());
-    }
-
-    @Test
-    public void testLeaseOrder() throws Exception {
-        final RoutePool<String, GracefullyCloseable> pool = new RoutePool<>("whatever");
-        final GracefullyCloseable conn1 = Mockito.mock(GracefullyCloseable.class);
-        final PoolEntry<String, GracefullyCloseable> entry1 = pool.createEntry(TimeValue.ZERO_MILLISECONDS);
-        entry1.assignConnection(conn1);
-        final GracefullyCloseable conn2 = Mockito.mock(GracefullyCloseable.class);
-        final PoolEntry<String, GracefullyCloseable> entry2 = pool.createEntry(TimeValue.ZERO_MILLISECONDS);
-        entry2.assignConnection(conn2);
-        final GracefullyCloseable conn3 = Mockito.mock(GracefullyCloseable.class);
-        final PoolEntry<String, GracefullyCloseable> entry3 = pool.createEntry(TimeValue.ZERO_MILLISECONDS);
-        entry3.assignConnection(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());
-
-        pool.free(entry1, true);
-        pool.free(entry2, true);
-        pool.free(entry3, true);
-
-        Assert.assertSame(entry1, pool.getLastUsed());
-
-        Assert.assertSame(entry3, pool.getFree(null));
-        Assert.assertSame(entry2, pool.getFree(null));
-        Assert.assertSame(entry1, pool.getFree(null));
-    }
-
-    @Test
-    public void testLeaseReleaseStateful() throws Exception {
-        final RoutePool<String, GracefullyCloseable> pool = new RoutePool<>("whatever");
-        final GracefullyCloseable conn1 = Mockito.mock(GracefullyCloseable.class);
-        final PoolEntry<String, GracefullyCloseable> entry1 = pool.createEntry(TimeValue.ZERO_MILLISECONDS);
-        entry1.assignConnection(conn1);
-        final GracefullyCloseable conn2 = Mockito.mock(GracefullyCloseable.class);
-        final PoolEntry<String, GracefullyCloseable> entry2 = pool.createEntry(TimeValue.ZERO_MILLISECONDS);
-        entry2.assignConnection(conn2);
-        final GracefullyCloseable conn3 = Mockito.mock(GracefullyCloseable.class);
-        final PoolEntry<String, GracefullyCloseable> entry3 = pool.createEntry(TimeValue.ZERO_MILLISECONDS);
-        entry3.assignConnection(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());
-
-        entry2.updateState(Boolean.FALSE);
-        pool.free(entry1, true);
-        pool.free(entry2, true);
-        pool.free(entry3, true);
-
-        Assert.assertSame(entry2, pool.getFree(Boolean.FALSE));
-        Assert.assertSame(entry3, pool.getFree(Boolean.FALSE));
-        Assert.assertSame(entry1, pool.getFree(null));
-        Assert.assertSame(null, pool.getFree(null));
-
-        entry1.updateState(Boolean.TRUE);
-        entry2.updateState(Boolean.FALSE);
-        entry3.updateState(Boolean.TRUE);
-        pool.free(entry1, true);
-        pool.free(entry2, true);
-        pool.free(entry3, true);
-
-        Assert.assertSame(null, pool.getFree(null));
-        Assert.assertSame(entry2, pool.getFree(Boolean.FALSE));
-        Assert.assertSame(null, pool.getFree(Boolean.FALSE));
-        Assert.assertSame(entry3, pool.getFree(Boolean.TRUE));
-        Assert.assertSame(entry1, pool.getFree(Boolean.TRUE));
-        Assert.assertSame(null, pool.getFree(Boolean.TRUE));
-    }
-
-    @Test(expected=IllegalStateException.class)
-    public void testReleaseInvalidEntry() throws Exception {
-        final RoutePool<String, GracefullyCloseable> pool = new RoutePool<>("whatever");
-        final GracefullyCloseable conn = Mockito.mock(GracefullyCloseable.class);
-        final PoolEntry<String, GracefullyCloseable> entry = new PoolEntry<>(ROUTE);
-        pool.free(entry, true);
-    }
-
-    @Test
-    public void testRemove() throws Exception {
-        final RoutePool<String, GracefullyCloseable> pool = new RoutePool<>("whatever");
-        final GracefullyCloseable conn1 = Mockito.mock(GracefullyCloseable.class);
-        final PoolEntry<String, GracefullyCloseable> entry1 = pool.createEntry(TimeValue.ZERO_MILLISECONDS);
-        entry1.assignConnection(conn1);
-        final GracefullyCloseable conn2 = Mockito.mock(GracefullyCloseable.class);
-        final PoolEntry<String, GracefullyCloseable> entry2 = pool.createEntry(TimeValue.ZERO_MILLISECONDS);
-        entry2.assignConnection(conn2);
-        final GracefullyCloseable conn3 = Mockito.mock(GracefullyCloseable.class);
-        final PoolEntry<String, GracefullyCloseable> entry3 = pool.createEntry(TimeValue.ZERO_MILLISECONDS);
-        entry3.assignConnection(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.assertTrue(pool.remove(entry2));
-        Assert.assertFalse(pool.remove(entry2));
-
-        Assert.assertEquals(2, pool.getAllocatedCount());
-        Assert.assertEquals(0, pool.getAvailableCount());
-        Assert.assertEquals(2, pool.getLeasedCount());
-
-        pool.free(entry1, true);
-        pool.free(entry3, true);
-
-        Assert.assertEquals(2, pool.getAllocatedCount());
-        Assert.assertEquals(2, pool.getAvailableCount());
-        Assert.assertEquals(0, pool.getLeasedCount());
-
-        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());
-    }
-
-    @Test(expected=IllegalArgumentException.class)
-    public void testReleaseInvalid() throws Exception {
-        final RoutePool<String, GracefullyCloseable> pool = new RoutePool<>("whatever");
-        pool.free(null, true);
-    }
-
-    @Test(expected=IllegalArgumentException.class)
-    public void testRemoveInvalid() throws Exception {
-        final RoutePool<String, GracefullyCloseable> pool = new RoutePool<>("whatever");
-        pool.remove(null);
-    }
-
-    @Test
-    public void testShutdown() throws Exception {
-        final RoutePool<String, GracefullyCloseable> pool = new RoutePool<>("whatever");
-        final GracefullyCloseable conn1 = Mockito.mock(GracefullyCloseable.class);
-        final PoolEntry<String, GracefullyCloseable> entry1 = pool.createEntry(TimeValue.ZERO_MILLISECONDS);
-        entry1.assignConnection(conn1);
-        final GracefullyCloseable conn2 = Mockito.mock(GracefullyCloseable.class);
-        final PoolEntry<String, GracefullyCloseable> entry2 = pool.createEntry(TimeValue.ZERO_MILLISECONDS);
-        entry2.assignConnection(conn2);
-
-        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());
-
-        pool.shutdown(ShutdownType.GRACEFUL);
-
-        Assert.assertEquals(0, pool.getAllocatedCount());
-        Assert.assertEquals(0, pool.getAvailableCount());
-        Assert.assertEquals(0, pool.getLeasedCount());
-
-        Mockito.verify(conn2).shutdown(ShutdownType.GRACEFUL);
-        Mockito.verify(conn1).shutdown(ShutdownType.GRACEFUL);
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/httpcomponents-core/blob/41d642f1/httpcore5/src/test/java/org/apache/hc/core5/pool/TestStrictConnPool.java
----------------------------------------------------------------------
diff --git a/httpcore5/src/test/java/org/apache/hc/core5/pool/TestStrictConnPool.java b/httpcore5/src/test/java/org/apache/hc/core5/pool/TestStrictConnPool.java
index 732513e..41dfa9a 100644
--- a/httpcore5/src/test/java/org/apache/hc/core5/pool/TestStrictConnPool.java
+++ b/httpcore5/src/test/java/org/apache/hc/core5/pool/TestStrictConnPool.java
@@ -120,7 +120,7 @@ public class TestStrictConnPool {
         }
     }
 
-    @Test
+    @Test(expected = IllegalStateException.class)
     public void testReleaseUnknownEntry() throws Exception {
         final StrictConnPool<String, HttpConnection> pool = new StrictConnPool<>(2, 2);
         pool.release(new PoolEntry<String, HttpConnection>("somehost"), true);