Skip to content

feat(pkg/p2p): reconnect on disconnected peers#3212

Draft
julienrbrt wants to merge 3 commits intomainfrom
julien/p2p
Draft

feat(pkg/p2p): reconnect on disconnected peers#3212
julienrbrt wants to merge 3 commits intomainfrom
julien/p2p

Conversation

@julienrbrt
Copy link
Copy Markdown
Member

Attempt to reconnect on peers when having connectivity issue.
PR made by AI. Still testing.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 30, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fc7c8673-c526-4e3c-88e9-c88192068822

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch julien/p2p

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 30, 2026

The latest Buf updates on your PR. Results from workflow CI / buf-check (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed⏩ skipped✅ passed✅ passedMar 30, 2026, 6:56 PM

@claude
Copy link
Copy Markdown
Contributor

claude bot commented Mar 30, 2026

Claude finished @julienrbrt's task in 2m 18s —— View job


Code Review: feat(pkg/p2p): reconnect on disconnected peers

  • Gather context
  • Read changed files
  • Analyze code quality, concurrency, error handling, and resilience
  • Post review feedback

Overview

The PR adds two features to pkg/p2p/client.go: reactive reconnection to seed peers on disconnect (via network.Notifee) and periodic peer-discovery refresh. Additionally, two unrelated cosmetic changes are made to the AWS/GCP KMS signers.

Overall the approach is solid and idiomatic for libp2p. A few correctness concerns and missing tests are worth addressing before merge.


Issues

1. Untracked reconnect goroutine — potential resource leak on close

pkg/p2p/client.go:366

go func(info peer.AddrInfo) {
    if err := c.host.Connect(ctx, info); err != nil && ctx.Err() == nil {
        c.logger.Warn().Str("peer", info.ID.String()).Err(err).Msg("failed to reconnect to seed peer")
    }
}(sp)

This goroutine is not tracked by maintenanceWg, so Close() returns (and c.host gets closed) while a Connect call may still be in-flight. The goroutine will eventually drain because ctx is cancelled, but it can race against host teardown and log a spurious warning after shutdown. Either track it with the WaitGroup or use a separate semaphore.

Fix this →


2. refreshPeerDiscovery blocks the maintenance goroutine while draining peerCh

pkg/p2p/client.go:390-409

for p := range peerCh {
    ...
    select {
    case c.connectSem <- struct{}{}:
        go func(peer peer.AddrInfo) { ... }(p)
    case <-ctx.Done():
        return
    }
}

The for p := range peerCh loop runs entirely in the maintenance goroutine. While it's blocked waiting for semaphore slots, incoming reconnectCh messages are not being processed. With peerLimit = 60 peers and connectWorkers = 16, the loop can sit in the select up to 44 times. This isn't a correctness bug (the buffer of 32 on reconnectCh absorbs bursts) but it does mean reconnect latency spikes to up to the full discovery timeout during a refresh cycle. Consider running refreshPeerDiscovery in its own goroutine (tracked by the WG) so the main loop stays responsive.

Fix this →


3. Backoff reset threshold reuses maxReconnectCooldown

pkg/p2p/client.go:343

if time.Since(st.lastAttempt) > maxReconnectCooldown {
    st.attempts = 0
}

The threshold for resetting the attempt counter is the same as the cap on backoff (5 minutes). This means a peer that sits at max backoff and then disconnects again will always immediately get attempts = 0 reset and restart at the base 5-second delay. This may be intentional (treat long gaps as "fresh"), but it could also mask persistent flapping peers. Consider using a separate, longer constant (e.g. 2× maxReconnectCooldown) for the reset threshold, or documenting the intentional behaviour.


4. attempts incremented before success is confirmed

pkg/p2p/client.go:363-373

st.attempts++
c.logger.Info().Str("peer", sp.ID.String()).Msg("reconnecting to disconnected seed peer")
go func(info peer.AddrInfo) {
    if err := c.host.Connect(ctx, info); err != nil && ctx.Err() == nil {
        c.logger.Warn()...
    }
}(sp)

The counter is incremented before knowing whether the attempt succeeded. If the next Disconnected event arrives quickly (before the goroutine finishes), isConnected will return false, causing attempts to be incremented again before the goroutine even reports a result. This leads to faster-than-intended backoff escalation in the race window. This is minor but worth noting.


5. Missing tests for the new reconnect machinery

Codecov reports 40% patch coverage with 38 lines missing in pkg/p2p/client.go. None of the following are tested:

  • disconnectNotifee.Disconnected (channel send, drop-when-full, non-seed peer ignored)
  • The exponential backoff / cooldown logic in startConnectionMaintenance
  • refreshPeerDiscovery (semaphore, isConnected early exit)
  • isConnected

These are the most complex and failure-prone parts of the change. At minimum, unit tests for the backoff state machine and the notifee filter would prevent regressions. The existing mocknet-based test harness in utils_test.go should make integration tests straightforward too.


6. Unrelated changes in signer packages

pkg/signer/aws/signer.go:162 and pkg/signer/gcp/signer.go:192 swap:

for attempt := 0; attempt < maxAttempts; attempt++ {

for attempt := range maxAttempts {

These are functionally equivalent (Go 1.22+ range-over-int) but are entirely unrelated to P2P reconnect. They would be cleaner in a separate commit or PR.


Positives

  • The use of network.Notifee is the correct libp2p API for disconnect events — reactive and low-overhead.
  • The exponential backoff pattern with a cap (min(attempts, 6) → max 64× base, then hard cap) is sound.
  • connectSem correctly bounds concurrent outbound connection attempts.
  • Close() properly cancels the maintenance context and calls maintenanceWg.Wait() — no goroutine leak from the main loop.
  • newDisconnectNotifee is correctly registered after c.started = true (no race with uninitialized state).
  • The nil-guard on reconnectCh in Disconnected handles the pre-start case cleanly.
  • isConnected early-exit in the maintenance loop avoids redundant reconnects when the peer recovers on its own.

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 30, 2026

Codecov Report

❌ Patch coverage is 33.70787% with 59 lines in your changes missing coverage. Please review.
✅ Project coverage is 61.21%. Comparing base (8d68f9d) to head (6069213).

Files with missing lines Patch % Lines
pkg/p2p/client.go 32.18% 57 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3212      +/-   ##
==========================================
- Coverage   61.41%   61.21%   -0.21%     
==========================================
  Files         120      120              
  Lines       12474    12561      +87     
==========================================
+ Hits         7661     7689      +28     
- Misses       3953     4010      +57     
- Partials      860      862       +2     
Flag Coverage Δ
combined 61.21% <33.70%> (-0.21%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant