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 }