package com.limegroup.gnutella.util;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import com.limegroup.gnutella.io.ConnectObserver;
import com.limegroup.gnutella.io.NBSocket;
import com.limegroup.gnutella.io.SocketFactory;
class LimitedSocketController extends SimpleSocketController {
/**
* The maximum number of concurrent connection attempts.
*/
private final int MAX_CONNECTING_SOCKETS;
/**
* The current number of waiting socket attempts.
*/
private int _socketsConnecting = 0;
/**
* Any non-blocking Requestors waiting on a pending socket.
*/
private final List WAITING_REQUESTS = new LinkedList();
/**
* Constructs a new LimitedSocketController that only allows 'max'
* number of connections concurrently.
* @param max
*/
LimitedSocketController(int max) {
this.MAX_CONNECTING_SOCKETS = max;
}
/**
* Connects to the given InetSocketAddress.
* This will only connect if the number of connecting sockets has not
* exceeded it's limit. If we're above the limit already, then
* the connection attempt will not take place until a prior attempt
* completes (either by success or failure).
* If observer is null, this will block until this connection attempt finishes.
* Otherwise, observer will be notified of success or failure.
*/
protected Socket connectPlain(InetSocketAddress addr, int timeout, ConnectObserver observer) throws IOException {
NBSocket socket = SocketFactory.newSocket();
if(observer == null) {
// BLOCKING.
waitForSocket();
try {
socket.connect(addr, timeout);
} finally {
releaseSocket();
}
} else {
// NON BLOCKING
if(addWaitingSocket(socket, addr, timeout, observer)) {
socket.connect(addr, timeout, new DelegateConnector(observer));
}
}
return socket;
}
/**
* Removes the given observer from connecting. If the attempt has already begun,
* this will return false and the observer will eventually be notified.
* Otherwise, this will return true and the observer will never be notified,
* because the connection will never be attempted.
*/
public synchronized boolean removeConnectObserver(ConnectObserver observer) {
for(Iterator i = WAITING_REQUESTS.iterator(); i.hasNext(); ) {
Requestor next = (Requestor)i.next();
if(next.observer == observer) {
i.remove();
return true;
// must handle proxy'd kinds also.
} else if(next.observer instanceof ProxyUtils.ProxyConnector) {
if(((ProxyUtils.ProxyConnector)next.observer).getDelegateObserver() == observer) {
i.remove();
return true;
}
}
}
return false;
}
/** Returns the maximum number of concurrent attempts this will allow. */
public int getNumAllowedSockets() {
return MAX_CONNECTING_SOCKETS;
}
/**
* Runs through any waiting Requestors and initiates a connection to them.
*/
private void runWaitingRequests() {
// We must connect outside of the lock, so as not to expose being locked to external
// entities.
List toBeProcessed = new ArrayList(Math.min(WAITING_REQUESTS.size(), Math.max(0, MAX_CONNECTING_SOCKETS - _socketsConnecting)));
synchronized(this) {
while(_socketsConnecting < MAX_CONNECTING_SOCKETS && !WAITING_REQUESTS.isEmpty()) {
toBeProcessed.add(WAITING_REQUESTS.remove(0));
_socketsConnecting++;
}
}
for(int i = 0; i < toBeProcessed.size(); i++) {
Requestor next = (Requestor)toBeProcessed.get(i);
next.socket.connect(next.addr, next.timeout, new DelegateConnector(next.observer));
}
}
/**
* Determines if the given requestor can immediately connect.
* If not, adds it to a pool of future connection-wanters.
*/
private synchronized boolean addWaitingSocket(NBSocket socket,
InetSocketAddress addr, int timeout, ConnectObserver observer) {
if (_socketsConnecting >= MAX_CONNECTING_SOCKETS) {
WAITING_REQUESTS.add(new Requestor(socket, addr, timeout, observer));
return false;
} else {
_socketsConnecting++;
return true;
}
}
/**
* Waits until we're allowed to do an active outgoing socket connection.
*/
private synchronized void waitForSocket() throws IOException {
while (_socketsConnecting >= MAX_CONNECTING_SOCKETS) {
try {
wait();
} catch (InterruptedException ix) {
throw new IOException(ix.getMessage());
}
}
_socketsConnecting++;
}
/**
* Notification that a socket has been released.
*
* If there are waiting non-blocking requests, spawns starts a new connection for them.
*/
private void releaseSocket() {
// Release this slot.
synchronized(this) {
_socketsConnecting--;
}
// See if any non-blocking requests are queued.
runWaitingRequests();
// If there's room, notify blocking requests.
synchronized(this) {
if(_socketsConnecting < MAX_CONNECTING_SOCKETS) {
notifyAll();
}
}
}
/** A ConnectObserver to maintain the _socketsConnecting variable. */
private class DelegateConnector implements ConnectObserver {
private final ConnectObserver delegate;
DelegateConnector(ConnectObserver observer) {
delegate = observer;
}
public void handleConnect(Socket s) throws IOException {
releaseSocket();
delegate.handleConnect(s);
}
public void shutdown() {
releaseSocket();
delegate.shutdown();
}
// unused.
public void handleIOException(IOException x) {}
}
/** Simple struct to hold data for non-blocking waiting requests. */
private static class Requestor {
private final InetSocketAddress addr;
private final int timeout;
private final NBSocket socket;
private final ConnectObserver observer;
Requestor(NBSocket socket, InetSocketAddress addr, int timeout, ConnectObserver observer) {
this.socket = socket;
this.addr = addr;
this.timeout = timeout;
this.observer = observer;
}
}
}