@cryptotaxi247 / kubo / commits / 70ceaa975

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 {