|
|
|
@ -78,9 +78,8 @@ type transport interface { |
|
|
|
|
close() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// bucket contains nodes, ordered by their last activity.
|
|
|
|
|
// the entry that was most recently active is the last element
|
|
|
|
|
// in entries.
|
|
|
|
|
// bucket contains nodes, ordered by their last activity. the entry
|
|
|
|
|
// that was most recently active is the first element in entries.
|
|
|
|
|
type bucket struct { |
|
|
|
|
lastLookup time.Time |
|
|
|
|
entries []*Node |
|
|
|
@ -235,7 +234,7 @@ func (tab *Table) Lookup(targetID NodeID) []*Node { |
|
|
|
|
|
|
|
|
|
if fails >= maxFindnodeFailures { |
|
|
|
|
glog.V(logger.Detail).Infof("Evacuating node %x: %d findnode failures", n.ID[:8], fails) |
|
|
|
|
tab.del(n) |
|
|
|
|
tab.delete(n) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
reply <- tab.bondall(r) |
|
|
|
@ -401,15 +400,11 @@ func (tab *Table) bond(pinged bool, id NodeID, addr *net.UDPAddr, tcpPort uint16 |
|
|
|
|
node = w.n |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// Even if bonding temporarily failed, give the node a chance
|
|
|
|
|
if node != nil { |
|
|
|
|
tab.mutex.Lock() |
|
|
|
|
defer tab.mutex.Unlock() |
|
|
|
|
|
|
|
|
|
b := tab.buckets[logdist(tab.self.sha, node.sha)] |
|
|
|
|
if !b.bump(node) { |
|
|
|
|
tab.pingreplace(node, b) |
|
|
|
|
} |
|
|
|
|
// Add the node to the table even if the bonding ping/pong
|
|
|
|
|
// fails. It will be relaced quickly if it continues to be
|
|
|
|
|
// unresponsive.
|
|
|
|
|
tab.add(node) |
|
|
|
|
tab.db.updateFindFails(id, 0) |
|
|
|
|
} |
|
|
|
|
return node, result |
|
|
|
@ -420,7 +415,7 @@ func (tab *Table) pingpong(w *bondproc, pinged bool, id NodeID, addr *net.UDPAdd |
|
|
|
|
<-tab.bondslots |
|
|
|
|
defer func() { tab.bondslots <- struct{}{} }() |
|
|
|
|
|
|
|
|
|
// Ping the remote side and wait for a pong
|
|
|
|
|
// Ping the remote side and wait for a pong.
|
|
|
|
|
if w.err = tab.ping(id, addr); w.err != nil { |
|
|
|
|
close(w.done) |
|
|
|
|
return |
|
|
|
@ -431,33 +426,14 @@ func (tab *Table) pingpong(w *bondproc, pinged bool, id NodeID, addr *net.UDPAdd |
|
|
|
|
// waitping will simply time out.
|
|
|
|
|
tab.net.waitping(id) |
|
|
|
|
} |
|
|
|
|
// Bonding succeeded, update the node database
|
|
|
|
|
// Bonding succeeded, update the node database.
|
|
|
|
|
w.n = newNode(id, addr.IP, uint16(addr.Port), tcpPort) |
|
|
|
|
tab.db.updateNode(w.n) |
|
|
|
|
close(w.done) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (tab *Table) pingreplace(new *Node, b *bucket) { |
|
|
|
|
if len(b.entries) == bucketSize { |
|
|
|
|
oldest := b.entries[bucketSize-1] |
|
|
|
|
if err := tab.ping(oldest.ID, oldest.addr()); err == nil { |
|
|
|
|
// The node responded, we don't need to replace it.
|
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
// Add a slot at the end so the last entry doesn't
|
|
|
|
|
// fall off when adding the new node.
|
|
|
|
|
b.entries = append(b.entries, nil) |
|
|
|
|
} |
|
|
|
|
copy(b.entries[1:], b.entries) |
|
|
|
|
b.entries[0] = new |
|
|
|
|
if tab.nodeAddedHook != nil { |
|
|
|
|
tab.nodeAddedHook(new) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// ping a remote endpoint and wait for a reply, also updating the node database
|
|
|
|
|
// accordingly.
|
|
|
|
|
// ping a remote endpoint and wait for a reply, also updating the node
|
|
|
|
|
// database accordingly.
|
|
|
|
|
func (tab *Table) ping(id NodeID, addr *net.UDPAddr) error { |
|
|
|
|
// Update the last ping and send the message
|
|
|
|
|
tab.db.updateLastPing(id, time.Now()) |
|
|
|
@ -467,24 +443,53 @@ func (tab *Table) ping(id NodeID, addr *net.UDPAddr) error { |
|
|
|
|
// Pong received, update the database and return
|
|
|
|
|
tab.db.updateLastPong(id, time.Now()) |
|
|
|
|
tab.db.ensureExpirer() |
|
|
|
|
|
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// add puts the entries into the table if their corresponding
|
|
|
|
|
// bucket is not full. The caller must hold tab.mutex.
|
|
|
|
|
func (tab *Table) add(entries []*Node) { |
|
|
|
|
// add attempts to add the given node its corresponding bucket. If the
|
|
|
|
|
// bucket has space available, adding the node succeeds immediately.
|
|
|
|
|
// Otherwise, the node is added if the least recently active node in
|
|
|
|
|
// the bucket does not respond to a ping packet.
|
|
|
|
|
//
|
|
|
|
|
// The caller must not hold tab.mutex.
|
|
|
|
|
func (tab *Table) add(new *Node) { |
|
|
|
|
b := tab.buckets[logdist(tab.self.sha, new.sha)] |
|
|
|
|
tab.mutex.Lock() |
|
|
|
|
if b.bump(new) { |
|
|
|
|
tab.mutex.Unlock() |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
var oldest *Node |
|
|
|
|
if len(b.entries) == bucketSize { |
|
|
|
|
oldest = b.entries[bucketSize-1] |
|
|
|
|
// Let go of the mutex so other goroutines can access
|
|
|
|
|
// the table while we ping the least recently active node.
|
|
|
|
|
tab.mutex.Unlock() |
|
|
|
|
if err := tab.ping(oldest.ID, oldest.addr()); err == nil { |
|
|
|
|
// The node responded, don't replace it.
|
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
tab.mutex.Lock() |
|
|
|
|
} |
|
|
|
|
added := b.replace(new, oldest) |
|
|
|
|
tab.mutex.Unlock() |
|
|
|
|
if added && tab.nodeAddedHook != nil { |
|
|
|
|
tab.nodeAddedHook(new) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// stuff adds nodes the table to the end of their corresponding bucket
|
|
|
|
|
// if the bucket is not full. The caller must hold tab.mutex.
|
|
|
|
|
func (tab *Table) stuff(nodes []*Node) { |
|
|
|
|
outer: |
|
|
|
|
for _, n := range entries { |
|
|
|
|
for _, n := range nodes { |
|
|
|
|
if n.ID == tab.self.ID { |
|
|
|
|
// don't add self.
|
|
|
|
|
continue |
|
|
|
|
continue // don't add self
|
|
|
|
|
} |
|
|
|
|
bucket := tab.buckets[logdist(tab.self.sha, n.sha)] |
|
|
|
|
for i := range bucket.entries { |
|
|
|
|
if bucket.entries[i].ID == n.ID { |
|
|
|
|
// already in bucket
|
|
|
|
|
continue outer |
|
|
|
|
continue outer // already in bucket
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if len(bucket.entries) < bucketSize { |
|
|
|
@ -496,12 +501,11 @@ outer: |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// del removes an entry from the node table (used to evacuate failed/non-bonded
|
|
|
|
|
// discovery peers).
|
|
|
|
|
func (tab *Table) del(node *Node) { |
|
|
|
|
// delete removes an entry from the node table (used to evacuate
|
|
|
|
|
// failed/non-bonded discovery peers).
|
|
|
|
|
func (tab *Table) delete(node *Node) { |
|
|
|
|
tab.mutex.Lock() |
|
|
|
|
defer tab.mutex.Unlock() |
|
|
|
|
|
|
|
|
|
bucket := tab.buckets[logdist(tab.self.sha, node.sha)] |
|
|
|
|
for i := range bucket.entries { |
|
|
|
|
if bucket.entries[i].ID == node.ID { |
|
|
|
@ -511,6 +515,27 @@ func (tab *Table) del(node *Node) { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (b *bucket) replace(n *Node, last *Node) bool { |
|
|
|
|
// Don't add if b already contains n.
|
|
|
|
|
for i := range b.entries { |
|
|
|
|
if b.entries[i].ID == n.ID { |
|
|
|
|
return false |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// Replace last if it is still the last entry or just add n if b
|
|
|
|
|
// isn't full. If is no longer the last entry, it has either been
|
|
|
|
|
// replaced with someone else or became active.
|
|
|
|
|
if len(b.entries) == bucketSize && (last == nil || b.entries[bucketSize-1].ID != last.ID) { |
|
|
|
|
return false |
|
|
|
|
} |
|
|
|
|
if len(b.entries) < bucketSize { |
|
|
|
|
b.entries = append(b.entries, nil) |
|
|
|
|
} |
|
|
|
|
copy(b.entries[1:], b.entries) |
|
|
|
|
b.entries[0] = n |
|
|
|
|
return true |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (b *bucket) bump(n *Node) bool { |
|
|
|
|
for i := range b.entries { |
|
|
|
|
if b.entries[i].ID == n.ID { |
|
|
|
|