/* * Created on Apr 22, 2005 * Created by Alon Rohter * Copyright (C) 2005, 2006 Aelitis, All Rights Reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * AELITIS, SAS au capital de 46,603.30 euros * 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France. * */ package com.aelitis.azureus.core.networkmanager.impl; import java.io.IOException; import java.util.*; import org.gudy.azureus2.core3.util.AEDiagnostics; import org.gudy.azureus2.core3.util.AEMonitor; import org.gudy.azureus2.core3.util.Debug; import org.gudy.azureus2.core3.util.SystemTime; import com.aelitis.azureus.core.networkmanager.*; /** * */ public class MultiPeerDownloader2 implements RateControlledEntity { private static final int MOVE_TO_IDLE_TIME = 500; private static final Object ADD_ACTION = new Object(); private static final Object REMOVE_ACTION = new Object(); private volatile ArrayList connections_cow = new ArrayList(); //copied-on-write private final AEMonitor connections_mon = new AEMonitor( "MultiPeerDownloader" ); private final RateHandler main_handler; private List pending_actions; private connectionList active_connections = new connectionList(); private connectionList idle_connections = new connectionList(); private long last_idle_check; private volatile EventWaiter waiter; /** * Create new downloader using the given "global" rate handler to limit all peers managed by this downloader. * @param main_handler */ public MultiPeerDownloader2( RateHandler _main_handler ) { main_handler = _main_handler; } public RateHandler getRateHandler() { return( main_handler ); } /** * Add the given connection to the downloader. * @param connection to add */ public void addPeerConnection( NetworkConnectionBase connection ) { EventWaiter waiter_to_kick = null; try{ connections_mon.enter(); //copy-on-write int cow_size = connections_cow.size(); if ( cow_size == 0 ){ waiter_to_kick = waiter; if ( waiter_to_kick != null ){ waiter = null; } } ArrayList conn_new = new ArrayList( cow_size + 1 ); conn_new.addAll( connections_cow ); conn_new.add( connection ); connections_cow = conn_new; if ( pending_actions == null ){ pending_actions = new ArrayList(); } pending_actions.add( new Object[]{ ADD_ACTION, connection }); }finally{ connections_mon.exit(); } if ( waiter_to_kick != null ){ waiter_to_kick.eventOccurred(); } } /** * Remove the given connection from the downloader. * @param connection to remove * @return true if the connection was found and removed, false if not removed */ public boolean removePeerConnection( NetworkConnectionBase connection ) { try{ connections_mon.enter(); //copy-on-write ArrayList conn_new = new ArrayList( connections_cow ); boolean removed = conn_new.remove( connection ); if( !removed ) return false; connections_cow = conn_new; if ( pending_actions == null ){ pending_actions = new ArrayList(); } pending_actions.add( new Object[]{ REMOVE_ACTION, connection }); return true; }finally{ connections_mon.exit(); } } public boolean canProcess( EventWaiter waiter ) { if( main_handler.getCurrentNumBytesAllowed() < 1/*NetworkManager.getTcpMssSize()*/ ) return false; return true; } public long getBytesReadyToWrite() { return( 0 ); } public int getConnectionCount( EventWaiter _waiter ) { int result = connections_cow.size(); if ( result == 0 ){ waiter = _waiter; } return( result ); } public int getReadyConnectionCount( EventWaiter waiter ) { int res = 0; for (Iterator it=connections_cow.iterator();it.hasNext();){ NetworkConnectionBase connection = (NetworkConnectionBase)it.next(); if ( connection.getTransportBase().isReadyForRead( waiter ) == 0 ){ res++; } } return( res ); } public int doProcessing( EventWaiter waiter, int max_bytes ) { // Note - this is single threaded // System.out.println( "MPD: do process - " + connections_cow.size() + "/" + active_connections.size() + "/" + idle_connections.size()); int num_bytes_allowed = main_handler.getCurrentNumBytesAllowed(); if ( num_bytes_allowed < 1 ){ return 0; } if ( max_bytes > 0 && max_bytes < num_bytes_allowed ){ num_bytes_allowed = max_bytes; } if ( pending_actions != null ){ try{ connections_mon.enter(); for (int i=0;i<pending_actions.size();i++){ Object[] entry = (Object[])pending_actions.get(i); NetworkConnectionBase connection = (NetworkConnectionBase)entry[1]; if ( entry[0] == ADD_ACTION ){ active_connections.add( connection ); }else{ active_connections.remove( connection ); idle_connections.remove( connection ); } } pending_actions = null; }finally{ connections_mon.exit(); } } long now = SystemTime.getSteppedMonotonousTime(); if ( now - last_idle_check > MOVE_TO_IDLE_TIME ){ last_idle_check = now; // move any active ones off of the idle queue connectionEntry entry = idle_connections.head(); while( entry != null ){ NetworkConnectionBase connection = entry.connection; connectionEntry next = entry.next; if ( connection.getTransportBase().isReadyForRead( waiter ) == 0 ){ // System.out.println( " moving to active " + connection.getString()); idle_connections.remove( entry ); active_connections.addToStart( entry ); } entry = next; } } // process the active set int num_bytes_remaining = num_bytes_allowed; connectionEntry entry = active_connections.head(); int num_entries = active_connections.size(); for (int i=0; i<num_entries && entry != null && num_bytes_remaining > 0;i++ ){ NetworkConnectionBase connection = entry.connection; connectionEntry next = entry.next; long ready = connection.getTransportBase().isReadyForRead( waiter ); // System.out.println( " " + connection.getString() + " - " + ready ); if ( ready == 0 ){ int mss = connection.getMssSize(); int allowed = num_bytes_remaining > mss ? mss : num_bytes_remaining; int bytes_read = 0; try{ bytes_read = connection.getIncomingMessageQueue().receiveFromTransport( allowed ); }catch( Throwable e ) { if( AEDiagnostics.TRACE_CONNECTION_DROPS ) { if( e.getMessage() == null ) { Debug.out( "null read exception message: ", e ); } else { if( e.getMessage().indexOf( "end of stream on socket read" ) == -1 && e.getMessage().indexOf( "An existing connection was forcibly closed by the remote host" ) == -1 && e.getMessage().indexOf( "Connection reset by peer" ) == -1 && e.getMessage().indexOf( "An established connection was aborted by the software in your host machine" ) == -1 ) { System.out.println( "MP: read exception [" +connection.getTransportBase().getDescription()+ "]: " +e.getMessage() ); } } } if (! (e instanceof IOException )){ Debug.printStackTrace(e); } connection.notifyOfException( e ); } num_bytes_remaining -= bytes_read; // System.out.println( " moving to end " + connection.getString()); active_connections.moveToEnd( entry ); }else if ( ready > MOVE_TO_IDLE_TIME ){ // System.out.println( " moving to idle " + connection.getString()); active_connections.remove( entry ); idle_connections.addToEnd( entry ); } entry = next; } int total_bytes_read = num_bytes_allowed - num_bytes_remaining; if( total_bytes_read > 0 ){ main_handler.bytesProcessed( total_bytes_read ); return total_bytes_read; } return 0; //zero bytes read } public int getPriority() { return RateControlledEntity.PRIORITY_HIGH; } public boolean getPriorityBoost(){ return false; } public String getString() { StringBuffer str = new StringBuffer(); str.append( "MPD (" + connections_cow.size() + "/" + active_connections.size() + "/" + idle_connections.size() + ": " ); int num = 0; for (Iterator it=connections_cow.iterator();it.hasNext();){ NetworkConnectionBase connection = (NetworkConnectionBase)it.next(); if ( num++ > 0 ){ str.append( "," ); } str.append( connection.getString()); } return( str.toString()); } protected static class connectionList { private int size; private connectionEntry head; private connectionEntry tail; protected connectionEntry add( NetworkConnectionBase connection ) { connectionEntry entry = new connectionEntry( connection ); if ( head == null ){ head = tail = entry; }else{ tail.next = entry; entry.prev = tail; tail = entry; } size++; return( entry ); } protected void addToEnd( connectionEntry entry ) { entry.next = null; entry.prev = tail; if ( tail == null ){ head = tail = entry; }else{ tail.next = entry; tail = entry; } size++; } protected void addToStart( connectionEntry entry ) { entry.next = head; entry.prev = null; if ( head == null ){ head = tail = entry; }else{ head.prev = entry; head = entry; } size++; } protected void moveToEnd( connectionEntry entry ) { if ( entry != tail ){ connectionEntry prev = entry.prev; connectionEntry next = entry.next; if ( prev == null ){ head = next; }else{ prev.next = next; } next.prev = prev; entry.prev = tail; entry.next = null; tail.next = entry; tail = entry; } } protected connectionEntry remove( NetworkConnectionBase connection ) { connectionEntry entry = head; while( entry != null ){ if ( entry.connection == connection ){ remove( entry ); return( entry ); }else{ entry = entry.next; } } return( null ); } protected void remove( connectionEntry entry ) { connectionEntry prev = entry.prev; connectionEntry next = entry.next; if ( prev == null ){ head = next; }else{ prev.next = next; } if ( next == null ){ tail = prev; }else{ next.prev = prev; } size--; } protected int size() { return( size ); } protected connectionEntry head() { return( head ); } } protected static class connectionEntry { private connectionEntry next; private connectionEntry prev; private NetworkConnectionBase connection; protected connectionEntry( NetworkConnectionBase _connection ) { connection = _connection; } } }