github.com/celestiaorg/celestia-node@v0.15.0-beta.1/share/p2p/discovery/backoff.go (about) 1 package discovery 2 3 import ( 4 "context" 5 "errors" 6 "sync" 7 "time" 8 9 "github.com/libp2p/go-libp2p/core/host" 10 "github.com/libp2p/go-libp2p/core/peer" 11 "github.com/libp2p/go-libp2p/p2p/discovery/backoff" 12 ) 13 14 const ( 15 // gcInterval is a default period after which disconnected peers will be removed from cache 16 gcInterval = time.Minute 17 // connectTimeout is the timeout used for dialing peers and discovering peer addresses. 18 connectTimeout = time.Minute * 2 19 ) 20 21 var ( 22 defaultBackoffFactory = backoff.NewFixedBackoff(time.Minute * 10) 23 errBackoffNotEnded = errors.New("share/discovery: backoff period has not ended") 24 ) 25 26 // backoffConnector wraps a libp2p.Host to establish a connection with peers 27 // with adding a delay for the next connection attempt. 28 type backoffConnector struct { 29 h host.Host 30 backoff backoff.BackoffFactory 31 32 cacheLk sync.Mutex 33 cacheData map[peer.ID]backoffData 34 } 35 36 // backoffData stores time when next connection attempt with the remote peer. 37 type backoffData struct { 38 nexttry time.Time 39 backoff backoff.BackoffStrategy 40 } 41 42 func newBackoffConnector(h host.Host, factory backoff.BackoffFactory) *backoffConnector { 43 return &backoffConnector{ 44 h: h, 45 backoff: factory, 46 cacheData: make(map[peer.ID]backoffData), 47 } 48 } 49 50 // Connect puts peer to the backoffCache and tries to establish a connection with it. 51 func (b *backoffConnector) Connect(ctx context.Context, p peer.AddrInfo) error { 52 if b.HasBackoff(p.ID) { 53 return errBackoffNotEnded 54 } 55 56 ctx, cancel := context.WithTimeout(ctx, connectTimeout) 57 defer cancel() 58 59 err := b.h.Connect(ctx, p) 60 // we don't want to add backoff when the context is canceled. 61 if !errors.Is(err, context.Canceled) { 62 b.Backoff(p.ID) 63 } 64 return err 65 } 66 67 // Backoff adds or extends backoff delay for the peer. 68 func (b *backoffConnector) Backoff(p peer.ID) { 69 b.cacheLk.Lock() 70 defer b.cacheLk.Unlock() 71 72 data, ok := b.cacheData[p] 73 if !ok { 74 data = backoffData{} 75 data.backoff = b.backoff() 76 b.cacheData[p] = data 77 } 78 79 data.nexttry = time.Now().Add(data.backoff.Delay()) 80 b.cacheData[p] = data 81 } 82 83 // HasBackoff checks if peer is in backoff. 84 func (b *backoffConnector) HasBackoff(p peer.ID) bool { 85 b.cacheLk.Lock() 86 cache, ok := b.cacheData[p] 87 b.cacheLk.Unlock() 88 return ok && time.Now().Before(cache.nexttry) 89 } 90 91 // GC is a perpetual GCing loop. 92 func (b *backoffConnector) GC(ctx context.Context) { 93 ticker := time.NewTicker(gcInterval) 94 defer ticker.Stop() 95 96 for { 97 select { 98 case <-ctx.Done(): 99 return 100 case <-ticker.C: 101 b.cacheLk.Lock() 102 for id, cache := range b.cacheData { 103 if cache.nexttry.Before(time.Now()) { 104 delete(b.cacheData, id) 105 } 106 } 107 b.cacheLk.Unlock() 108 } 109 } 110 } 111 112 func (b *backoffConnector) Size() int { 113 b.cacheLk.Lock() 114 defer b.cacheLk.Unlock() 115 return len(b.cacheData) 116 }