/* * 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 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 final HashMap peer_connections = new HashMap(); private final LinkedList discovered_peers = new LinkedList(); private final AEMonitor map_mon = new AEMonitor( "PeerDatabase" ); private PeerItem[] cached_peer_popularities = null; private int popularity_pos = 0; private long last_rebuild_time = 0; private long last_rotation_time = 0; private PeerItem self_peer; private BloomFilter filter_one = null; private BloomFilter filter_two = BloomFilterFactory.createAddOnly( BLOOM_FILTER_SIZE ); 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.addLast( peer ); //add unknown peer int max_cache_size = PeerUtils.MAX_CONNECTIONS_PER_TORRENT; if( max_cache_size < 1 || max_cache_size > MAX_DISCOVERED_PEERS ) max_cache_size = MAX_DISCOVERED_PEERS; if( discovered_peers.size() > max_cache_size ) { discovered_peers.removeFirst(); } } } 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 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( ) { return(getNextOptimisticConnectPeer(0)); } protected PeerItem getNextOptimisticConnectPeer( int recursion_count ) { PeerItem peer = null; boolean discovered_peer = false; //first see if there are any unknown peers to try try{ map_mon.enter(); if( !discovered_peers.isEmpty() ) { peer = (PeerItem)discovered_peers.removeFirst(); discovered_peer = true; } } finally{ map_mon.exit(); } //pick one from those obtained via peer exchange if needed if( peer == null ) { if( cached_peer_popularities == null || popularity_pos == cached_peer_popularities.length ) { //rebuild needed cached_peer_popularities = null; //clear cache long time_since_rebuild = SystemTime.getCurrentTime() - last_rebuild_time; //only allow exchange list rebuild every few min, otherwise we'll spam attempts endlessly if( time_since_rebuild > MIN_REBUILD_WAIT_TIME || time_since_rebuild < 0 ) { cached_peer_popularities = getExchangedPeersSortedByLeastPopularFirst(); popularity_pos = 0; last_rebuild_time = SystemTime.getCurrentTime(); } } if( cached_peer_popularities != null && cached_peer_popularities.length > 0 ) { peer = cached_peer_popularities[ popularity_pos ]; popularity_pos++; last_rebuild_time = SystemTime.getCurrentTime(); //ensure rebuild waits min rebuild time after the cache is depleted before trying attempts again } } //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 = SystemTime.getCurrentTime() - last_rotation_time; if( diff < 0 || diff > BLOOM_ROTATION_PERIOD ) { filter_one = filter_two; filter_two = BloomFilterFactory.createAddOnly( BLOOM_FILTER_SIZE ); last_rotation_time = SystemTime.getCurrentTime(); } //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.addLast( 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 ); } } return peer; } 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()); } }