p2p/net/swarm: dial backoff
This commit introduces a backoff when failing to dial peers. It makes everything much faster.
Juan Batiz-Benet committed
Jan 13, 2015 at 05:04 UTC
70ceaa975d5b12bb3e610dcf03d89ef603862872
2 files changed
+83
-7
p2p/net/swarm/swarm.go
+5
-4
@@ -33,6 +33,7 @@ type Swarm struct {
33
peers peer.Peerstore
34
connh ConnHandler
35
dsync dialsync
36
+ backf dialbackoff
37
38
cg ctxgroup.ContextGroup
39
}
@@ -50,10 +51,10 @@ func NewSwarm(ctx context.Context, listenAddrs []ma.Multiaddr,
51
}
52
53
s := &Swarm{
53
- swarm: ps.NewSwarm(PSTransport),
54
- local: local,
55
- peers: peers,
56
- cg: ctxgroup.WithContext(ctx),
54
+ swarm: ps.NewSwarm(PSTransport),
55
+ local: local,
56
+ peers: peers,
57
+ cg: ctxgroup.WithContext(ctx),
58
}
59
60
// configure Swarm
p2p/net/swarm/swarm_dial.go
+78
-3
@@ -96,6 +96,71 @@ func (ds *dialsync) Unlock(dst peer.ID) {
96
ds.lock.Unlock()
97
}
98
99
+// dialbackoff is a struct used to avoid over-dialing the same, dead peers.
100
+// Whenever we totally time out on a peer (all three attempts), we add them
101
+// to dialbackoff. Then, whenevers goroutines would _wait_ (dialsync), they
102
+// check dialbackoff. If it's there, they don't wait and exit promptly with
103
+// an error. (the single goroutine that is actually dialing continues to
104
+// dial). If a dial is successful, the peer is removed from backoff.
105
+// Example:
106
+//
107
+// for {
108
+// if ok, wait := dialsync.Lock(p); !ok {
109
+// if backoff.Backoff(p) {
110
+// return errDialFailed
111
+// }
112
+// <-wait
113
+// continue
114
+// }
115
+// defer dialsync.Unlock(p)
116
+// c, err := actuallyDial(p)
117
+// if err != nil {
118
+// dialbackoff.AddBackoff(p)
119
+// continue
120
+// }
121
+// dialbackoff.Clear(p)
122
+// }
123
+//
124
+type dialbackoff struct {
125
+ entries map[peer.ID]struct{}
126
+ lock sync.RWMutex
127
+}
128
+
129
+func (db *dialbackoff) init() {
130
+ if db.entries == nil {
131
+ db.entries = make(map[peer.ID]struct{})
132
+ }
133
+}
134
+
135
+// Backoff returns whether the client should backoff from dialing
136
+// peeer p
137
+func (db *dialbackoff) Backoff(p peer.ID) bool {
138
+ db.lock.Lock()
139
+ db.init()
140
+ _, found := db.entries[p]
141
+ db.lock.Unlock()
142
+ return found
143
+}
144
+
145
+// AddBackoff lets other nodes know that we've entered backoff with
146
+// peer p, so dialers should not wait unnecessarily. We still will
147
+// attempt to dial with one goroutine, in case we get through.
148
+func (db *dialbackoff) AddBackoff(p peer.ID) {
149
+ db.lock.Lock()
150
+ db.init()
151
+ db.entries[p] = struct{}{}
152
+ db.lock.Unlock()
153
+}
154
+
155
+// Clear removes a backoff record. Clients should call this after a
156
+// successful Dial.
157
+func (db *dialbackoff) Clear(p peer.ID) {
158
+ db.lock.Lock()
159
+ db.init()
160
+ delete(db.entries, p)
161
+ db.lock.Unlock()
162
+}
163
+
164
// Dial connects to a peer.
165
//
166
// The idea is that the client of Swarm does not need to know what network
@@ -103,6 +168,7 @@ func (ds *dialsync) Unlock(dst peer.ID) {
168
// This allows us to use various transport protocols, do NAT traversal/relay,
169
// etc. to achive connection.
170
func (s *Swarm) Dial(ctx context.Context, p peer.ID) (*Conn, error) {
171
+ log := log.Prefix("swarm %s dialing %s", s.local, p)
172
if p == s.local {
173
return nil, errors.New("Attempted connection to self!")
174
}
@@ -126,7 +192,13 @@ func (s *Swarm) Dial(ctx context.Context, p peer.ID) (*Conn, error) {
192
193
// check if there's an ongoing dial to this peer
194
if ok, wait := s.dsync.Lock(p); !ok {
129
- log.Debugf("swarm %s dialing %s -- waiting for ongoing dial", s.local, p)
195
+
196
+ if s.backf.Backoff(p) {
197
+ log.Debugf("backoff")
198
+ return nil, fmt.Errorf("%s failed to dial %s, backing off.", s.local, p)
199
+ }
200
+
201
+ log.Debugf("waiting for ongoing dial")
202
select {
203
case <-wait: // wait for that dial to finish.
204
continue // and see if it worked (loop), OR we got an incoming dial.
@@ -137,14 +209,17 @@ func (s *Swarm) Dial(ctx context.Context, p peer.ID) (*Conn, error) {
209
210
// ok, we have been charged to dial! let's do it.
211
// if it succeeds, dial will add the conn to the swarm itself.
140
- log.Debugf("swarm %s dialing %s -- dial start", s.local, p)
212
+ log.Debugf("dial start")
213
ctxT, _ := context.WithTimeout(ctx, DialTimeout)
214
conn, err = s.dial(ctxT, p)
215
s.dsync.Unlock(p)
144
- log.Debugf("swarm %s dialing %s -- dial end %s", s.local, p, conn)
216
+ log.Debugf("dial end %s", conn)
217
if err != nil {
218
+ s.backf.AddBackoff(p) // let others know to backoff
219
+
220
continue // ok, we failed. try again. (if loop is done, our error is output)
221
}
222
+ s.backf.Clear(p) // okay, no longer need to backoff
223
return conn, nil
224
}
225
if err == nil {