-
Notifications
You must be signed in to change notification settings - Fork 387
fix(p2p): update peer address on reconnect and optimize reacher #5348
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
Changes from 3 commits
b376d4f
b22d234
8f309fa
64e0bc4
73e102a
59da0bf
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 |
|---|---|---|
|
|
@@ -7,6 +7,7 @@ | |
| package reacher | ||
|
|
||
| import ( | ||
| "container/heap" | ||
| "context" | ||
| "sync" | ||
| "time" | ||
|
|
@@ -27,11 +28,41 @@ type peer struct { | |
| overlay swarm.Address | ||
| addr ma.Multiaddr | ||
| retryAfter time.Time | ||
| index int // index in the heap | ||
| } | ||
|
|
||
| // peerHeap is a min-heap of peers ordered by retryAfter time. | ||
| type peerHeap []*peer | ||
|
|
||
| func (h peerHeap) Len() int { return len(h) } | ||
| func (h peerHeap) Less(i, j int) bool { return h[i].retryAfter.Before(h[j].retryAfter) } | ||
| func (h peerHeap) Swap(i, j int) { | ||
| h[i], h[j] = h[j], h[i] | ||
| h[i].index = i | ||
| h[j].index = j | ||
| } | ||
|
|
||
| func (h *peerHeap) Push(x any) { | ||
| n := len(*h) | ||
| p := x.(*peer) | ||
| p.index = n | ||
| *h = append(*h, p) | ||
| } | ||
|
|
||
| func (h *peerHeap) Pop() any { | ||
| old := *h | ||
| n := len(old) | ||
| p := old[n-1] | ||
| old[n-1] = nil // avoid memory leak | ||
| p.index = -1 // for safety | ||
| *h = old[0 : n-1] | ||
| return p | ||
| } | ||
|
|
||
| type reacher struct { | ||
| mu sync.Mutex | ||
| peers map[string]*peer | ||
| mu sync.Mutex | ||
| peerHeap peerHeap // min-heap ordered by retryAfter | ||
| peerIndex map[string]*peer // lookup by overlay for O(1) access | ||
|
|
||
| newPeer chan struct{} | ||
| quit chan struct{} | ||
|
|
@@ -53,12 +84,13 @@ type Options struct { | |
|
|
||
| func New(streamer p2p.Pinger, notifier p2p.ReachableNotifier, o *Options, log log.Logger) *reacher { | ||
| r := &reacher{ | ||
| newPeer: make(chan struct{}, 1), | ||
| quit: make(chan struct{}), | ||
| pinger: streamer, | ||
| peers: make(map[string]*peer), | ||
| notifier: notifier, | ||
| logger: log.WithName("reacher").Register(), | ||
| newPeer: make(chan struct{}, 1), | ||
| quit: make(chan struct{}), | ||
| pinger: streamer, | ||
| peerHeap: make(peerHeap, 0), | ||
| peerIndex: make(map[string]*peer), | ||
| notifier: notifier, | ||
| logger: log.WithName("reacher").Register(), | ||
| } | ||
|
|
||
| if o == nil { | ||
|
|
@@ -80,7 +112,7 @@ func (r *reacher) manage() { | |
|
|
||
| defer r.wg.Done() | ||
|
|
||
| c := make(chan *peer) | ||
| c := make(chan peer) | ||
|
Contributor
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. Here and also bellow, why not using a pointer to a peer anymore?
Member
Author
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. By sending a value copy through the channel, each ping goroutine gets its own independent snapshot of the peer data, which is safe to read concurrently |
||
| defer close(c) | ||
|
|
||
| ctx, cancel := context.WithCancel(context.Background()) | ||
|
|
@@ -93,7 +125,7 @@ func (r *reacher) manage() { | |
|
|
||
| for { | ||
|
|
||
| p, tryAfter := r.tryAcquirePeer() | ||
| p, ok, tryAfter := r.tryAcquirePeer() | ||
|
|
||
| // if no peer is returned, | ||
| // wait until either more work or the closest retry-after time. | ||
|
|
@@ -111,7 +143,7 @@ func (r *reacher) manage() { | |
| } | ||
|
|
||
| // wait for work | ||
| if p == nil { | ||
| if !ok { | ||
| select { | ||
| case <-r.quit: | ||
| return | ||
|
|
@@ -129,7 +161,7 @@ func (r *reacher) manage() { | |
| } | ||
| } | ||
|
|
||
| func (r *reacher) ping(c chan *peer, ctx context.Context) { | ||
| func (r *reacher) ping(c chan peer, ctx context.Context) { | ||
| defer r.wg.Done() | ||
| for p := range c { | ||
| func() { | ||
|
|
@@ -147,44 +179,47 @@ func (r *reacher) ping(c chan *peer, ctx context.Context) { | |
| } | ||
| } | ||
|
|
||
| func (r *reacher) tryAcquirePeer() (*peer, time.Duration) { | ||
| func (r *reacher) tryAcquirePeer() (peer, bool, time.Duration) { | ||
| r.mu.Lock() | ||
| defer r.mu.Unlock() | ||
|
|
||
| var ( | ||
| now = time.Now() | ||
| nextClosest time.Time | ||
| ) | ||
| if len(r.peerHeap) == 0 { | ||
| return peer{}, false, 0 | ||
| } | ||
|
|
||
| for _, p := range r.peers { | ||
| now := time.Now() | ||
|
|
||
| // retry after has expired, retry | ||
| if now.After(p.retryAfter) { | ||
| p.retryAfter = time.Now().Add(r.options.RetryAfterDuration) | ||
| return p, 0 | ||
| } | ||
| // Peek at the peer with the earliest retryAfter | ||
| p := r.peerHeap[0] | ||
|
|
||
| // here, we find the peer with the earliest retry after | ||
| if nextClosest.IsZero() || p.retryAfter.Before(nextClosest) { | ||
| nextClosest = p.retryAfter | ||
| } | ||
| // If retryAfter has not expired, return time to wait | ||
| if now.Before(p.retryAfter) { | ||
| return peer{}, false, time.Until(p.retryAfter) | ||
| } | ||
|
|
||
| if nextClosest.IsZero() { | ||
| return nil, 0 | ||
| } | ||
| // Update retryAfter and fix heap position | ||
| p.retryAfter = time.Now().Add(r.options.RetryAfterDuration) | ||
| heap.Fix(&r.peerHeap, p.index) | ||
|
|
||
| // return the time to wait until the closest retry after | ||
| return nil, time.Until(nextClosest) | ||
| // Return a copy so callers can read fields without holding the lock. | ||
| return *p, true, 0 | ||
| } | ||
|
|
||
| // Connected adds a new peer to the queue for testing reachability. | ||
| // If the peer already exists, its address is updated. | ||
| func (r *reacher) Connected(overlay swarm.Address, addr ma.Multiaddr) { | ||
| r.mu.Lock() | ||
| defer r.mu.Unlock() | ||
|
|
||
| if _, ok := r.peers[overlay.ByteString()]; !ok { | ||
| r.peers[overlay.ByteString()] = &peer{overlay: overlay, addr: addr} | ||
| key := overlay.ByteString() | ||
| if existing, ok := r.peerIndex[key]; ok { | ||
| existing.addr = addr // Update address for reconnecting peer | ||
| existing.retryAfter = time.Time{} // Reset to trigger immediate re-ping | ||
| heap.Fix(&r.peerHeap, existing.index) | ||
| } else { | ||
| p := &peer{overlay: overlay, addr: addr} | ||
| r.peerIndex[key] = p | ||
| heap.Push(&r.peerHeap, p) | ||
| } | ||
|
|
||
| select { | ||
|
|
@@ -198,7 +233,11 @@ func (r *reacher) Disconnected(overlay swarm.Address) { | |
| r.mu.Lock() | ||
| defer r.mu.Unlock() | ||
|
|
||
| delete(r.peers, overlay.ByteString()) | ||
| key := overlay.ByteString() | ||
| if p, ok := r.peerIndex[key]; ok { | ||
| heap.Remove(&r.peerHeap, p.index) | ||
| delete(r.peerIndex, key) | ||
| } | ||
| } | ||
|
|
||
| // Close stops the worker. Must be called once. | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
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 would move the heap into a separate file