/* * Created on Apr 26, 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.peermanager.peerdb; import java.util.*; import org.gudy.azureus2.core3.peer.util.PeerUtils; import org.gudy.azureus2.core3.util.*; import com.aelitis.azureus.core.util.bloom.*; /** * */ public class PeerDatabase { private static final int STARTUP_MIN_REBUILD_WAIT_TIME = 10*1000; private static final int STARTUP_MILLIS = 120*1000; private static final int MIN_REBUILD_WAIT_TIME = 60*1000; private static final int MAX_DISCOVERED_PEERS = 500; private static final int BLOOM_ROTATION_PERIOD = 7*60*1000; private static final int BLOOM_FILTER_SIZE = 10000; private long start_time = SystemTime.getMonotonousTime(); private final HashMap peer_connections = new HashMap(); private final TreeSet<PeerItem> discovered_peers = new TreeSet<PeerItem>( new Comparator<PeerItem>() { public int compare( PeerItem o1, PeerItem o2 ) { long res = o2.getPriority() - o1.getPriority(); if ( res == 0 ){ res = o1.compareTo( o2 ); } return( res<0?-1:(res>0?1:0 )); } }); private final AEMonitor map_mon = new AEMonitor( "PeerDatabase" ); private PeerItem[] cached_peer_popularities = null; private int popularity_pos = 0; private long last_rebuild_time = Integer.MIN_VALUE; private long last_rotation_time = Integer.MIN_VALUE; private PeerItem self_peer; private BloomFilter filter_one = null; private BloomFilter filter_two = BloomFilterFactory.createAddOnly( BLOOM_FILTER_SIZE ); private long pex_count_last_time; private int pex_count_last; private int pex_used_count; private int total_peers_returned; protected PeerDatabase() { /* nothing */ } /** * Register a new peer connection with the database. * @param base_peer_item key * @return registered connection */ public PeerExchangerItem registerPeerConnection( PeerItem base_peer_item, PeerExchangerItem.Helper helper ) { try{ map_mon.enter(); PeerExchangerItem new_connection = new PeerExchangerItem( this, base_peer_item, helper ); //update connection adds for( Iterator it = peer_connections.entrySet().iterator(); it.hasNext(); ) { //go through all existing connections Map.Entry entry = (Map.Entry)it.next(); PeerItem old_key = (PeerItem)entry.getKey(); PeerExchangerItem old_connection = (PeerExchangerItem)entry.getValue(); if( old_connection.getHelper().isSeed() && new_connection.getHelper().isSeed() ) { continue; //dont exchange seed peers to other seeds } old_connection.notifyAdded( base_peer_item ); //notify existing connection of new one new_connection.notifyAdded( old_key ); //notify new connection of existing one for initial exchange } peer_connections.put( base_peer_item, new_connection ); return new_connection; } finally{ map_mon.exit(); } } protected void deregisterPeerConnection( PeerItem base_peer_key ) { try{ map_mon.enter(); peer_connections.remove( base_peer_key ); //update connection drops for( Iterator it = peer_connections.values().iterator(); it.hasNext(); ) { //go through all remaining connections PeerExchangerItem old_connection = (PeerExchangerItem)it.next(); //dont skip seed2seed drop notification, as the dropped peer may not have been seeding initially old_connection.notifyDropped( base_peer_key ); //notify existing connection of drop } } finally{ map_mon.exit(); } } public void seedStatusChanged( PeerExchangerItem item ) { // only bother with non-seed -> seed transitions, the opposite are too rate to bother with if ( item.getHelper().isSeed()){ try{ map_mon.enter(); for ( Iterator it = peer_connections.values().iterator(); it.hasNext(); ){ PeerExchangerItem connection = (PeerExchangerItem)it.next(); if ( connection != item && connection.getHelper().isSeed()){ // System.out.println( "seedStatusChanged: dropping: originator= " + item.getBasePeer().getAddressString() + ",target=" + connection.getBasePeer().getAddressString()); connection.notifyDropped( item.getBasePeer() ); item.notifyDropped( connection.getBasePeer() ); } } }finally{ map_mon.exit(); } } } /** * Add a potential peer obtained via tracker announce, DHT announce, plugin, etc. * @param peer to add */ public void addDiscoveredPeer( PeerItem peer ) { try{ map_mon.enter(); for( Iterator it = peer_connections.values().iterator(); it.hasNext(); ) { //check to make sure we dont already know about this peer PeerExchangerItem connection = (PeerExchangerItem)it.next(); if( connection.isConnectedToPeer( peer ) ) return; //we already know about this peer via exchange, so ignore discovery } if( !discovered_peers.contains( peer ) ) { discovered_peers.add( peer ); //add unknown peer int max_cache_size = PeerUtils.MAX_CONNECTIONS_PER_TORRENT * 2; // cache twice the amount to allow for failures if( max_cache_size < 1 || max_cache_size > MAX_DISCOVERED_PEERS ) max_cache_size = MAX_DISCOVERED_PEERS; if( discovered_peers.size() > max_cache_size ) { Iterator<PeerItem> it = discovered_peers.iterator(); it.next(); it.remove(); } } } finally{ map_mon.exit(); } } /** * Mark the given peer as ourself. * @param self peer */ public void setSelfPeer( PeerItem self ) { self_peer = self; } /** * Get the peer item that represents ourself. * @return self peer, or null if unknown */ public PeerItem getSelfPeer() { /* //disabled for now, as getExternalIpAddress() will potential run a full version check every 60s if( self_peer == null ) { //determine our 'self' info from config String ip = VersionCheckClient.getSingleton().getExternalIpAddress(); if( ip != null && ip.length() > 0 ) { self_peer = PeerItemFactory.createPeerItem( ip, NetworkManager.getSingleton().getTCPListeningPortNumber(), 0 ); } } */ return self_peer; } public PeerItem[] getDiscoveredPeers() { try{ map_mon.enter(); return((PeerItem[])discovered_peers.toArray( new PeerItem[discovered_peers.size()] )); }finally{ map_mon.exit(); } } public PeerItem[] getDiscoveredPeers( String address ) { List<PeerItem> result = null; try{ map_mon.enter(); Iterator<PeerItem> it = discovered_peers.iterator(); while( it.hasNext()){ PeerItem peer = it.next(); if( peer.getIP().equals( address )){ if ( result == null ){ result = new ArrayList<PeerItem>(); } result.add( peer ); } } }finally{ map_mon.exit(); } if ( result == null ){ return( new PeerItem[0]); }else{ return( result.toArray( new PeerItem[result.size()] )); } } public int getDiscoveredPeerCount() { try{ map_mon.enter(); return( discovered_peers.size()); }finally{ map_mon.exit(); } } /** * Get the next potential peer for optimistic connect. * @return peer to connect, or null of no optimistic peer available */ public PeerItem getNextOptimisticConnectPeer( ) { PeerItem item = getNextOptimisticConnectPeer(0); /* if ( item != null ){ System.out.println( "pri: " + item.getPriority()); } */ return( item ); } private PeerItem getNextOptimisticConnectPeer( final int recursion_count ) { long now = SystemTime.getMonotonousTime(); boolean starting_up = now - start_time <= STARTUP_MILLIS; PeerItem peer = null; boolean discovered_peer = false; boolean tried_pex = false; if ( starting_up && total_peers_returned % 5 == 0 ){ // inject a few PEX peers during startup as we know they're live and can help bootstrap the torrent peer = getPeerFromPEX( now, starting_up ); tried_pex = true; } if ( peer == null ){ //first see if there are any unknown peers to try try{ map_mon.enter(); if( !discovered_peers.isEmpty() ) { Iterator<PeerItem> it = discovered_peers.iterator(); peer = it.next(); it.remove(); discovered_peer = true; } }finally{ map_mon.exit(); } } //pick one from those obtained via peer exchange if needed if ( peer == null && !tried_pex ) { peer = getPeerFromPEX( now, starting_up ); } //to reduce the number of wasted outgoing attempts, we limit how frequently we hand out the same optimistic peer in a given time period if( peer != null ) { //check if it's time to rotate the bloom filters long diff = now - last_rotation_time; if ( diff > BLOOM_ROTATION_PERIOD ) { filter_one = filter_two; filter_two = BloomFilterFactory.createAddOnly( BLOOM_FILTER_SIZE ); last_rotation_time = now; } //check to see if we've already given this peer out optimistically in the last 5-10min boolean already_recorded = false; byte[] peer_serialisation = peer.getSerialization(); if ( filter_one.contains( peer_serialisation ) && recursion_count < 100 ) { // we've recently given this peer, so recursively find another peer to try PeerItem next_peer = getNextOptimisticConnectPeer( recursion_count + 1); if ( next_peer != null ){ // we've found a better peer that this one. If the existing peer was discovered (as opposed to PEX) // then we'll save it for later as it might come in useful if ( discovered_peer ){ try{ map_mon.enter(); discovered_peers.add( peer ); }finally{ map_mon.exit(); } } peer = next_peer; already_recorded = true; // this peer's already in the bloom filters as it has been returned // by a recursive call } } if( !already_recorded ) { //we've found a suitable peer filter_one.add( peer_serialisation ); filter_two.add( peer_serialisation ); } } if ( recursion_count == 0 && peer != null ){ total_peers_returned++; } return peer; } private PeerItem getPeerFromPEX( long now, boolean starting_up ) { PeerItem peer; if( cached_peer_popularities == null || popularity_pos == cached_peer_popularities.length ) { //rebuild needed cached_peer_popularities = null; //clear cache long time_since_rebuild = now - last_rebuild_time; //only allow exchange list rebuild every few min, otherwise we'll spam attempts endlessly if( time_since_rebuild > (starting_up?STARTUP_MIN_REBUILD_WAIT_TIME:MIN_REBUILD_WAIT_TIME )) { cached_peer_popularities = getExchangedPeersSortedByLeastPopularFirst(); popularity_pos = 0; last_rebuild_time = now; } } if ( cached_peer_popularities != null && cached_peer_popularities.length > 0 ) { peer = cached_peer_popularities[ popularity_pos ]; popularity_pos++; pex_used_count++; last_rebuild_time = now; //ensure rebuild waits min rebuild time after the cache is depleted before trying attempts again }else{ peer = null; } return( peer ); } public int getExchangedPeerCount() { long now = SystemTime.getMonotonousTime(); if ( now - pex_count_last_time >= 10*1000 ){ PeerItem[] peers = getExchangedPeersSortedByLeastPopularFirst(); pex_count_last = peers==null?0:peers.length; pex_count_last_time = now; } return( Math.max( 0, pex_count_last - popularity_pos )); } public int getExchangedPeersUsed() { return( pex_used_count ); } private PeerItem[] getExchangedPeersSortedByLeastPopularFirst() { HashMap popularity_counts = new HashMap(); try{ map_mon.enter(); //count popularity of all known peers for( Iterator it = peer_connections.values().iterator(); it.hasNext(); ) { PeerExchangerItem connection = (PeerExchangerItem)it.next(); PeerItem[] peers = connection.getConnectedPeers(); for( int i=0; i < peers.length; i++ ) { PeerItem peer = peers[i]; Integer count = (Integer)popularity_counts.get( peer ); if( count == null ) { count = new Integer( 1 ); } else { count = new Integer( count.intValue() + 1 ); } popularity_counts.put( peer, count ); } } } finally{ map_mon.exit(); } if( popularity_counts.isEmpty() ) return null; //now sort by popularity Map.Entry[] sorted_entries = new Map.Entry[ popularity_counts.size() ]; popularity_counts.entrySet().toArray( sorted_entries ); Arrays.sort( sorted_entries, new Comparator() { public int compare( Object obj1, Object obj2 ) { Map.Entry en1 = (Map.Entry)obj1; Map.Entry en2 = (Map.Entry)obj2; return ((Integer)en1.getValue()).compareTo( (Integer)en2.getValue() ); //we want least popular in front } }); PeerItem[] sorted_peers = new PeerItem[ sorted_entries.length ]; for( int i=0; i < sorted_entries.length; i++ ) { Map.Entry entry = sorted_entries[i]; sorted_peers[i] = (PeerItem)entry.getKey(); } return sorted_peers; } //TODO destroy() method? public String getString() { return("pc=" + peer_connections.size() + ",dp=" + discovered_peers.size()); } }