p2p/discover: add node expirer and related tests

This commit is contained in:
Péter Szilágyi
2015-04-27 17:38:28 +03:00
parent a136e2bb22
commit 437cf4b3ac
3 changed files with 116 additions and 4 deletions

View File

@ -7,6 +7,7 @@ import (
"bytes"
"encoding/binary"
"os"
"sync"
"time"
"github.com/ethereum/go-ethereum/logger"
@ -17,13 +18,19 @@ import (
"github.com/syndtr/goleveldb/leveldb/storage"
)
// Special node ID to use as a nil element.
var nodeDBNilNodeID = NodeID{}
var (
nodeDBNilNodeID = NodeID{} // Special node ID to use as a nil element.
nodeDBNodeExpiration = 24 * time.Hour // Time after which an unseen node should be dropped.
nodeDBCleanupCycle = time.Hour // Time period for running the expiration task.
)
// nodeDB stores all nodes we know about.
type nodeDB struct {
lvl *leveldb.DB // Interface to the database itself
seeder iterator.Iterator // Iterator for fetching possible seed nodes
runner sync.Once // Ensures we can start at most one expirer
quit chan struct{} // Channel to signal the expiring thread to stop
}
// Schema layout for the node database
@ -53,7 +60,10 @@ func newMemoryNodeDB() (*nodeDB, error) {
if err != nil {
return nil, err
}
return &nodeDB{lvl: db}, nil
return &nodeDB{
lvl: db,
quit: make(chan struct{}),
}, nil
}
// newPersistentNodeDB creates/opens a leveldb backed persistent node database,
@ -91,7 +101,10 @@ func newPersistentNodeDB(path string, version int) (*nodeDB, error) {
return newPersistentNodeDB(path, version)
}
}
return &nodeDB{lvl: db}, nil
return &nodeDB{
lvl: db,
quit: make(chan struct{}),
}, nil
}
// makeKey generates the leveldb key-blob from a node id and its particular
@ -164,6 +177,55 @@ func (db *nodeDB) updateNode(node *Node) error {
return db.lvl.Put(makeKey(node.ID, nodeDBDiscoverRoot), blob, nil)
}
// expirer should be started in a go routine, and is responsible for looping ad
// infinitum and dropping stale data from the database.
func (db *nodeDB) expirer() {
db.runner.Do(func() {
tick := time.Tick(nodeDBCleanupCycle)
for {
select {
case <-tick:
if err := db.expireNodes(); err != nil {
glog.V(logger.Error).Infof("Failed to expire nodedb items: %v", err)
}
case <-db.quit:
return
}
}
})
}
// expireNodes iterates over the database and deletes all nodes that have not
// been seen (i.e. received a pong from) for some alloted time.
func (db *nodeDB) expireNodes() error {
threshold := time.Now().Add(-nodeDBNodeExpiration)
// Find discovered nodes that are older than the allowance
it := db.lvl.NewIterator(nil, nil)
defer it.Release()
for it.Next() {
// Skip the item if not a discovery node
id, field := splitKey(it.Key())
if field != nodeDBDiscoverRoot {
continue
}
// Skip the node if not expired yet
if seen := db.lastPong(id); seen.After(threshold) {
continue
}
// Otherwise delete all associated information
prefix := makeKey(id, "")
for ok := it.Seek(prefix); ok && bytes.HasPrefix(it.Key(), prefix); ok = it.Next() {
if err := db.lvl.Delete(it.Key(), nil); err != nil {
return err
}
}
}
return nil
}
// lastPing retrieves the time of the last ping packet send to a remote node,
// requesting binding.
func (db *nodeDB) lastPing(id NodeID) time.Time {
@ -226,5 +288,6 @@ func (db *nodeDB) close() {
if db.seeder != nil {
db.seeder.Release()
}
close(db.quit)
db.lvl.Close()
}