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  }