@cryptotaxi247 / kubo / commits / f09dba772

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 +}