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

io.jsync.http.impl.PriorityHttpConnectionPool Maven / Gradle / Ivy

There is a newer version: 1.10.13
Show newest version
/*
 * Copyright (c) 2011-2013 The original author or authors
 * ------------------------------------------------------
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and Apache License v2.0 which accompanies this distribution.
 *
 *     The Eclipse Public License is available at
 *     http://www.eclipse.org/legal/epl-v10.html
 *
 *     The Apache License v2.0 is available at
 *     http://www.opensource.org/licenses/apache2.0.php
 *
 * You may elect to redistribute this code under either of these licenses.
 */

package io.jsync.http.impl;

import io.jsync.Handler;
import io.jsync.impl.ConcurrentHashSet;
import io.jsync.impl.DefaultContext;
import io.jsync.logging.Logger;
import io.jsync.logging.impl.LoggerFactory;

import java.util.*;

/**
 * @author Nathan Pahucki
 * @author Tim Fox
 */
public abstract class PriorityHttpConnectionPool implements HttpPool {

    private static final Logger log = LoggerFactory.getLogger(PriorityHttpConnectionPool.class);

    private final Set available = new HashSet<>();
    private final Set allConnections = new ConcurrentHashSet<>();
    private final Queue waiters = new ArrayDeque<>();
    private int maxPoolSize = 1;
    private int connectionCount;
    private int maxWaiterQueueSize = -1;

    /**
     * Returns the maximum number of connections in the pool
     */
    public int getMaxPoolSize() {
        return maxPoolSize;
    }

    /**
     * Set the maximum pool size to the value specified by {@code maxConnections}

* The client will maintain up to {@code maxConnections} HTTP connections in an internal pool

*/ public void setMaxPoolSize(int maxConnections) { this.maxPoolSize = maxConnections; } public int getMaxWaiterQueueSize() { return maxWaiterQueueSize; } public void setMaxWaiterQueueSize(final int maxWaiterQueueSize) { this.maxWaiterQueueSize = maxWaiterQueueSize; } public synchronized void report() { log.trace("available: " + available.size() + " connection count: " + connectionCount + " waiters: " + waiters.size()); } public void getConnection(final Handler handler, Handler connectExceptionHandler, DefaultContext context) { boolean connect = false; ClientConnection conn; outer: synchronized (this) { conn = selectConnection(available, connectionCount, maxPoolSize); if (conn != null) { break outer; } else { if (connectionCount < maxPoolSize) { //Create new connection connect = true; connectionCount++; break outer; } // Add to waiters if (maxWaiterQueueSize == -1 || maxWaiterQueueSize > waiters.size()) { waiters.add(new Waiter(handler, connectExceptionHandler, context)); } else { // There are too many requests in waiter queue. Return exception to avoid OOM. connectExceptionHandler.handle(new ConnectionPoolTooBusyException("Too many requests to be handled. The request will be cancelled to avoid OOM.")); } } } // We do this outside the sync block to minimise the critical section if (conn != null) { handler.handle(conn); } else if (connect) { connect(new Handler() { @Override public void handle(ClientConnection conn) { allConnections.add(conn); handler.handle(conn); } }, connectExceptionHandler, context); } } /** * Inform the pool that the connection has been closed externally * or the connection attempt failed */ public void connectionClosed(ClientConnection conn) { Waiter waiter; synchronized (this) { connectionCount--; if (conn != null) { allConnections.remove(conn); available.remove(conn); } if (connectionCount < maxPoolSize) { //Now the connection count has come down, maybe there is another waiter that can //create a new connection waiter = waiters.poll(); if (waiter != null) { connectionCount++; } } else { waiter = null; } } // We do the actual connect outside the sync block to minimise the critical section if (waiter != null) { connect(waiter.handler, waiter.connectionExceptionHandler, waiter.context); } } /** * Return a connection to the pool so it can be used by others. */ public void returnConnection(final ClientConnection conn) { Waiter waiter; synchronized (this) { //Return it to the pool waiter = waiters.poll(); if (waiter == null) { available.add(conn); } } if (waiter != null) { final Waiter w = waiter; w.context.execute(new Runnable() { public void run() { w.handler.handle(conn); } }); } } /** * Close the pool */ public void close() { synchronized (this) { available.clear(); waiters.clear(); } for (ClientConnection conn : allConnections) { try { conn.actualClose(); } catch (Throwable t) { log.error("Failed to close connection", t); } } allConnections.clear(); } /** * Implement this method in a sub-class to implement the actual connection creation for the specific type of connection */ protected abstract void connect(final Handler connectHandler, final Handler connectErrorHandler, final DefaultContext context); private ClientConnection selectConnection(Set available, int connectionCount, int maxPoolSize) { ClientConnection conn = null; if (!available.isEmpty()) { final boolean useOccupiedConnections = connectionCount >= maxPoolSize; final Iterator clientConnectionIterator = available.iterator(); while (clientConnectionIterator.hasNext()) { final ClientConnection c = clientConnectionIterator.next(); if (c.isClosed()) { // remove closed connections from pool clientConnectionIterator.remove(); continue; } // Ideal situation for all cases, a cached but unoccupied connection. if (c.getOutstandingRequestCount() < 1) { conn = c; break; } // prevent a fully occupied from picking more requests since in this case the new incoming requests will probably time out. if (c.isFullyOccupied()) { clientConnectionIterator.remove(); continue; } if (useOccupiedConnections) { // Otherwise, lets try to pick the connection that has the least amount of outstanding requests on it, // even though we don't have any good way to know how long the requests in the front of this one might take // it's still better than the old behavior which seems to glob all the requests into the first connection // in the available list. if (conn == null || conn.getOutstandingRequestCount() > c.getOutstandingRequestCount()) { conn = c; } } } if (conn != null) available.remove(conn); } return conn; // might still be null, which would either create a connection, or put the request in a wait list } private static class Waiter { final Handler handler; final Handler connectionExceptionHandler; final DefaultContext context; private Waiter(Handler handler, Handler connectionExceptionHandler, DefaultContext context) { this.handler = handler; this.connectionExceptionHandler = connectionExceptionHandler; this.context = context; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy