more tests and add in table filtering by peer latency
Jeromy committed
Aug 11, 2014 at 20:11 UTC
f09dba772cdcc5cb7705a816db8d83917a65d50e
7 files changed
+223
-46
peer/peer.go
+2
@@ -71,6 +71,8 @@ func (p *Peer) GetLatency() (out time.Duration) {
71
return
72
}
73
74
+// TODO: Instead of just keeping a single number,
75
+// keep a running average over the last hour or so
76
func (p *Peer) SetLatency(laten time.Duration) {
77
p.latenLock.Lock()
78
p.latency = laten
routing/dht/dht.go
+42
-7
@@ -74,8 +74,12 @@ func NewDHT(p *peer.Peer, net swarm.Network) *IpfsDHT {
74
dht.listeners = make(map[uint64]*listenInfo)
75
dht.providers = make(map[u.Key][]*providerInfo)
76
dht.shutdown = make(chan struct{})
77
- dht.routes = make([]*kb.RoutingTable, 1)
78
- dht.routes[0] = kb.NewRoutingTable(20, kb.ConvertPeerID(p.ID))
77
+
78
+ dht.routes = make([]*kb.RoutingTable, 3)
79
+ dht.routes[0] = kb.NewRoutingTable(20, kb.ConvertPeerID(p.ID), time.Millisecond*30)
80
+ dht.routes[1] = kb.NewRoutingTable(20, kb.ConvertPeerID(p.ID), time.Millisecond*100)
81
+ dht.routes[2] = kb.NewRoutingTable(20, kb.ConvertPeerID(p.ID), time.Hour)
82
+
83
dht.birth = time.Now()
84
return dht
85
}
@@ -253,7 +257,13 @@ func (dht *IpfsDHT) handleGetValue(p *peer.Peer, pmes *PBDHTMessage) {
257
// No providers?
258
// Find closest peer on given cluster to desired key and reply with that info
259
256
- level := pmes.GetValue()[0] // Using value field to specify cluster level
260
+ level := 0
261
+ if len(pmes.GetValue()) < 1 {
262
+ // TODO: maybe return an error? Defaulting isnt a good idea IMO
263
+ u.PErr("handleGetValue: no routing level specified, assuming 0")
264
+ } else {
265
+ level = int(pmes.GetValue()[0]) // Using value field to specify cluster level
266
+ }
267
268
closer := dht.routes[level].NearestPeer(kb.ConvertKey(u.Key(pmes.GetKey())))
269
@@ -477,6 +487,7 @@ func (dht *IpfsDHT) getValueSingle(p *peer.Peer, key u.Key, timeout time.Duratio
487
response_chan := dht.ListenFor(pmes.Id, 1, time.Minute)
488
489
mes := swarm.NewMessage(p, pmes.ToProtobuf())
490
+ t := time.Now()
491
dht.network.Send(mes)
492
493
// Wait for either the response or a timeout
@@ -490,6 +501,8 @@ func (dht *IpfsDHT) getValueSingle(p *peer.Peer, key u.Key, timeout time.Duratio
501
u.PErr("response channel closed before timeout, please investigate.")
502
return nil, u.ErrTimeout
503
}
504
+ roundtrip := time.Since(t)
505
+ resp.Peer.SetLatency(roundtrip)
506
pmes_out := new(PBDHTMessage)
507
err := proto.Unmarshal(resp.Data, pmes_out)
508
if err != nil {
@@ -513,7 +526,8 @@ func (dht *IpfsDHT) getFromPeerList(key u.Key, timeout time.Duration,
526
u.PErr("getValue error: %s", err)
527
continue
528
}
516
- p, err = dht.Connect(maddr)
529
+
530
+ p, err = dht.network.Connect(maddr)
531
if err != nil {
532
u.PErr("getValue error: %s", err)
533
continue
@@ -547,9 +561,21 @@ func (dht *IpfsDHT) PutLocal(key u.Key, value []byte) error {
561
}
562
563
func (dht *IpfsDHT) Update(p *peer.Peer) {
550
- removed := dht.routes[0].Update(p)
551
- if removed != nil {
552
- dht.network.Drop(removed)
564
+ for _, route := range dht.routes {
565
+ removed := route.Update(p)
566
+ // Only drop the connection if no tables refer to this peer
567
+ if removed != nil {
568
+ found := false
569
+ for _, r := range dht.routes {
570
+ if r.Find(removed.ID) != nil {
571
+ found = true
572
+ break
573
+ }
574
+ }
575
+ if !found {
576
+ dht.network.Drop(removed)
577
+ }
578
+ }
579
}
580
}
581
@@ -574,6 +600,7 @@ func (dht *IpfsDHT) findPeerSingle(p *peer.Peer, id peer.ID, timeout time.Durati
600
601
mes := swarm.NewMessage(p, pmes.ToProtobuf())
602
listenChan := dht.ListenFor(pmes.Id, 1, time.Minute)
603
+ t := time.Now()
604
dht.network.Send(mes)
605
after := time.After(timeout)
606
select {
@@ -581,6 +608,8 @@ func (dht *IpfsDHT) findPeerSingle(p *peer.Peer, id peer.ID, timeout time.Durati
608
dht.Unlisten(pmes.Id)
609
return nil, u.ErrTimeout
610
case resp := <-listenChan:
611
+ roundtrip := time.Since(t)
612
+ resp.Peer.SetLatency(roundtrip)
613
pmes_out := new(PBDHTMessage)
614
err := proto.Unmarshal(resp.Data, pmes_out)
615
if err != nil {
@@ -590,3 +619,9 @@ func (dht *IpfsDHT) findPeerSingle(p *peer.Peer, id peer.ID, timeout time.Durati
619
return pmes_out, nil
620
}
621
}
622
+
623
+func (dht *IpfsDHT) PrintTables() {
624
+ for _, route := range dht.routes {
625
+ route.Print()
626
+ }
627
+}
routing/dht/ext_test.go
+36
-3
@@ -16,13 +16,16 @@ import (
16
// fauxNet is a standin for a swarm.Network in order to more easily recreate
17
// different testing scenarios
18
type fauxNet struct {
19
- Chan *swarm.Chan
19
+ Chan *swarm.Chan
20
+ handlers []mesHandleFunc
21
22
swarm.Network
22
-
23
- handlers []mesHandleFunc
23
}
24
25
+// mesHandleFunc is a function that takes in outgoing messages
26
+// and can respond to them, simulating other peers on the network.
27
+// returning nil will chose not to respond and pass the message onto the
28
+// next registered handler
29
type mesHandleFunc func(*swarm.Message) *swarm.Message
30
31
func newFauxNet() *fauxNet {
@@ -32,6 +35,9 @@ func newFauxNet() *fauxNet {
35
return fn
36
}
37
38
+// Instead of 'Listening' Start up a goroutine that will check
39
+// all outgoing messages against registered message handlers,
40
+// and reply if needed
41
func (f *fauxNet) Listen() error {
42
go func() {
43
for {
@@ -95,6 +101,7 @@ func TestGetFailures(t *testing.T) {
101
t.Fatal("Did not get expected error!")
102
}
103
104
+ // Reply with failures to every message
105
fn.AddHandler(func(mes *swarm.Message) *swarm.Message {
106
pmes := new(PBDHTMessage)
107
err := proto.Unmarshal(mes.Data, pmes)
@@ -120,4 +127,30 @@ func TestGetFailures(t *testing.T) {
127
} else {
128
t.Fatal("expected error, got none.")
129
}
130
+
131
+ success := make(chan struct{})
132
+ fn.handlers = nil
133
+ fn.AddHandler(func(mes *swarm.Message) *swarm.Message {
134
+ resp := new(PBDHTMessage)
135
+ err := proto.Unmarshal(mes.Data, resp)
136
+ if err != nil {
137
+ t.Fatal(err)
138
+ }
139
+ if resp.GetSuccess() {
140
+ t.Fatal("Get returned success when it shouldnt have.")
141
+ }
142
+ success <- struct{}{}
143
+ return nil
144
+ })
145
+
146
+ // Now we test this DHT's handleGetValue failure
147
+ req := DHTMessage{
148
+ Type: PBDHTMessage_GET_VALUE,
149
+ Key: "hello",
150
+ Id: GenerateMessageID(),
151
+ Value: []byte{0},
152
+ }
153
+ fn.Chan.Incoming <- swarm.NewMessage(other, req.ToProtobuf())
154
+
155
+ <-success
156
}
routing/dht/routing.go
+3
-5
@@ -89,9 +89,7 @@ func (s *IpfsDHT) GetValue(key u.Key, timeout time.Duration) ([]byte, error) {
89
panic("not yet implemented")
90
}
91
92
- // TODO: dht.Connect has overhead due to an internal
93
- // ping to the target. Use something else
94
- p, err = s.Connect(maddr)
92
+ p, err = s.network.Connect(maddr)
93
if err != nil {
94
// Move up route level
95
panic("not yet implemented.")
@@ -167,7 +165,7 @@ func (s *IpfsDHT) FindProviders(key u.Key, timeout time.Duration) ([]*peer.Peer,
165
u.PErr("error connecting to new peer: %s", err)
166
continue
167
}
170
- p, err = s.Connect(maddr)
168
+ p, err = s.network.Connect(maddr)
169
if err != nil {
170
u.PErr("error connecting to new peer: %s", err)
171
continue
@@ -204,7 +202,7 @@ func (s *IpfsDHT) FindPeer(id peer.ID, timeout time.Duration) (*peer.Peer, error
202
return nil, u.WrapError(err, "FindPeer received bad info")
203
}
204
207
- nxtPeer, err := s.Connect(addr)
205
+ nxtPeer, err := s.network.Connect(addr)
206
if err != nil {
207
return nil, u.WrapError(err, "FindPeer failed to connect to new peer.")
208
}
routing/kbucket/bucket.go
+36
-18
@@ -2,16 +2,27 @@ package dht
2
3
import (
4
"container/list"
5
+ "sync"
6
7
peer "github.com/jbenet/go-ipfs/peer"
8
)
9
10
// Bucket holds a list of peers.
10
-type Bucket list.List
11
+type Bucket struct {
12
+ lk sync.RWMutex
13
+ list *list.List
14
+}
15
+
16
+func NewBucket() *Bucket {
17
+ b := new(Bucket)
18
+ b.list = list.New()
19
+ return b
20
+}
21
22
func (b *Bucket) Find(id peer.ID) *list.Element {
13
- bucket_list := (*list.List)(b)
14
- for e := bucket_list.Front(); e != nil; e = e.Next() {
23
+ b.lk.RLock()
24
+ defer b.lk.RUnlock()
25
+ for e := b.list.Front(); e != nil; e = e.Next() {
26
if e.Value.(*peer.Peer).ID.Equal(id) {
27
return e
28
}
@@ -20,34 +31,42 @@ func (b *Bucket) Find(id peer.ID) *list.Element {
31
}
32
33
func (b *Bucket) MoveToFront(e *list.Element) {
23
- bucket_list := (*list.List)(b)
24
- bucket_list.MoveToFront(e)
34
+ b.lk.Lock()
35
+ b.list.MoveToFront(e)
36
+ b.lk.Unlock()
37
}
38
39
func (b *Bucket) PushFront(p *peer.Peer) {
28
- bucket_list := (*list.List)(b)
29
- bucket_list.PushFront(p)
40
+ b.lk.Lock()
41
+ b.list.PushFront(p)
42
+ b.lk.Unlock()
43
}
44
45
func (b *Bucket) PopBack() *peer.Peer {
33
- bucket_list := (*list.List)(b)
34
- last := bucket_list.Back()
35
- bucket_list.Remove(last)
46
+ b.lk.Lock()
47
+ defer b.lk.Unlock()
48
+ last := b.list.Back()
49
+ b.list.Remove(last)
50
return last.Value.(*peer.Peer)
51
}
52
53
func (b *Bucket) Len() int {
40
- bucket_list := (*list.List)(b)
41
- return bucket_list.Len()
54
+ b.lk.RLock()
55
+ defer b.lk.RUnlock()
56
+ return b.list.Len()
57
}
58
59
// Splits a buckets peers into two buckets, the methods receiver will have
60
// peers with CPL equal to cpl, the returned bucket will have peers with CPL
61
// greater than cpl (returned bucket has closer peers)
62
func (b *Bucket) Split(cpl int, target ID) *Bucket {
48
- bucket_list := (*list.List)(b)
63
+ b.lk.Lock()
64
+ defer b.lk.Unlock()
65
+
66
out := list.New()
50
- e := bucket_list.Front()
67
+ newbuck := NewBucket()
68
+ newbuck.list = out
69
+ e := b.list.Front()
70
for e != nil {
71
peer_id := ConvertPeerID(e.Value.(*peer.Peer).ID)
72
peer_cpl := prefLen(peer_id, target)
@@ -55,15 +74,14 @@ func (b *Bucket) Split(cpl int, target ID) *Bucket {
74
cur := e
75
out.PushBack(e.Value)
76
e = e.Next()
58
- bucket_list.Remove(cur)
77
+ b.list.Remove(cur)
78
continue
79
}
80
e = e.Next()
81
}
63
- return (*Bucket)(out)
82
+ return newbuck
83
}
84
85
func (b *Bucket) getIter() *list.Element {
67
- bucket_list := (*list.List)(b)
68
- return bucket_list.Front()
86
+ return b.list.Front()
87
}
routing/kbucket/table.go
+24
-6
@@ -2,8 +2,10 @@ package dht
2
3
import (
4
"container/list"
5
+ "fmt"
6
"sort"
7
"sync"
8
+ "time"
9
10
peer "github.com/jbenet/go-ipfs/peer"
11
u "github.com/jbenet/go-ipfs/util"
@@ -18,16 +20,20 @@ type RoutingTable struct {
20
// Blanket lock, refine later for better performance
21
tabLock sync.RWMutex
22
23
+ // Maximum acceptable latency for peers in this cluster
24
+ maxLatency time.Duration
25
+
26
// kBuckets define all the fingers to other nodes.
27
Buckets []*Bucket
28
bucketsize int
29
}
30
26
-func NewRoutingTable(bucketsize int, local_id ID) *RoutingTable {
31
+func NewRoutingTable(bucketsize int, local_id ID, latency time.Duration) *RoutingTable {
32
rt := new(RoutingTable)
28
- rt.Buckets = []*Bucket{new(Bucket)}
33
+ rt.Buckets = []*Bucket{NewBucket()}
34
rt.bucketsize = bucketsize
35
rt.local = local_id
36
+ rt.maxLatency = latency
37
return rt
38
}
39
@@ -48,6 +54,10 @@ func (rt *RoutingTable) Update(p *peer.Peer) *peer.Peer {
54
e := bucket.Find(p.ID)
55
if e == nil {
56
// New peer, add to bucket
57
+ if p.GetLatency() > rt.maxLatency {
58
+ // Connection doesnt meet requirements, skip!
59
+ return nil
60
+ }
61
bucket.PushFront(p)
62
63
// Are we past the max bucket size?
@@ -150,17 +160,16 @@ func (rt *RoutingTable) NearestPeers(id ID, count int) []*peer.Peer {
160
// In the case of an unusual split, one bucket may be empty.
161
// if this happens, search both surrounding buckets for nearest peer
162
if cpl > 0 {
153
- plist := (*list.List)(rt.Buckets[cpl-1])
163
+ plist := rt.Buckets[cpl-1].list
164
peerArr = copyPeersFromList(id, peerArr, plist)
165
}
166
167
if cpl < len(rt.Buckets)-1 {
158
- plist := (*list.List)(rt.Buckets[cpl+1])
168
+ plist := rt.Buckets[cpl+1].list
169
peerArr = copyPeersFromList(id, peerArr, plist)
170
}
171
} else {
162
- plist := (*list.List)(bucket)
163
- peerArr = copyPeersFromList(id, peerArr, plist)
172
+ peerArr = copyPeersFromList(id, peerArr, bucket.list)
173
}
174
175
// Sort by distance to local peer
@@ -193,3 +202,12 @@ func (rt *RoutingTable) Listpeers() []*peer.Peer {
202
}
203
return peers
204
}
205
+
206
+func (rt *RoutingTable) Print() {
207
+ fmt.Printf("Routing Table, bs = %d, Max latency = %d\n", rt.bucketsize, rt.maxLatency)
208
+ rt.tabLock.RLock()
209
+ peers := rt.Listpeers()
210
+ for i, p := range peers {
211
+ fmt.Printf("%d) %s %s\n", i, p.ID.Pretty(), p.GetLatency().String())
212
+ }
213
+}
routing/kbucket/table_test.go
+80
-7
@@ -1,11 +1,11 @@
1
package dht
2
3
import (
4
- "container/list"
4
crand "crypto/rand"
5
"crypto/sha256"
6
"math/rand"
7
"testing"
8
+ "time"
9
10
peer "github.com/jbenet/go-ipfs/peer"
11
)
@@ -27,7 +27,7 @@ func _randID() ID {
27
28
// Test basic features of the bucket struct
29
func TestBucket(t *testing.T) {
30
- b := new(Bucket)
30
+ b := NewBucket()
31
32
peers := make([]*peer.Peer, 100)
33
for i := 0; i < 100; i++ {
@@ -45,7 +45,7 @@ func TestBucket(t *testing.T) {
45
}
46
47
spl := b.Split(0, ConvertPeerID(local.ID))
48
- llist := (*list.List)(b)
48
+ llist := b.list
49
for e := llist.Front(); e != nil; e = e.Next() {
50
p := ConvertPeerID(e.Value.(*peer.Peer).ID)
51
cpl := xor(p, local_id).commonPrefixLen()
@@ -54,7 +54,7 @@ func TestBucket(t *testing.T) {
54
}
55
}
56
57
- rlist := (*list.List)(spl)
57
+ rlist := spl.list
58
for e := rlist.Front(); e != nil; e = e.Next() {
59
p := ConvertPeerID(e.Value.(*peer.Peer).ID)
60
cpl := xor(p, local_id).commonPrefixLen()
@@ -67,7 +67,7 @@ func TestBucket(t *testing.T) {
67
// Right now, this just makes sure that it doesnt hang or crash
68
func TestTableUpdate(t *testing.T) {
69
local := _randPeer()
70
- rt := NewRoutingTable(10, ConvertPeerID(local.ID))
70
+ rt := NewRoutingTable(10, ConvertPeerID(local.ID), time.Hour)
71
72
peers := make([]*peer.Peer, 100)
73
for i := 0; i < 100; i++ {
@@ -93,7 +93,7 @@ func TestTableUpdate(t *testing.T) {
93
94
func TestTableFind(t *testing.T) {
95
local := _randPeer()
96
- rt := NewRoutingTable(10, ConvertPeerID(local.ID))
96
+ rt := NewRoutingTable(10, ConvertPeerID(local.ID), time.Hour)
97
98
peers := make([]*peer.Peer, 100)
99
for i := 0; i < 5; i++ {
@@ -110,7 +110,7 @@ func TestTableFind(t *testing.T) {
110
111
func TestTableFindMultiple(t *testing.T) {
112
local := _randPeer()
113
- rt := NewRoutingTable(20, ConvertPeerID(local.ID))
113
+ rt := NewRoutingTable(20, ConvertPeerID(local.ID), time.Hour)
114
115
peers := make([]*peer.Peer, 100)
116
for i := 0; i < 18; i++ {
@@ -124,3 +124,76 @@ func TestTableFindMultiple(t *testing.T) {
124
t.Fatalf("Got back different number of peers than we expected.")
125
}
126
}
127
+
128
+// Looks for race conditions in table operations. For a more 'certain'
129
+// test, increase the loop counter from 1000 to a much higher number
130
+// and set GOMAXPROCS above 1
131
+func TestTableMultithreaded(t *testing.T) {
132
+ local := peer.ID("localPeer")
133
+ tab := NewRoutingTable(20, ConvertPeerID(local), time.Hour)
134
+ var peers []*peer.Peer
135
+ for i := 0; i < 500; i++ {
136
+ peers = append(peers, _randPeer())
137
+ }
138
+
139
+ done := make(chan struct{})
140
+ go func() {
141
+ for i := 0; i < 1000; i++ {
142
+ n := rand.Intn(len(peers))
143
+ tab.Update(peers[n])
144
+ }
145
+ done <- struct{}{}
146
+ }()
147
+
148
+ go func() {
149
+ for i := 0; i < 1000; i++ {
150
+ n := rand.Intn(len(peers))
151
+ tab.Update(peers[n])
152
+ }
153
+ done <- struct{}{}
154
+ }()
155
+
156
+ go func() {
157
+ for i := 0; i < 1000; i++ {
158
+ n := rand.Intn(len(peers))
159
+ tab.Find(peers[n].ID)
160
+ }
161
+ done <- struct{}{}
162
+ }()
163
+ <-done
164
+ <-done
165
+ <-done
166
+}
167
+
168
+func BenchmarkUpdates(b *testing.B) {
169
+ b.StopTimer()
170
+ local := ConvertKey("localKey")
171
+ tab := NewRoutingTable(20, local, time.Hour)
172
+
173
+ var peers []*peer.Peer
174
+ for i := 0; i < b.N; i++ {
175
+ peers = append(peers, _randPeer())
176
+ }
177
+
178
+ b.StartTimer()
179
+ for i := 0; i < b.N; i++ {
180
+ tab.Update(peers[i])
181
+ }
182
+}
183
+
184
+func BenchmarkFinds(b *testing.B) {
185
+ b.StopTimer()
186
+ local := ConvertKey("localKey")
187
+ tab := NewRoutingTable(20, local, time.Hour)
188
+
189
+ var peers []*peer.Peer
190
+ for i := 0; i < b.N; i++ {
191
+ peers = append(peers, _randPeer())
192
+ tab.Update(peers[i])
193
+ }
194
+
195
+ b.StartTimer()
196
+ for i := 0; i < b.N; i++ {
197
+ tab.Find(peers[i].ID)
198
+ }
199
+}