github.com/keltia/go-ipfs@v0.3.8-0.20150909044612-210793031c63/routing/kbucket/table.go (about)

     1  // package kbucket implements a kademlia 'k-bucket' routing table.
     2  package kbucket
     3  
     4  import (
     5  	"fmt"
     6  	"sort"
     7  	"sync"
     8  	"time"
     9  
    10  	peer "github.com/ipfs/go-ipfs/p2p/peer"
    11  	u "github.com/ipfs/go-ipfs/util"
    12  )
    13  
    14  var log = u.Logger("table")
    15  
    16  // RoutingTable defines the routing table.
    17  type RoutingTable struct {
    18  
    19  	// ID of the local peer
    20  	local ID
    21  
    22  	// Blanket lock, refine later for better performance
    23  	tabLock sync.RWMutex
    24  
    25  	// latency metrics
    26  	metrics peer.Metrics
    27  
    28  	// Maximum acceptable latency for peers in this cluster
    29  	maxLatency time.Duration
    30  
    31  	// kBuckets define all the fingers to other nodes.
    32  	Buckets    []*Bucket
    33  	bucketsize int
    34  }
    35  
    36  // NewRoutingTable creates a new routing table with a given bucketsize, local ID, and latency tolerance.
    37  func NewRoutingTable(bucketsize int, localID ID, latency time.Duration, m peer.Metrics) *RoutingTable {
    38  	rt := new(RoutingTable)
    39  	rt.Buckets = []*Bucket{newBucket()}
    40  	rt.bucketsize = bucketsize
    41  	rt.local = localID
    42  	rt.maxLatency = latency
    43  	rt.metrics = m
    44  	return rt
    45  }
    46  
    47  // Update adds or moves the given peer to the front of its respective bucket
    48  // If a peer gets removed from a bucket, it is returned
    49  func (rt *RoutingTable) Update(p peer.ID) {
    50  	rt.tabLock.Lock()
    51  	defer rt.tabLock.Unlock()
    52  	peerID := ConvertPeerID(p)
    53  	cpl := commonPrefixLen(peerID, rt.local)
    54  
    55  	bucketID := cpl
    56  	if bucketID >= len(rt.Buckets) {
    57  		bucketID = len(rt.Buckets) - 1
    58  	}
    59  
    60  	bucket := rt.Buckets[bucketID]
    61  	if bucket.Has(p) {
    62  		// If the peer is already in the table, move it to the front.
    63  		// This signifies that it it "more active" and the less active nodes
    64  		// Will as a result tend towards the back of the list
    65  		bucket.MoveToFront(p)
    66  		return
    67  	}
    68  
    69  	if rt.metrics.LatencyEWMA(p) > rt.maxLatency {
    70  		// Connection doesnt meet requirements, skip!
    71  		return
    72  	}
    73  
    74  	// New peer, add to bucket
    75  	bucket.PushFront(p)
    76  
    77  	// Are we past the max bucket size?
    78  	if bucket.Len() > rt.bucketsize {
    79  		// If this bucket is the rightmost bucket, and its full
    80  		// we need to split it and create a new bucket
    81  		if bucketID == len(rt.Buckets)-1 {
    82  			rt.nextBucket()
    83  			return
    84  		} else {
    85  			// If the bucket cant split kick out least active node
    86  			bucket.PopBack()
    87  			return
    88  		}
    89  	}
    90  }
    91  
    92  // Remove deletes a peer from the routing table. This is to be used
    93  // when we are sure a node has disconnected completely.
    94  func (rt *RoutingTable) Remove(p peer.ID) {
    95  	rt.tabLock.Lock()
    96  	defer rt.tabLock.Unlock()
    97  	peerID := ConvertPeerID(p)
    98  	cpl := commonPrefixLen(peerID, rt.local)
    99  
   100  	bucketID := cpl
   101  	if bucketID >= len(rt.Buckets) {
   102  		bucketID = len(rt.Buckets) - 1
   103  	}
   104  
   105  	bucket := rt.Buckets[bucketID]
   106  	bucket.Remove(p)
   107  }
   108  
   109  func (rt *RoutingTable) nextBucket() peer.ID {
   110  	bucket := rt.Buckets[len(rt.Buckets)-1]
   111  	newBucket := bucket.Split(len(rt.Buckets)-1, rt.local)
   112  	rt.Buckets = append(rt.Buckets, newBucket)
   113  	if newBucket.Len() > rt.bucketsize {
   114  		return rt.nextBucket()
   115  	}
   116  
   117  	// If all elements were on left side of split...
   118  	if bucket.Len() > rt.bucketsize {
   119  		return bucket.PopBack()
   120  	}
   121  	return ""
   122  }
   123  
   124  // Find a specific peer by ID or return nil
   125  func (rt *RoutingTable) Find(id peer.ID) peer.ID {
   126  	srch := rt.NearestPeers(ConvertPeerID(id), 1)
   127  	if len(srch) == 0 || srch[0] != id {
   128  		return ""
   129  	}
   130  	return srch[0]
   131  }
   132  
   133  // NearestPeer returns a single peer that is nearest to the given ID
   134  func (rt *RoutingTable) NearestPeer(id ID) peer.ID {
   135  	peers := rt.NearestPeers(id, 1)
   136  	if len(peers) > 0 {
   137  		return peers[0]
   138  	}
   139  
   140  	log.Debugf("NearestPeer: Returning nil, table size = %d", rt.Size())
   141  	return ""
   142  }
   143  
   144  // NearestPeers returns a list of the 'count' closest peers to the given ID
   145  func (rt *RoutingTable) NearestPeers(id ID, count int) []peer.ID {
   146  	rt.tabLock.RLock()
   147  	defer rt.tabLock.RUnlock()
   148  	cpl := commonPrefixLen(id, rt.local)
   149  
   150  	// Get bucket at cpl index or last bucket
   151  	var bucket *Bucket
   152  	if cpl >= len(rt.Buckets) {
   153  		cpl = len(rt.Buckets) - 1
   154  	}
   155  	bucket = rt.Buckets[cpl]
   156  
   157  	var peerArr peerSorterArr
   158  	if bucket.Len() == 0 {
   159  		// In the case of an unusual split, one bucket may be empty.
   160  		// if this happens, search both surrounding buckets for nearest peer
   161  		if cpl > 0 {
   162  			plist := rt.Buckets[cpl-1].list
   163  			peerArr = copyPeersFromList(id, peerArr, plist)
   164  		}
   165  
   166  		if cpl < len(rt.Buckets)-1 {
   167  			plist := rt.Buckets[cpl+1].list
   168  			peerArr = copyPeersFromList(id, peerArr, plist)
   169  		}
   170  	} else {
   171  		peerArr = copyPeersFromList(id, peerArr, bucket.list)
   172  	}
   173  
   174  	// Sort by distance to local peer
   175  	sort.Sort(peerArr)
   176  
   177  	var out []peer.ID
   178  	for i := 0; i < count && i < peerArr.Len(); i++ {
   179  		out = append(out, peerArr[i].p)
   180  	}
   181  
   182  	return out
   183  }
   184  
   185  // Size returns the total number of peers in the routing table
   186  func (rt *RoutingTable) Size() int {
   187  	var tot int
   188  	rt.tabLock.RLock()
   189  	for _, buck := range rt.Buckets {
   190  		tot += buck.Len()
   191  	}
   192  	rt.tabLock.RUnlock()
   193  	return tot
   194  }
   195  
   196  // ListPeers takes a RoutingTable and returns a list of all peers from all buckets in the table.
   197  // NOTE: This is potentially unsafe... use at your own risk
   198  func (rt *RoutingTable) ListPeers() []peer.ID {
   199  	var peers []peer.ID
   200  	rt.tabLock.RLock()
   201  	for _, buck := range rt.Buckets {
   202  		peers = append(peers, buck.Peers()...)
   203  	}
   204  	rt.tabLock.RUnlock()
   205  	return peers
   206  }
   207  
   208  // Print prints a descriptive statement about the provided RoutingTable
   209  func (rt *RoutingTable) Print() {
   210  	fmt.Printf("Routing Table, bs = %d, Max latency = %d\n", rt.bucketsize, rt.maxLatency)
   211  	rt.tabLock.RLock()
   212  
   213  	for i, b := range rt.Buckets {
   214  		fmt.Printf("\tbucket: %d\n", i)
   215  
   216  		b.lk.RLock()
   217  		for e := b.list.Front(); e != nil; e = e.Next() {
   218  			p := e.Value.(peer.ID)
   219  			fmt.Printf("\t\t- %s %s\n", p.Pretty(), rt.metrics.LatencyEWMA(p).String())
   220  		}
   221  		b.lk.RUnlock()
   222  	}
   223  	rt.tabLock.RUnlock()
   224  }