-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
multi+refactor: persistent peer manager #5700
Changes from 1 commit
252c36a
8e772db
082bc98
1f6da30
417991c
5221aaf
a5f03dc
6756734
194de63
73c105d
b7e26cf
cdcdfb6
e226516
a41637e
71b397d
c11367e
b7bd6f9
077d9ca
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,41 @@ | ||
package peer | ||
|
||
import ( | ||
"crypto/rand" | ||
"math/big" | ||
"sync" | ||
"time" | ||
|
||
"github.com/btcsuite/btcd/btcec/v2" | ||
"github.com/btcsuite/btcd/connmgr" | ||
"github.com/lightningnetwork/lnd/lnwire" | ||
"github.com/lightningnetwork/lnd/routing/route" | ||
) | ||
|
||
const ( | ||
// defaultStableConnDuration is a floor under which all reconnection | ||
// attempts will apply exponential randomized backoff. Connections | ||
// durations exceeding this value will be eligible to have their | ||
// backoffs reduced. | ||
defaultStableConnDuration = 10 * time.Minute | ||
) | ||
|
||
// PersistentPeerMgrConfig holds the config of the PersistentPeerManager. | ||
type PersistentPeerMgrConfig struct { | ||
// MinBackoff is the shortest backoff when reconnecting to a persistent | ||
// peer. | ||
MinBackoff time.Duration | ||
|
||
// MaxBackoff is the longest backoff when reconnecting to a persistent | ||
// peer. | ||
MaxBackoff time.Duration | ||
} | ||
|
||
// PersistentPeerManager manages persistent peers. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe add a basic description of what is/makes a peer persistent? |
||
type PersistentPeerManager struct { | ||
// cfg holds the config of the manager. | ||
cfg *PersistentPeerMgrConfig | ||
|
||
// conns maps a peer's public key to a persistentPeer object. | ||
conns map[route.Vertex]*persistentPeer | ||
|
||
|
@@ -34,11 +59,17 @@ type persistentPeer struct { | |
// connReqs holds all the active connection requests that we have for | ||
// the peer. | ||
connReqs []*connmgr.ConnReq | ||
|
||
// backoff is the time to wait before trying to reconnect to a peer. | ||
backoff time.Duration | ||
} | ||
|
||
// NewPersistentPeerManager creates a new PersistentPeerManager instance. | ||
func NewPersistentPeerManager() *PersistentPeerManager { | ||
func NewPersistentPeerManager( | ||
cfg *PersistentPeerMgrConfig) *PersistentPeerManager { | ||
|
||
return &PersistentPeerManager{ | ||
cfg: cfg, | ||
conns: make(map[route.Vertex]*persistentPeer), | ||
} | ||
} | ||
|
@@ -49,10 +80,18 @@ func (m *PersistentPeerManager) AddPeer(pubKey *btcec.PublicKey, perm bool) { | |
m.Lock() | ||
defer m.Unlock() | ||
|
||
m.conns[route.NewVertex(pubKey)] = &persistentPeer{ | ||
pubKey: pubKey, | ||
perm: perm, | ||
addrs: make(map[string]*lnwire.NetAddress), | ||
peerKey := route.NewVertex(pubKey) | ||
|
||
backoff := m.cfg.MinBackoff | ||
if peer, ok := m.conns[peerKey]; ok { | ||
backoff = peer.backoff | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the peer is already being tracked, does the fetch call above still add something? Perhaps |
||
} | ||
|
||
m.conns[peerKey] = &persistentPeer{ | ||
pubKey: pubKey, | ||
perm: perm, | ||
addrs: make(map[string]*lnwire.NetAddress), | ||
backoff: backoff, | ||
} | ||
} | ||
|
||
|
@@ -236,3 +275,91 @@ func (m *PersistentPeerManager) NumPeerConnReqs(pubKey *btcec.PublicKey) int { | |
|
||
return len(peer.connReqs) | ||
} | ||
|
||
// NextPeerBackoff calculates, sets and returns the next backoff duration that | ||
// should be used before attempting to reconnect to the peer. | ||
func (m *PersistentPeerManager) NextPeerBackoff(pubKey *btcec.PublicKey, | ||
startTime time.Time) time.Duration { | ||
|
||
m.Lock() | ||
defer m.Unlock() | ||
|
||
peer, ok := m.conns[route.NewVertex(pubKey)] | ||
if !ok { | ||
return m.cfg.MinBackoff | ||
} | ||
|
||
peer.backoff = nextPeerBackoff( | ||
peer.backoff, m.cfg.MinBackoff, m.cfg.MaxBackoff, startTime, | ||
) | ||
|
||
return peer.backoff | ||
} | ||
|
||
// nextPeerBackoff computes the next backoff duration for a peer using | ||
// exponential backoff. If no previous backoff was known, the default is | ||
// returned. | ||
func nextPeerBackoff(currentBackoff, minBackoff, maxBackoff time.Duration, | ||
startTime time.Time) time.Duration { | ||
|
||
// If the peer failed to start properly, we'll just use the previous | ||
// backoff to compute the subsequent randomized exponential backoff | ||
// duration. This will roughly double on average. | ||
if startTime.IsZero() { | ||
return computeNextBackoff(currentBackoff, maxBackoff) | ||
} | ||
|
||
// The peer succeeded in starting. If the connection didn't last long | ||
// enough to be considered stable, we'll continue to back off retries | ||
// with this peer. | ||
connDuration := time.Since(startTime) | ||
if connDuration < defaultStableConnDuration { | ||
return computeNextBackoff(currentBackoff, maxBackoff) | ||
} | ||
|
||
// The peer succeed in starting and this was stable peer, so we'll | ||
// reduce the timeout duration by the length of the connection after | ||
// applying randomized exponential backoff. We'll only apply this in the | ||
// case that: | ||
// reb(curBackoff) - connDuration > cfg.MinBackoff | ||
relaxedBackoff := computeNextBackoff(currentBackoff, maxBackoff) | ||
relaxedBackoff -= connDuration | ||
|
||
if relaxedBackoff > maxBackoff { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should be minBackoff |
||
return relaxedBackoff | ||
} | ||
|
||
// Lastly, if reb(currBackoff) - connDuration <= cfg.MinBackoff, meaning | ||
// the stable connection lasted much longer than our previous backoff. | ||
// To reward such good behavior, we'll reconnect after the default | ||
// timeout. | ||
return minBackoff | ||
} | ||
|
||
// computeNextBackoff uses a truncated exponential backoff to compute the next | ||
// backoff using the value of the exiting backoff. The returned duration is | ||
// randomized in either direction by 1/20 to prevent tight loops from | ||
// stabilizing. | ||
func computeNextBackoff(currBackoff, maxBackoff time.Duration) time.Duration { | ||
// Double the current backoff, truncating if it exceeds our maximum. | ||
nextBackoff := 2 * currBackoff | ||
if nextBackoff > maxBackoff { | ||
nextBackoff = maxBackoff | ||
} | ||
|
||
// Using 1/10 of our duration as a margin, compute a random offset to | ||
// avoid the nodes entering connection cycles. | ||
margin := nextBackoff / 10 | ||
|
||
var wiggle big.Int | ||
wiggle.SetUint64(uint64(margin)) | ||
if _, err := rand.Int(rand.Reader, &wiggle); err != nil { | ||
// Randomizing is not mission critical, so we'll just return the | ||
// current backoff. | ||
return nextBackoff | ||
} | ||
|
||
// Otherwise add in our wiggle, but subtract out half of the margin so | ||
// that the backoff can tweaked by 1/20 in either direction. | ||
return nextBackoff + (time.Duration(wiggle.Uint64()) - margin/2) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think
route.Vertex
needs to be introduced