All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.feilong.lib.org.apache.http.pool.AbstractConnPool Maven / Gradle / Ivy

Go to download

feilong is a suite of core and expanded libraries that include utility classes, http, excel,cvs, io classes, and much much more.

The newest version!
/*
 * ====================================================================
 * 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 com.feilong.lib.org.apache.http.pool;

import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CancellationException;
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.atomic.AtomicReference;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import com.feilong.lib.org.apache.http.annotation.Contract;
import com.feilong.lib.org.apache.http.annotation.ThreadingBehavior;
import com.feilong.lib.org.apache.http.concurrent.FutureCallback;
import com.feilong.lib.org.apache.http.util.Args;
import com.feilong.lib.org.apache.http.util.Asserts;

/**
 * Abstract synchronous (blocking) pool of connections.
 * 

* Please note that this class does not maintain its own pool of execution {@link Thread}s. * Therefore, one must call {@link Future#get()} or {@link Future#get(long, TimeUnit)} * method on the {@link Future} object returned by the * {@link #lease(Object, Object, FutureCallback)} method in order for the lease operation * to complete. * * @param * the route type that represents the opposite endpoint of a pooled * connection. * @param * the connection type. * @param * the type of the pool entry containing a pooled connection. * @since 4.2 */ @Contract(threading = ThreadingBehavior.SAFE_CONDITIONAL) public abstract class AbstractConnPool> implements ConnPool,ConnPoolControl{ private final Lock lock; private final Condition condition; private final ConnFactory connFactory; private final Map> routeToPool; private final Set leased; private final LinkedList available; private final LinkedList> pending; private final Map maxPerRoute; private volatile boolean isShutDown; private volatile int defaultMaxPerRoute; private volatile int maxTotal; private volatile int validateAfterInactivity; public AbstractConnPool(final ConnFactory connFactory, final int defaultMaxPerRoute, final int maxTotal){ super(); this.connFactory = Args.notNull(connFactory, "Connection factory"); this.defaultMaxPerRoute = Args.positive(defaultMaxPerRoute, "Max per route value"); this.maxTotal = Args.positive(maxTotal, "Max total value"); this.lock = new ReentrantLock(); this.condition = this.lock.newCondition(); this.routeToPool = new HashMap<>(); this.leased = new HashSet<>(); this.available = new LinkedList<>(); this.pending = new LinkedList<>(); this.maxPerRoute = new HashMap<>(); } /** * Creates a new entry for the given connection with the given route. */ 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){ } /** * @since 4.4 */ protected boolean validate(final E entry){ return true; } public boolean isShutdown(){ return this.isShutDown; } /** * Shuts down the pool. */ public void shutdown() throws IOException{ if (this.isShutDown){ return; } this.isShutDown = true; this.lock.lock(); try{ 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.available.clear(); }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 C conn){ return AbstractConnPool.this.createEntry(route, conn); } }; this.routeToPool.put(route, pool); } return pool; } private static Exception operationAborted(){ return new CancellationException("Operation aborted"); } /** * {@inheritDoc} *

* Please note that this class does not maintain its own pool of execution * {@link Thread}s. Therefore, one must call {@link Future#get()} * or {@link Future#get(long, TimeUnit)} method on the {@link Future} * returned by this method in order for the lease operation to complete. */ @Override public Future lease(final T route,final Object state,final FutureCallback callback){ Args.notNull(route, "Route"); Asserts.check(!this.isShutDown, "Connection pool shut down"); return new Future(){ private final AtomicBoolean cancelled = new AtomicBoolean(false); private final AtomicBoolean done = new AtomicBoolean(false); private final AtomicReference entryRef = new AtomicReference<>(null); @Override public boolean cancel(final boolean mayInterruptIfRunning){ if (done.compareAndSet(false, true)){ cancelled.set(true); lock.lock(); try{ condition.signalAll(); }finally{ lock.unlock(); } if (callback != null){ callback.cancelled(); } return true; } return false; } @Override public boolean isCancelled(){ return cancelled.get(); } @Override public boolean isDone(){ return done.get(); } @Override public E get() throws InterruptedException,ExecutionException{ try{ return get(0L, TimeUnit.MILLISECONDS); }catch (final TimeoutException ex){ throw new ExecutionException(ex); } } @Override public E get(final long timeout,final TimeUnit timeUnit) throws InterruptedException,ExecutionException,TimeoutException{ for (;;){ synchronized (this){ try{ final E entry = entryRef.get(); if (entry != null){ return entry; } if (done.get()){ throw new ExecutionException(operationAborted()); } final E leasedEntry = getPoolEntryBlocking(route, state, timeout, timeUnit, this); if (validateAfterInactivity > 0){ if (leasedEntry.getUpdated() + validateAfterInactivity <= System.currentTimeMillis()){ if (!validate(leasedEntry)){ leasedEntry.close(); release(leasedEntry, false); continue; } } } if (done.compareAndSet(false, true)){ entryRef.set(leasedEntry); done.set(true); onLease(leasedEntry); if (callback != null){ callback.completed(leasedEntry); } return leasedEntry; } release(leasedEntry, true); throw new ExecutionException(operationAborted()); }catch (final IOException ex){ if (done.compareAndSet(false, true)){ if (callback != null){ callback.failed(ex); } } throw new ExecutionException(ex); } } } } }; } /** * Attempts to lease a connection for the given route and with the given * state from the pool. *

* Please note that this class does not maintain its own pool of execution * {@link Thread}s. Therefore, one must call {@link Future#get()} * or {@link Future#get(long, TimeUnit)} method on the {@link Future} * returned by this method in order for the lease operation to complete. * * @param route * route of the connection. * @param state * arbitrary object that represents a particular state * (usually a security principal or a unique token identifying * the user whose credentials have been used while establishing the connection). * May be {@code null}. * @return future for a leased pool entry. */ public Future lease(final T route,final Object state){ return lease(route, state, null); } private E getPoolEntryBlocking(final T route,final Object state,final long timeout,final TimeUnit timeUnit,final Future future) throws IOException,InterruptedException,ExecutionException,TimeoutException{ Date deadline = null; if (timeout > 0){ deadline = new Date(System.currentTimeMillis() + timeUnit.toMillis(timeout)); } this.lock.lock(); try{ E entry; for (;;){ Asserts.check(!this.isShutDown, "Connection pool shut down"); if (future.isCancelled()){ throw new ExecutionException(operationAborted()); } final RouteSpecificPool pool = getPool(route); for (;;){ entry = pool.getFree(state); if (entry == null){ break; } if (entry.isExpired(System.currentTimeMillis())){ entry.close(); } if (entry.isClosed()){ this.available.remove(entry); pool.free(entry, false); }else{ break; } } if (entry != null){ this.available.remove(entry); this.leased.add(entry); onReuse(entry); return entry; } // 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.leased.size(); final int freeCapacity = Math.max(this.maxTotal - totalUsed, 0); if (freeCapacity > 0){ final int totalAvailable = this.available.size(); if (totalAvailable > freeCapacity - 1){ final E lastUsed = this.available.removeLast(); lastUsed.close(); final RouteSpecificPool otherpool = getPool(lastUsed.getRoute()); otherpool.remove(lastUsed); } final C conn = this.connFactory.create(route); entry = pool.add(conn); this.leased.add(entry); return entry; } } boolean success = false; try{ pool.queue(future); this.pending.add(future); if (deadline != null){ success = this.condition.awaitUntil(deadline); }else{ this.condition.await(); success = true; } if (future.isCancelled()){ throw new ExecutionException(operationAborted()); } }finally{ // In case of 'success', we were woken up by the // connection pool and should now have a connection // waiting for us, or else we're shutting down. // Just continue in the loop, both cases are checked. pool.unqueue(future); this.pending.remove(future); } // check for spurious wakeup vs. timeout if (!success && (deadline != null && deadline.getTime() <= System.currentTimeMillis())){ break; } } throw new TimeoutException("Timeout waiting for connection"); }finally{ this.lock.unlock(); } } @Override public void release(final E entry,final boolean reusable){ this.lock.lock(); try{ if (this.leased.remove(entry)){ final RouteSpecificPool pool = getPool(entry.getRoute()); pool.free(entry, reusable); if (reusable && !this.isShutDown){ this.available.addFirst(entry); }else{ entry.close(); } onRelease(entry); Future future = pool.nextPending(); if (future != null){ this.pending.remove(future); }else{ future = this.pending.poll(); } if (future != null){ this.condition.signalAll(); } } }finally{ this.lock.unlock(); } } private int getMax(final T route){ final Integer v = this.maxPerRoute.get(route); return v != null ? v : 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 per route 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, 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); return new PoolStats(pool.getLeasedCount(), pool.getPendingCount(), pool.getAvailableCount(), getMax(route)); }finally{ this.lock.unlock(); } } /** * Returns snapshot of all knows routes * * @return the set of 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(); } } 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); } }finally{ this.lock.unlock(); } } 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.getPendingCount() + pool.getAllocatedCount() == 0){ it.remove(); } } } /** * Closes connections that have been idle longer than the given period * of time and evicts them from the pool. * * @param idletime * maximum idle time. * @param timeUnit * time unit. */ public void closeIdle(final long idletime,final TimeUnit timeUnit){ Args.notNull(timeUnit, "Time unit"); long time = timeUnit.toMillis(idletime); if (time < 0){ time = 0; } final long deadline = System.currentTimeMillis() - time; enumAvailable(entry -> { if (entry.getUpdated() <= deadline){ entry.close(); } }); } /** * Closes expired connections and evicts them from the pool. */ public void closeExpired(){ final long now = System.currentTimeMillis(); enumAvailable(entry -> { if (entry.isExpired(now)){ entry.close(); } }); } /** * @return the number of milliseconds * @since 4.4 */ public int getValidateAfterInactivity(){ return this.validateAfterInactivity; } /** * @param ms * the number of milliseconds * @since 4.4 */ public void setValidateAfterInactivity(final int ms){ this.validateAfterInactivity = ms; } @Override public String toString(){ this.lock.lock(); try{ 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(); }finally{ this.lock.unlock(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy