@ -78,9 +78,8 @@ type transport interface {
close ( )
close ( )
}
}
// bucket contains nodes, ordered by their last activity.
// bucket contains nodes, ordered by their last activity. the entry
// the entry that was most recently active is the last element
// that was most recently active is the first element in entries.
// in entries.
type bucket struct {
type bucket struct {
lastLookup time . Time
lastLookup time . Time
entries [ ] * Node
entries [ ] * Node
@ -235,7 +234,7 @@ func (tab *Table) Lookup(targetID NodeID) []*Node {
if fails >= maxFindnodeFailures {
if fails >= maxFindnodeFailures {
glog . V ( logger . Detail ) . Infof ( "Evacuating node %x: %d findnode failures" , n . ID [ : 8 ] , fails )
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 )
reply <- tab . bondall ( r )
@ -401,15 +400,11 @@ func (tab *Table) bond(pinged bool, id NodeID, addr *net.UDPAddr, tcpPort uint16
node = w . n
node = w . n
}
}
}
}
// Even if bonding temporarily failed, give the node a chance
if node != nil {
if node != nil {
tab . mutex . Lock ( )
// Add the node to the table even if the bonding ping/pong
defer tab . mutex . Unlock ( )
// fails. It will be relaced quickly if it continues to be
// unresponsive.
b := tab . buckets [ logdist ( tab . self . sha , node . sha ) ]
tab . add ( node )
if ! b . bump ( node ) {
tab . pingreplace ( node , b )
}
tab . db . updateFindFails ( id , 0 )
tab . db . updateFindFails ( id , 0 )
}
}
return node , result
return node , result
@ -420,7 +415,7 @@ func (tab *Table) pingpong(w *bondproc, pinged bool, id NodeID, addr *net.UDPAdd
<- tab . bondslots
<- tab . bondslots
defer func ( ) { tab . bondslots <- struct { } { } } ( )
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 {
if w . err = tab . ping ( id , addr ) ; w . err != nil {
close ( w . done )
close ( w . done )
return
return
@ -431,33 +426,14 @@ func (tab *Table) pingpong(w *bondproc, pinged bool, id NodeID, addr *net.UDPAdd
// waitping will simply time out.
// waitping will simply time out.
tab . net . waitping ( id )
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 )
w . n = newNode ( id , addr . IP , uint16 ( addr . Port ) , tcpPort )
tab . db . updateNode ( w . n )
tab . db . updateNode ( w . n )
close ( w . done )
close ( w . done )
}
}
func ( tab * Table ) pingreplace ( new * Node , b * bucket ) {
// ping a remote endpoint and wait for a reply, also updating the node
if len ( b . entries ) == bucketSize {
// database accordingly.
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.
func ( tab * Table ) ping ( id NodeID , addr * net . UDPAddr ) error {
func ( tab * Table ) ping ( id NodeID , addr * net . UDPAddr ) error {
// Update the last ping and send the message
// Update the last ping and send the message
tab . db . updateLastPing ( id , time . Now ( ) )
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
// Pong received, update the database and return
tab . db . updateLastPong ( id , time . Now ( ) )
tab . db . updateLastPong ( id , time . Now ( ) )
tab . db . ensureExpirer ( )
tab . db . ensureExpirer ( )
return nil
return nil
}
}
// add puts the entries into the table if their corresponding
// add attempts to add the given node its corresponding bucket. If the
// bucket is not full. The caller must hold tab.mutex.
// bucket has space available, adding the node succeeds immediately.
func ( tab * Table ) add ( entries [ ] * Node ) {
// 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 :
outer :
for _ , n := range entries {
for _ , n := range nod es {
if n . ID == tab . self . ID {
if n . ID == tab . self . ID {
// don't add self.
continue // don't add self
continue
}
}
bucket := tab . buckets [ logdist ( tab . self . sha , n . sha ) ]
bucket := tab . buckets [ logdist ( tab . self . sha , n . sha ) ]
for i := range bucket . entries {
for i := range bucket . entries {
if bucket . entries [ i ] . ID == n . ID {
if bucket . entries [ i ] . ID == n . ID {
// already in bucket
continue outer // already in bucket
continue outer
}
}
}
}
if len ( bucket . entries ) < bucketSize {
if len ( bucket . entries ) < bucketSize {
@ -496,12 +501,11 @@ outer:
}
}
}
}
// del removes an entry from the node table (used to evacuate failed/non-bonded
// delete removes an entry from the node table (used to evacuate
// discovery peers).
// failed/non-bonded discovery peers).
func ( tab * Table ) del ( node * Node ) {
func ( tab * Table ) delete ( node * Node ) {
tab . mutex . Lock ( )
tab . mutex . Lock ( )
defer tab . mutex . Unlock ( )
defer tab . mutex . Unlock ( )
bucket := tab . buckets [ logdist ( tab . self . sha , node . sha ) ]
bucket := tab . buckets [ logdist ( tab . self . sha , node . sha ) ]
for i := range bucket . entries {
for i := range bucket . entries {
if bucket . entries [ i ] . ID == node . ID {
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 {
func ( b * bucket ) bump ( n * Node ) bool {
for i := range b . entries {
for i := range b . entries {
if b . entries [ i ] . ID == n . ID {
if b . entries [ i ] . ID == n . ID {