fix(node): use readiness for peer liveness - #248
Conversation
|
Warning Review limit reached
Next review available in: 54 minutes Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughPeer liveness checks now use DB-aware ChangesPeer readiness probing
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant GossipTask
participant ping_peer_readiness
participant PeerReadyEndpoint
participant PeerHealthEndpoint
participant PeerDatabase
GossipTask->>ping_peer_readiness: Probe peer URL
ping_peer_readiness->>PeerReadyEndpoint: GET /ready
PeerReadyEndpoint-->>ping_peer_readiness: Readiness response
ping_peer_readiness->>PeerHealthEndpoint: GET /health after 404
PeerHealthEndpoint-->>ping_peer_readiness: Legacy response
ping_peer_readiness-->>GossipTask: Ready boolean
GossipTask->>PeerDatabase: Persist after success or second failure
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
|
Thanks for the contribution. A couple of things will help us review this faster:
See CONTRIBUTING.md. Update the PR and these notes will clear automatically. |
|
For reviewer context, the This PR adds regression coverage inside the existing The triage workflow currently recognizes newly added literal Validation completed locally:
|
jatmn
left a comment
There was a problem hiding this comment.
I found issues that need to be addressed before this is ready.
Findings
-
[P1] Preserve compatibility with peers that do not implement
/ready
crates/gitlawb-node/src/main.rs:985
This unconditionally changes the peer wire contract from/healthto/ready. Released v0.5.0 nodes expose/healthbut not/ready, and #170 deliberately left the peer probe on/healthfor exactly that rolling-upgrade case. Their/readyresponse is therefore 404, which this helper maps tofalse; both the periodic job and the manual ping persist that value. Since federated repository listing filters onlast_ping_ok, a healthy v0.5.0 peer disappears from federation after the first probe. Please add a version/capability transition (for example, a 404-only legacy/healthfallback that still treats a real/ready503 as unready) and cover the mixed-version case, or provide the required coordinated rollout and compatibility window. -
[P2] Update the operator peer-endpoint requirement
docs/RUN-A-NODE.md:180
The operator guide currently says that serving/healthis sufficient because peers ping it. After this change peers instead require/ready, so an operator following the documented contract can be reported unreachable. Update the guidance to require/ready(and retain/healthonly for its liveness purpose). -
[P3] Correct the degraded-router peer-ping comment
crates/gitlawb-node/src/main.rs:735
This comment still says peer pings use a successful/healthresponse. The changed callers now use/ready, so the comment gives the wrong rationale for the degraded response and is likely to mislead future outage-routing work.
|
Addressed all three findings in
Validation:
|
jatmn
left a comment
There was a problem hiding this comment.
I found issues that need to be addressed before this is ready.
Findings
- [P2] Keep the legacy fallback within one peer-probe deadline
crates/gitlawb-node/src/main.rs:988
The shared client gives each request a 10-second timeout, but the new 404 fallback starts a second independently timed/healthrequest. A legacy (or attacker-controlled public) peer can therefore hold the serial gossip loop for nearly 20 seconds by delaying/readybefore returning 404 and delaying/health; the prior probe had a single 10-second budget. Since the peer table is unbounded and the loop updates one row at a time, enough such rows cause sweeps to overrun the five-minute interval and leave peer reachability stale. Apply one deadline to the complete/ready-then-/healthoperation (or pass the remaining budget to the fallback), and cover a delayed-404 case.
jatmn
left a comment
There was a problem hiding this comment.
@beardthelion LGTM, requires your manual review.
beardthelion
left a comment
There was a problem hiding this comment.
The readiness switch is the right call and all four findings from the earlier rounds landed properly. I confirmed the compatibility story by execution rather than by reading it: an axum router carrying only /health and no fallback, which is v0.5.0's shape, answers 404 for GET /ready, and the probe falls through to /health and returns ready. /ready first shipped in v0.5.1, so the peers that arm exists for really do return 404. A 403 and a 500 both fail closed without touching /health.
I also mutated each line inside ping_peer_readiness_with_timeout one at a time against the seven tests. Pointing the probe at /health, widening the fallback trigger past 404, giving the fallback its own timeout budget, letting the client follow redirects, flipping the catch-all to true, and deleting the 404 arm each turn at least one test red. That part is well covered. Four things below.
Findings
-
[P2] Bind the manual ping path to readiness with a test
crates/gitlawb-node/src/api/peers.rs:442
Reverting this line to the pre-PR inline/healthprobe leaves all seven tests green. Every line inside the helper reddens under mutation, so the harness works and this is a real hole: the handler's readiness behavior is unpinned, and a later edit can quietly returnGET /api/v1/peers/{did}/pingto liveness-only while CI stays green. A test that drives the handler (or at minimum asserts the call site's behavior) closes it. -
[P2] Do not let a single probe sample drop a peer from federation
crates/gitlawb-node/src/main.rs:948-949
The probe result is written tolast_ping_okunconditionally, andapi/repos.rs:1451filters the federated fan-out on that flag, so one failed sample removes a peer's repos from/api/v1/repos/federateduntil the next 300s tick. The single-sample write predates this PR; what changes is the set of states that flip it, since a peer whose database hiccups for two seconds now drops out where previously only an unreachable process did. Two consecutive failures before writing false, or one re-probe inside the existing budget, would fix it. Whichever shape you pick, the property to preserve is that a peer survives one transient failure and still drops out on a sustained one. -
[P3] Log the probe outcome
crates/gitlawb-node/src/main.rs:1000-1017
Five materially different exits collapse into one bool with notracingon any arm: ready, database-degraded 503, legacy 404 then healthy, refused redirect, and budget exhaustion. From the logs an operator cannot tell a peer whose database is down from one that is unreachable, which is the distinction this PR exists to draw. The bootstrap announce in the same function logs both its error and its timeout arm, so this is a gap against the file's own habit. The 503 arm and the legacy-404 downgrade are the two worth recording; the downgrade also gives you the list of peers still on the old build, which is what tells you when the fallback can be dropped. -
[P3] Reconsider the anonymous ping route writing the federation gate
crates/gitlawb-node/src/api/peers.rs:444, route wired atcrates/gitlawb-node/src/server.rs:292
peer_read_routescarries no auth layer and no per-IP brake, whilesync_trigger_routesandpeer_write_routesin the same file each carry one with a comment explaining that outbound fan-out needs it. This handler now drives a database-backed probe on the target and writes the flag that gates federation, from a single unauthenticated sample. Same lineage as the finding above, and the cheaper fix is probably to report the probe result without callingmark_peer_ping, leaving the flag owned solely by the gossip loop.
Not asks, just recording them
The probe URL is built by string concatenation, so a peer that announces https://host/?x=1 gets probed at / rather than /ready. I ran it: the /ready mock is never requested, / answers 200, and the peer reads ready even though /ready would have said 503. is_public_http_url validates scheme and host only, and upsert_peer stores the URL verbatim, so that shape survives announce. It is the same concatenation the pre-PR code used, and a hostile peer controls its own answer regardless, so it only really bites an honestly misconfigured operator. Url::join would be the tidy follow-up, separate from this PR.
README.md:276 and docs/OSS-READINESS-AUDIT.md:69 still list /health alone. Both predate this change and neither claims to state the peer contract, so I am not asking you to touch them here.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
crates/gitlawb-node/src/main.rs (1)
941-962: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win
failed_oncenever sheds entries for peers that disappear.A DID is removed from the set only on a readiness success. Peers pruned from the
peerstable (or permanently down) leave their DID behind for the process lifetime, and DIDs are announce-influenceable. Retaining against the current snapshot each tick keeps the set bounded.♻️ Prune the failure set against the current peer list
let peers = match state.db.list_peers().await { Ok(p) => p, Err(_) => continue, }; + // Drop bookkeeping for peers that no longer exist so the set + // cannot grow unbounded over the process lifetime. + let current: HashSet<&str> = peers.iter().map(|p| p.did.as_str()).collect(); + failed_once.retain(|did: &String| current.contains(did.as_str())); for peer in peers {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/gitlawb-node/src/main.rs` around lines 941 - 962, Update the peer polling loop around failed_once and the list_peers result to prune failure entries against the current peer snapshot on every tick. Retain only DIDs present in peers, so removed or permanently unavailable peers cannot accumulate in the HashSet while preserving the existing readiness update behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@crates/gitlawb-node/src/main.rs`:
- Around line 941-962: Update the peer polling loop around failed_once and the
list_peers result to prune failure entries against the current peer snapshot on
every tick. Retain only DIDs present in peers, so removed or permanently
unavailable peers cannot accumulate in the HashSet while preserving the existing
readiness update behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 80a159f2-3246-4a44-ac4e-42951e15b196
📒 Files selected for processing (2)
crates/gitlawb-node/src/api/peers.rscrates/gitlawb-node/src/main.rs
Summary
Use the database-aware
/readyendpoint for periodic and manual peer checks, while falling back to/healthonly when an older peer returns 404 for/ready. Periodic federation state now requires two consecutive failed samples before marking a peer unreachable; the anonymous manual-ping endpoint is read-only.Motivation & context
/healthintentionally reports process liveness and remains successful during a database outage. Using it for peer reachability lets a node continue treating a peer as healthy even though that peer cannot serve database-backed requests. A single transient readiness failure also should not remove a peer from federated repository results for the next five-minute interval.Closes #176
Kind of change
What changed
/healthto/ready, with a 404-only/healthfallback for nodes released before/readyexisted.How a reviewer can verify
DATABASE_URL=postgres://postgres:postgres@localhost:5432/gitlawb_test cargo test --workspace cargo fmt --all -- --check cargo clippy --workspace --all-targets -- -D warnings cargo build --release --workspaceThe focused regressions can also be run with:
Before you request review
cargo test --workspacepasses locallycargo fmt --allandcargo clippy --workspace --all-targets -- -D warningsare cleanfeat(...),fix(...),docs(...)).env.exampleupdated if behavior or config changed (N/A)Protocol & signing impact
N/A — this does not change identity, signatures, UCANs, ref certificates, or P2P wire formats.
Notes for reviewers
The repository's current MSRV lane fails before compilation because newly resolved AWS crates require Rust 1.91.1 while CI installs
1.91(currently 1.91.0). That existing manifest/lock/dependency drift is tracked in #242 and #243; stable tests, clippy, formatting, and the release build are clean for this change.Summary by CodeRabbit
/healthand/readyendpoints for public URLs.