org.apache.http.nio.pool.AbstractNIOConnPool Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of httpcore-nio Show documentation
Show all versions of httpcore-nio Show documentation
Apache HttpComponents Core (non-blocking I/O)
/*
* ====================================================================
* 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
* .
*
*/
package org.apache.http.nio.pool;
import java.io.IOException;
import java.net.SocketAddress;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.http.annotation.Contract;
import org.apache.http.annotation.ThreadingBehavior;
import org.apache.http.concurrent.BasicFuture;
import org.apache.http.concurrent.FutureCallback;
import org.apache.http.nio.reactor.ConnectingIOReactor;
import org.apache.http.nio.reactor.IOReactorStatus;
import org.apache.http.nio.reactor.IOSession;
import org.apache.http.nio.reactor.SessionRequest;
import org.apache.http.nio.reactor.SessionRequestCallback;
import org.apache.http.pool.ConnPool;
import org.apache.http.pool.ConnPoolControl;
import org.apache.http.pool.PoolEntry;
import org.apache.http.pool.PoolEntryCallback;
import org.apache.http.pool.PoolStats;
import org.apache.http.util.Args;
import org.apache.http.util.Asserts;
import org.apache.http.util.LangUtils;
/**
* Abstract non-blocking connection pool.
*
* @param route
* @param connection object
* @param pool entry
*
* @since 4.2
*/
@Contract(threading = ThreadingBehavior.SAFE_CONDITIONAL)
public abstract class AbstractNIOConnPool>
implements ConnPool, ConnPoolControl {
private final ConnectingIOReactor ioreactor;
private final NIOConnFactory connFactory;
private final SocketAddressResolver addressResolver;
private final SessionRequestCallback sessionRequestCallback;
private final Map> routeToPool;
private final LinkedList> leasingRequests;
private final Set pending;
private final Set leased;
private final LinkedList available;
private final ConcurrentLinkedQueue> completedRequests;
private final Map maxPerRoute;
private final Lock lock;
private final AtomicBoolean isShutDown;
private volatile int defaultMaxPerRoute;
private volatile int maxTotal;
/**
* @deprecated use {@link AbstractNIOConnPool#AbstractNIOConnPool(ConnectingIOReactor,
* NIOConnFactory, SocketAddressResolver, int, int)}
*/
@Deprecated
public AbstractNIOConnPool(
final ConnectingIOReactor ioreactor,
final NIOConnFactory connFactory,
final int defaultMaxPerRoute,
final int maxTotal) {
super();
Args.notNull(ioreactor, "I/O reactor");
Args.notNull(connFactory, "Connection factory");
Args.positive(defaultMaxPerRoute, "Max per route value");
Args.positive(maxTotal, "Max total value");
this.ioreactor = ioreactor;
this.connFactory = connFactory;
this.addressResolver = new SocketAddressResolver() {
@Override
public SocketAddress resolveLocalAddress(final T route) throws IOException {
return AbstractNIOConnPool.this.resolveLocalAddress(route);
}
@Override
public SocketAddress resolveRemoteAddress(final T route) throws IOException {
return AbstractNIOConnPool.this.resolveRemoteAddress(route);
}
};
this.sessionRequestCallback = new InternalSessionRequestCallback();
this.routeToPool = new HashMap>();
this.leasingRequests = new LinkedList>();
this.pending = new HashSet();
this.leased = new HashSet();
this.available = new LinkedList();
this.maxPerRoute = new HashMap();
this.completedRequests = new ConcurrentLinkedQueue>();
this.lock = new ReentrantLock();
this.isShutDown = new AtomicBoolean(false);
this.defaultMaxPerRoute = defaultMaxPerRoute;
this.maxTotal = maxTotal;
}
/**
* @since 4.3
*/
public AbstractNIOConnPool(
final ConnectingIOReactor ioreactor,
final NIOConnFactory connFactory,
final SocketAddressResolver addressResolver,
final int defaultMaxPerRoute,
final int maxTotal) {
super();
Args.notNull(ioreactor, "I/O reactor");
Args.notNull(connFactory, "Connection factory");
Args.notNull(addressResolver, "Address resolver");
Args.positive(defaultMaxPerRoute, "Max per route value");
Args.positive(maxTotal, "Max total value");
this.ioreactor = ioreactor;
this.connFactory = connFactory;
this.addressResolver = addressResolver;
this.sessionRequestCallback = new InternalSessionRequestCallback();
this.routeToPool = new HashMap>();
this.leasingRequests = new LinkedList>();
this.pending = new HashSet();
this.leased = new HashSet();
this.available = new LinkedList();
this.completedRequests = new ConcurrentLinkedQueue>();
this.maxPerRoute = new HashMap();
this.lock = new ReentrantLock();
this.isShutDown = new AtomicBoolean(false);
this.defaultMaxPerRoute = defaultMaxPerRoute;
this.maxTotal = maxTotal;
}
/**
* @deprecated (4.3) use {@link SocketAddressResolver}
*/
@Deprecated
protected SocketAddress resolveRemoteAddress(final T route) {
return null;
}
/**
* @deprecated (4.3) use {@link SocketAddressResolver}
*/
@Deprecated
protected SocketAddress resolveLocalAddress(final T route) {
return null;
}
protected abstract E createEntry(T route, C conn);
/**
* @since 4.3
*/
protected void onLease(final E entry) {
}
/**
* @since 4.3
*/
protected void onRelease(final E entry) {
}
/**
* @since 4.4
*/
protected void onReuse(final E entry) {
}
public boolean isShutdown() {
return this.isShutDown.get();
}
public void shutdown(final long waitMs) throws IOException {
if (this.isShutDown.compareAndSet(false, true)) {
fireCallbacks();
this.lock.lock();
try {
for (final SessionRequest sessionRequest: this.pending) {
sessionRequest.cancel();
}
for (final E entry: this.available) {
entry.close();
}
for (final E entry: this.leased) {
entry.close();
}
for (final RouteSpecificPool pool: this.routeToPool.values()) {
pool.shutdown();
}
this.routeToPool.clear();
this.leased.clear();
this.pending.clear();
this.available.clear();
this.leasingRequests.clear();
this.ioreactor.shutdown(waitMs);
} finally {
this.lock.unlock();
}
}
}
private RouteSpecificPool getPool(final T route) {
RouteSpecificPool pool = this.routeToPool.get(route);
if (pool == null) {
pool = new RouteSpecificPool(route) {
@Override
protected E createEntry(final T route, final C conn) {
return AbstractNIOConnPool.this.createEntry(route, conn);
}
};
this.routeToPool.put(route, pool);
}
return pool;
}
public Future lease(
final T route, final Object state,
final long connectTimeout, final TimeUnit tunit,
final FutureCallback callback) {
return this.lease(route, state, connectTimeout, connectTimeout, tunit, callback);
}
/**
* @since 4.3
*/
public Future lease(
final T route, final Object state,
final long connectTimeout, final long leaseTimeout, final TimeUnit tunit,
final FutureCallback callback) {
Args.notNull(route, "Route");
Args.notNull(tunit, "Time unit");
Asserts.check(!this.isShutDown.get(), "Connection pool shut down");
final BasicFuture future = new BasicFuture(callback);
final LeaseRequest leaseRequest = new LeaseRequest(route, state,
connectTimeout >= 0 ? tunit.toMillis(connectTimeout) : -1,
leaseTimeout > 0 ? tunit.toMillis(leaseTimeout) : 0,
future);
this.lock.lock();
try {
final boolean completed = processPendingRequest(leaseRequest);
if (!leaseRequest.isDone() && !completed) {
this.leasingRequests.add(leaseRequest);
}
if (leaseRequest.isDone()) {
this.completedRequests.add(leaseRequest);
}
} finally {
this.lock.unlock();
}
fireCallbacks();
return new Future() {
@Override
public E get() throws InterruptedException, ExecutionException {
return future.get();
}
@Override
public E get(
final long timeout,
final TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
return future.get(timeout, unit);
}
@Override
public boolean cancel(final boolean mayInterruptIfRunning) {
try {
leaseRequest.cancel();
} finally {
return future.cancel(mayInterruptIfRunning);
}
}
@Override
public boolean isCancelled() {
return future.isCancelled();
}
@Override
public boolean isDone() {
return future.isDone();
}
};
}
@Override
public Future lease(final T route, final Object state, final FutureCallback callback) {
return lease(route, state, -1, TimeUnit.MICROSECONDS, callback);
}
public Future lease(final T route, final Object state) {
return lease(route, state, -1, TimeUnit.MICROSECONDS, null);
}
@Override
public void release(final E entry, final boolean reusable) {
if (entry == null) {
return;
}
if (this.isShutDown.get()) {
return;
}
this.lock.lock();
try {
if (this.leased.remove(entry)) {
final RouteSpecificPool pool = getPool(entry.getRoute());
pool.free(entry, reusable);
if (reusable) {
this.available.addFirst(entry);
onRelease(entry);
} else {
entry.close();
}
processNextPendingRequest();
}
} finally {
this.lock.unlock();
}
fireCallbacks();
}
private void processPendingRequests() {
final ListIterator> it = this.leasingRequests.listIterator();
while (it.hasNext()) {
final LeaseRequest request = it.next();
final BasicFuture future = request.getFuture();
if (future.isCancelled()) {
it.remove();
continue;
}
final boolean completed = processPendingRequest(request);
if (request.isDone() || completed) {
it.remove();
}
if (request.isDone()) {
this.completedRequests.add(request);
}
}
}
private void processNextPendingRequest() {
final ListIterator> it = this.leasingRequests.listIterator();
while (it.hasNext()) {
final LeaseRequest request = it.next();
final BasicFuture future = request.getFuture();
if (future.isCancelled()) {
it.remove();
continue;
}
final boolean completed = processPendingRequest(request);
if (request.isDone() || completed) {
it.remove();
}
if (request.isDone()) {
this.completedRequests.add(request);
}
if (completed) {
return;
}
}
}
private boolean processPendingRequest(final LeaseRequest request) {
final T route = request.getRoute();
final Object state = request.getState();
final long deadline = request.getDeadline();
final long now = System.currentTimeMillis();
if (now > deadline) {
request.failed(new TimeoutException("Connection lease request time out"));
return false;
}
final RouteSpecificPool pool = getPool(route);
E entry;
for (;;) {
entry = pool.getFree(state);
if (entry == null) {
break;
}
if (entry.isClosed() || entry.isExpired(System.currentTimeMillis())) {
entry.close();
this.available.remove(entry);
pool.free(entry, false);
} else {
break;
}
}
if (entry != null) {
this.available.remove(entry);
this.leased.add(entry);
request.completed(entry);
onReuse(entry);
onLease(entry);
return true;
}
// New connection is needed
final int maxPerRoute = getMax(route);
// Shrink the pool prior to allocating a new connection
final int excess = Math.max(0, pool.getAllocatedCount() + 1 - maxPerRoute);
if (excess > 0) {
for (int i = 0; i < excess; i++) {
final E lastUsed = pool.getLastUsed();
if (lastUsed == null) {
break;
}
lastUsed.close();
this.available.remove(lastUsed);
pool.remove(lastUsed);
}
}
if (pool.getAllocatedCount() < maxPerRoute) {
final int totalUsed = this.pending.size() + this.leased.size();
final int freeCapacity = Math.max(this.maxTotal - totalUsed, 0);
if (freeCapacity == 0) {
return false;
}
final int totalAvailable = this.available.size();
if (totalAvailable > freeCapacity - 1) {
if (!this.available.isEmpty()) {
final E lastUsed = this.available.removeLast();
lastUsed.close();
final RouteSpecificPool otherpool = getPool(lastUsed.getRoute());
otherpool.remove(lastUsed);
}
}
final SocketAddress localAddress;
final SocketAddress remoteAddress;
try {
remoteAddress = this.addressResolver.resolveRemoteAddress(route);
localAddress = this.addressResolver.resolveLocalAddress(route);
} catch (final IOException ex) {
request.failed(ex);
return false;
}
final SessionRequest sessionRequest = this.ioreactor.connect(
remoteAddress, localAddress, route, this.sessionRequestCallback);
request.attachSessionRequest(sessionRequest);
final long connectTimeout = request.getConnectTimeout();
if (connectTimeout >= 0) {
sessionRequest.setConnectTimeout(connectTimeout < Integer.MAX_VALUE ? (int) connectTimeout : Integer.MAX_VALUE);
}
this.pending.add(sessionRequest);
pool.addPending(sessionRequest, request.getFuture());
return true;
} else {
return false;
}
}
private void fireCallbacks() {
LeaseRequest request;
while ((request = this.completedRequests.poll()) != null) {
final BasicFuture future = request.getFuture();
final Exception ex = request.getException();
final E result = request.getResult();
boolean successfullyCompleted = false;
if (ex != null) {
future.failed(ex);
} else if (result != null) {
if (future.completed(result)) {
successfullyCompleted = true;
}
} else {
future.cancel();
}
if (!successfullyCompleted) {
release(result, true);
}
}
}
public void validatePendingRequests() {
this.lock.lock();
try {
final long now = System.currentTimeMillis();
final ListIterator> it = this.leasingRequests.listIterator();
while (it.hasNext()) {
final LeaseRequest request = it.next();
final BasicFuture future = request.getFuture();
if (future.isCancelled() && !request.isDone()) {
it.remove();
} else {
final long deadline = request.getDeadline();
if (now > deadline) {
request.failed(new TimeoutException("Connection lease request time out"));
}
if (request.isDone()) {
it.remove();
this.completedRequests.add(request);
}
}
}
} finally {
this.lock.unlock();
}
fireCallbacks();
}
protected void requestCompleted(final SessionRequest request) {
if (this.isShutDown.get()) {
return;
}
@SuppressWarnings("unchecked")
final
T route = (T) request.getAttachment();
this.lock.lock();
try {
this.pending.remove(request);
final RouteSpecificPool pool = getPool(route);
final IOSession session = request.getSession();
try {
final C conn = this.connFactory.create(route, session);
final E entry = pool.createEntry(request, conn);
if (pool.completed(request, entry)) {
this.leased.add(entry);
onLease(entry);
} else {
this.available.add(entry);
if (this.ioreactor.getStatus().compareTo(IOReactorStatus.ACTIVE) <= 0) {
processNextPendingRequest();
}
}
} catch (final IOException ex) {
pool.failed(request, ex);
}
} finally {
this.lock.unlock();
}
fireCallbacks();
}
protected void requestCancelled(final SessionRequest request) {
if (this.isShutDown.get()) {
return;
}
@SuppressWarnings("unchecked")
final
T route = (T) request.getAttachment();
this.lock.lock();
try {
this.pending.remove(request);
final RouteSpecificPool pool = getPool(route);
pool.cancelled(request);
if (this.ioreactor.getStatus().compareTo(IOReactorStatus.ACTIVE) <= 0) {
processNextPendingRequest();
}
} finally {
this.lock.unlock();
}
fireCallbacks();
}
protected void requestFailed(final SessionRequest request) {
if (this.isShutDown.get()) {
return;
}
@SuppressWarnings("unchecked")
final
T route = (T) request.getAttachment();
this.lock.lock();
try {
this.pending.remove(request);
final RouteSpecificPool pool = getPool(route);
pool.failed(request, request.getException());
processNextPendingRequest();
} finally {
this.lock.unlock();
}
fireCallbacks();
}
protected void requestTimeout(final SessionRequest request) {
if (this.isShutDown.get()) {
return;
}
@SuppressWarnings("unchecked")
final
T route = (T) request.getAttachment();
this.lock.lock();
try {
this.pending.remove(request);
final RouteSpecificPool pool = getPool(route);
pool.timeout(request);
processNextPendingRequest();
} finally {
this.lock.unlock();
}
fireCallbacks();
}
private int getMax(final T route) {
final Integer v = this.maxPerRoute.get(route);
if (v != null) {
return v.intValue();
} else {
return this.defaultMaxPerRoute;
}
}
@Override
public void setMaxTotal(final int max) {
Args.positive(max, "Max value");
this.lock.lock();
try {
this.maxTotal = max;
} finally {
this.lock.unlock();
}
}
@Override
public int getMaxTotal() {
this.lock.lock();
try {
return this.maxTotal;
} finally {
this.lock.unlock();
}
}
@Override
public void setDefaultMaxPerRoute(final int max) {
Args.positive(max, "Max value");
this.lock.lock();
try {
this.defaultMaxPerRoute = max;
} finally {
this.lock.unlock();
}
}
@Override
public int getDefaultMaxPerRoute() {
this.lock.lock();
try {
return this.defaultMaxPerRoute;
} finally {
this.lock.unlock();
}
}
@Override
public void setMaxPerRoute(final T route, final int max) {
Args.notNull(route, "Route");
this.lock.lock();
try {
if (max > -1) {
this.maxPerRoute.put(route, Integer.valueOf(max));
} else {
this.maxPerRoute.remove(route);
}
} finally {
this.lock.unlock();
}
}
@Override
public int getMaxPerRoute(final T route) {
Args.notNull(route, "Route");
this.lock.lock();
try {
return getMax(route);
} finally {
this.lock.unlock();
}
}
@Override
public PoolStats getTotalStats() {
this.lock.lock();
try {
return new PoolStats(
this.leased.size(),
this.pending.size(),
this.available.size(),
this.maxTotal);
} finally {
this.lock.unlock();
}
}
@Override
public PoolStats getStats(final T route) {
Args.notNull(route, "Route");
this.lock.lock();
try {
final RouteSpecificPool pool = getPool(route);
int pendingCount = 0;
for (final LeaseRequest request: leasingRequests) {
if (LangUtils.equals(route, request.getRoute())) {
pendingCount++;
}
}
return new PoolStats(
pool.getLeasedCount(),
pendingCount + pool.getPendingCount(),
pool.getAvailableCount(),
getMax(route));
} finally {
this.lock.unlock();
}
}
/**
* Returns snapshot of all knows routes
*
* @since 4.4
*/
public Set getRoutes() {
this.lock.lock();
try {
return new HashSet(routeToPool.keySet());
} finally {
this.lock.unlock();
}
}
/**
* Enumerates all available connections.
*
* @since 4.3
*/
protected void enumAvailable(final PoolEntryCallback callback) {
this.lock.lock();
try {
final Iterator it = this.available.iterator();
while (it.hasNext()) {
final E entry = it.next();
callback.process(entry);
if (entry.isClosed()) {
final RouteSpecificPool pool = getPool(entry.getRoute());
pool.remove(entry);
it.remove();
}
}
processPendingRequests();
purgePoolMap();
} finally {
this.lock.unlock();
}
}
/**
* Enumerates all leased connections.
*
* @since 4.3
*/
protected void enumLeased(final PoolEntryCallback callback) {
this.lock.lock();
try {
final Iterator it = this.leased.iterator();
while (it.hasNext()) {
final E entry = it.next();
callback.process(entry);
}
processPendingRequests();
} finally {
this.lock.unlock();
}
}
/**
* Use {@link #enumLeased(org.apache.http.pool.PoolEntryCallback)}
* or {@link #enumAvailable(org.apache.http.pool.PoolEntryCallback)} instead.
*
* @deprecated (4.3.2)
*/
@Deprecated
protected void enumEntries(final Iterator it, final PoolEntryCallback callback) {
while (it.hasNext()) {
final E entry = it.next();
callback.process(entry);
}
processPendingRequests();
}
private void purgePoolMap() {
final Iterator>> it = this.routeToPool.entrySet().iterator();
while (it.hasNext()) {
final Map.Entry> entry = it.next();
final RouteSpecificPool pool = entry.getValue();
if (pool.getAllocatedCount() == 0) {
it.remove();
}
}
}
public void closeIdle(final long idletime, final TimeUnit tunit) {
Args.notNull(tunit, "Time unit");
long time = tunit.toMillis(idletime);
if (time < 0) {
time = 0;
}
final long deadline = System.currentTimeMillis() - time;
enumAvailable(new PoolEntryCallback() {
@Override
public void process(final PoolEntry entry) {
if (entry.getUpdated() <= deadline) {
entry.close();
}
}
});
}
public void closeExpired() {
final long now = System.currentTimeMillis();
enumAvailable(new PoolEntryCallback() {
@Override
public void process(final PoolEntry entry) {
if (entry.isExpired(now)) {
entry.close();
}
}
});
}
@Override
public String toString() {
final StringBuilder buffer = new StringBuilder();
buffer.append("[leased: ");
buffer.append(this.leased);
buffer.append("][available: ");
buffer.append(this.available);
buffer.append("][pending: ");
buffer.append(this.pending);
buffer.append("]");
return buffer.toString();
}
class InternalSessionRequestCallback implements SessionRequestCallback {
@Override
public void completed(final SessionRequest request) {
requestCompleted(request);
}
@Override
public void cancelled(final SessionRequest request) {
requestCancelled(request);
}
@Override
public void failed(final SessionRequest request) {
requestFailed(request);
}
@Override
public void timeout(final SessionRequest request) {
requestTimeout(request);
}
}
}