refactor(featureset): thread &'static FeatureSet instead of Arc - #652
Conversation
FeatureSet is resolved exactly once (after the cluster lock's fork version feeds the gnosis hotfix) and never mutated, so it lives for the whole process. Leak it once at the resolution point and thread a Copy &'static FeatureSet everywhere it previously flowed as Arc<FeatureSet>: - node/mod.rs leaks once via Box::leak; drops all Arc::clones threading it into the consensus controller, p2p behaviour, and core workflow. - Fields on ConsensusController::Config, qbft::Consensus (+ its Config), TrackerService, InclusionCore, InclusionChecker, WireInputs, and WireP2PParams lose the Arc. - get_round_timer_func and the three round-timer with_duty constructors take &'static; their new()/Default no longer allocate a throwaway FeatureSet::new() — the field is now Option<&'static FeatureSet> (None until bound to a duty, which is the only reader). - Collapses the second &FeatureSet convention in tracker/analysis.rs and infosync onto &'static, so there is a single convention. - tracker_feature_set (wire.rs) leaks its own small masked static instead of deep-cloning into a second Arc. - Rewrites the featureset crate module doc to mandate &'static. - Tests construct and leak their own set per test (Box::leak), equally cheap. Closes #616 Co-Authored-By: Bohdan Ohorodnii <35969035+varex83@users.noreply.github.com>
|
Could you please fix the conflicts and merge main? |
|
# Conflicts: # crates/core/src/tracker/inclusion.rs
rustdoc rejects intra-doc links to the private report_missed and report_att_inclusion functions from the public InclusionCore::new docs under -D warnings. Use plain code spans instead. Co-Authored-By: Bohdan Ohorodnii <varex83@users.noreply.github.com>
emlautarom1
left a comment
There was a problem hiding this comment.
LGTM, no semantic changes. Before merging check if we can remove explicit 'static annotations (run your agent against the diff and make it check).
| fn default_feature_set() -> &'static FeatureSet { | ||
| Box::leak(Box::new(FeatureSet::new())) |
There was a problem hiding this comment.
In general, the're are multiple places where we can drop the specific 'static lifetime annotation. This is one case, and by dropping it we can remove the need for this function altogether. Check for all signatures with 'static and see if we can drop it.
There was a problem hiding this comment.
Done in a932bf7. I let the compiler decide: stripped 'static from every &FeatureSet occurrence in the workspace, then added it back one error at a time until it compiled. What the borrow checker actually requires is only struct fields, the constructors that feed them, and the timer closures get_round_timer_func builds.
Dropped from: the eight tracker::analysis functions (including this one), infosync::Component::new, and wire::tracker_feature_set (now elided, tying the derived set's lifetime to the input). A &'static FeatureSet coerces on the way in, so no caller changed.
And yes — default_feature_set() is gone; the tests borrow a local &FeatureSet::new(). I also recorded the narrowed rule in the featureset module doc so it doesn't drift back: spell 'static out only where the reference is stored.
| feature_set: Arc<FeatureSet>, | ||
| /// `None` until bound to a duty; the round-one proposal-timeout override is | ||
| /// the only thing that reads it, and it never fires without a duty. | ||
| feature_set: Option<&'static FeatureSet>, |
There was a problem hiding this comment.
We could try to merge this field with the duty since they're both either None or Some at the same time. Not major, but removes the need for comments and an implicit dependency.
There was a problem hiding this comment.
Merged in a932bf7. All three timers now hold a single duty: Option<BoundDuty>:
struct BoundDuty {
duty: Duty,
feature_set: &'static FeatureSet,
}The implicit dependency and both explanatory comments are gone, and proposal_timeout_duration drops a parameter along with its is_some_and dance:
fn proposal_timeout_duration(bound: Option<&BoundDuty>, round: i64) -> Option<Duration> {
let bound = bound?;
if round == 1 && is_proposer(&bound.duty) && bound.feature_set.enabled(Feature::ProposalTimeout)Review follow-up on #652. - Drop `'static` from every `&FeatureSet` signature that only reads the set during the call: the eight `tracker::analysis` functions, `infosync::Component::new`, and `wire::tracker_feature_set` (its lifetime is now elided, tying the derived set to the input). A `&'static FeatureSet` coerces on the way in, so callers are unchanged. The remaining annotations are exactly the ones the borrow checker requires: struct fields, the constructors that feed them, and the timer closures built by `get_round_timer_func`. - The `analysis` tests no longer need a leaking `default_feature_set()` helper — they borrow a local `&FeatureSet::new()`. - Merge each round timer's `feature_set: Option<&'static FeatureSet>` into its `duty` field as a single `Option<BoundDuty>`. Both were `None` or `Some` together, so the implicit dependency (and the comments explaining it) is gone, and `proposal_timeout_duration` loses a parameter plus its `is_some_and` dance. - Document the narrowed convention in the `featureset` module doc. Co-authored-by: varex83 <bohdan.ohorodnii@nethermind.io>
Closes #616
FeatureSetis resolved exactly once — after the cluster lock's fork version feeds the gnosis hotfix — and never mutated, so it lives for the whole process. This replaces theArc<FeatureSet>threading with aCopy&'static FeatureSetobtained by leaking once at the resolution point.Changes
node/mod.rs):let feature_set: &'static FeatureSet = Box::leak(Box::new(resolve_feature_set(...)?));. All threeArc::clones threading it into the consensus controller, p2p behaviour, and core workflow are dropped (&'staticisCopy).Arc:ConsensusController::Config,qbft::Consensus+ itsConfig,TrackerService,InclusionCore,InclusionChecker,WireInputs,WireP2PParams.get_round_timer_funcand the threewith_dutyconstructors take&'static FeatureSet. Thenew()/Defaultpaths no longer allocate a throwawayFeatureSet::new()— the field is nowOption<&'static FeatureSet>(Noneuntil bound to a duty, which is the only reader via the round-one proposal-timeout override).&FeatureSetconvention intracker/analysis.rs(9 signatures) andinfosyncis collapsed onto&'static FeatureSet.tracker_feature_set(wire.rs) leaks its own small masked static instead of deep-cloning the whole set into a secondArc.featuresetcrate module doc now mandates the&'staticconvention (leak once at startup).Box::leak(Box::new(...))), equally cheap per the issue.Acceptance
Arc<FeatureSet>in the workspace:FeatureSet::new()throwaway allocations for filling fields (timers useOption<&'static>=None).&'static FeatureSet).#[allow(clippy::too_many_arguments)]onTracker::startis retained: even withoutfeature_set,starthas 8 parameters, so clippy still requires it (clippy-D warningspasses with it in place).Verification (all pass)
cargo build --workspace --all-featurescargo +nightly fmt --allcargo clippy --workspace --all-targets --all-features -- -D warningscargo testfor touched crates (pluto-featureset,pluto-consensus,pluto-core,pluto-infosync,pluto-app) — all green, including thewiring.rsTier-1 tests.🤖 Generated with Claude Code