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);