refactor(network): turn the AddressBook/CandidateSet into tower services - #11218
refactor(network): turn the AddressBook/CandidateSet into tower services#11218oxarbitrage wants to merge 3 commits into
Conversation
And two more auto-invalidated findings. Analyzed five files, diff |
| struct AddressBookClient { | ||
| /// The channel to the address book updater task. | ||
| request_tx: mpsc::Sender<AddressBookCall>, | ||
| } |
There was a problem hiding this comment.
the tower service should hold address book directly:
struct AddressBookService { address_book: AddressBook }
perhaps we should also make sure AddressBook is cheap to clone and use a watch channel:
struct AddressBookService { address_book: AddressBook, address_book_rx: WatchReceiver<AddressBook>, address_book_tx }
I was also just working on converting the address book to a service as part of the v2 p2p impl.
There was a problem hiding this comment.
Done in 0173b534a — the service now holds the address book and serves requests directly; the forwarding client is gone. Named AddressBookHandler since the alias owns AddressBookService.
Left out for now:
- The updater task still drains
change_channel's fire-and-forget mpsc. Collapsing it needshandshake/tests/vectors.rsconverted to a mock service, so I kept it out of this PR. - The watch channel —
AddressBookisn't cheap to clone yet, so that wants its own change.
|
Both addressed. F-186231 — correct, and the arithmetic checked out. F-186233 — resolved by the full series rather than disputed. The finding depends on the crawler holding It also doesn't bite quantitatively: there are no per-message change events (the collector only fires on the error arm), so at 200 connections the steady rate is around 15 events/sec, and a maximal 200-event burst drains in 5-20 ms against a 100 ms |
Implements the plan approved in #1976 (see this comment), as one PR with three staged commits rather than three stacked PRs. Each commit compiles and passes the
zebra-networksuite on its own, so the series is bisect-clean.Please review commit by commit — each maps to one stage of the approved plan.
The commits
1.
promote AddressBookUpdater to a Buffer-wrapped AddressBookService— the address book is now held by aBuffer-wrapped tower service that serves requests directly, the same idiom asPeerSet.NextReconnectPeerpicks the next candidate and marks itAttemptPendingin a single request, fixing the ordering TODO atcandidate_set.rs:414by construction. Writes are serialised by the address book mutex, shared with the fire-and-forget change path. Hot reads (getpeerinfo, the inboundGetAddrcache) stay on the existing shared handle.init()'s signature is unchanged, so no other crate is affected. Rate-limit timers stay exactly where they were: zero network-visible behavior change.2.
replace CandidateSet manual rate-limit timers with a rate-limit layer— pacing moves out ofmin_next_handshake/min_next_crawland into two middlewares inpeer_set/candidate_set/rate_limit.rs. The intervals themselves are unchanged. This is the only commit with network-visible behavior in scope, and the field-revert target if one is ever needed. Equivalence notes below.3.
dissolve CandidateSet into crawl_and_dial— the struct and itsArc<futures::lock::Mutex<..>>are gone;crawl_and_dialclones the service handles into its spawned tasks. Rate limits are shared across clones, so pacing is identical without the lock.Rate-limit equivalence (commit 2)
RateLimitOnYieldpacesNextReconnectPeer: every call is forwarded immediately, and only responses that actually yield a candidate reserve a pacing slot and sleep. Reservation is atomic, so the clones introduced in commit 3 still yield at least one interval apart. After sleeping, the shared timer is recharged from the actual wake time — exactly like the replaced timer — so scheduler latency never accumulates as a pacing deficit.SkipRateLimitwraps the newCrawlFanoutservice (the formerupdate_fanout+PEER_GET_ADDR_TIMEOUTlogic). While rate-limited, calls are skipped and returnNonewithout contacting any peers. The limit is claimed at call start (so concurrent crawls are skipped) and recharged when a crawl completes, matching the replaced timer's charge-at-completion semantics.Noneimmediately, without consuming rate-limit budget.Changes from review
Both are amended into commit 1, so the series stays bisect-clean.
change_channel's fire-and-forget mpsc; collapsing that too needshandshake/tests/vectors.rsconverted to a mock service, so it is left as a follow-up.update()used to trigger a refresh, and a refresh walks the address book several times, so one peer-suppliedAddrbatch could hold the mutex across roughly 925k entry visits with no yield — on an async task, that stalls a runtime thread.Extendnow applies its changes and refreshes once.Deviations from the approved plan
spawn_blocking: a permanently-running blocking task inhibits tokio's paused-clock auto-advance, stalling everytime::pause()test in the workspace.Arc<Mutex<AddressBook>>is retained as the hot-read handle described above.Testing
The full
zebra-networksuite passes at every commit boundary. New tests: N concurrentNextReconnectPeerrequests never return the same peer, and a vector test covering every request variant.The three
listener_*zcashd_compat*/listener_bans_*failures on a stock macOS dev machine are environmental (no127.0.0.xloopback aliases configured); they fail identically on unmodifiedmainand pass on Linux CI.CI note
The coverage that matters for this PR is the Zebra full sync — it exercises handshaking, peer acquisition, and connection pacing against the real network, which is exactly what these commits touch. A full-sync run against this branch is the integration signal I'd like this PR judged on, and I'll post the result here.
Note that "Integration Tests on GCP" has been red on
mainsince 2026-07-28 for unrelated reasons on the lightwalletd jobs (#10636), and none of its jobs are required checks (the required set islint,test-crates,unit-tests,pr-gate-result). A red overall status here is not by itself evidence of a regression in this PR.Please land it early in a release cycle so it soaks on
main.Closes #1976.