fix(st): add errdefer chain for epoch cache functions - #227
Conversation
Signed-off-by: Chen Kai <281165273grape@gmail.com>
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly enhances the reliability of the Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request refactors createFromState to improve resource management during error scenarios by introducing helper functions that encapsulate resource initialization and cleanup using errdefer. This is a solid improvement for safety and directly addresses the PR's motivation. My review includes suggestions to further enhance maintainability by reducing code duplication and to align the new code with the repository's style guide concerning assertions and code formatting for resource management.
| var effective_balance_increments = try effectiveBalanceIncrementsInit(allocator, validator_count); | ||
| errdefer effective_balance_increments.deinit(); |
There was a problem hiding this comment.
The style guide recommends using newlines to visually group resource allocation and deallocation. Please add a blank line before the resource allocation on line 140 to adhere to this rule. This comment also applies to the other new helper functions in this file where resource allocation is the first statement.
References
- Use newlines to group resource allocation and deallocation, i.e. before the resource allocation and after the corresponding defer statement, to make leaks easier to spot. (link)
| fn initEffectiveBalanceIncrementsRc(allocator: Allocator, validator_count: usize) !*EffectiveBalanceIncrementsRc { | ||
| var effective_balance_increments = try effectiveBalanceIncrementsInit(allocator, validator_count); | ||
| errdefer effective_balance_increments.deinit(); | ||
|
|
||
| return try EffectiveBalanceIncrementsRc.init(allocator, effective_balance_increments); | ||
| } |
There was a problem hiding this comment.
The style guide requires asserting all function arguments. This function is missing assertions for its arguments, such as allocator and validator_count. Please add assertions to ensure correctness and adherence to the style guide. This also applies to the other new helper functions in this file.
References
- Assert all function arguments and return values, pre/postconditions and invariants. A function must not operate blindly on data it has not checked. (link)
| fn initCurrentSyncCommitteeCacheRc( | ||
| allocator: Allocator, | ||
| state: *AnyBeaconState, | ||
| pubkey_to_index: *const PubkeyIndexMap, | ||
| skip_sync_committee_cache: bool, | ||
| ) !*SyncCommitteeCacheRc { | ||
| var sync_committee_cache = blk: { | ||
| if (skip_sync_committee_cache) break :blk SyncCommitteeCacheAllForks.initEmpty(); | ||
| var sync_committee_view = try state.currentSyncCommittee(); | ||
| var sync_committee: types.altair.SyncCommittee.Type = undefined; | ||
| try sync_committee_view.toValue(allocator, &sync_committee); | ||
| break :blk try SyncCommitteeCacheAllForks.initSyncCommittee(allocator, &sync_committee, pubkey_to_index); | ||
| }; | ||
| errdefer sync_committee_cache.deinit(); | ||
|
|
||
| return try SyncCommitteeCacheRc.init(allocator, sync_committee_cache); | ||
| } | ||
|
|
||
| fn initNextSyncCommitteeCacheRc( | ||
| allocator: Allocator, | ||
| state: *AnyBeaconState, | ||
| pubkey_to_index: *const PubkeyIndexMap, | ||
| skip_sync_committee_cache: bool, | ||
| ) !*SyncCommitteeCacheRc { | ||
| var sync_committee_cache = blk: { | ||
| if (skip_sync_committee_cache) break :blk SyncCommitteeCacheAllForks.initEmpty(); | ||
| var sync_committee_view = try state.nextSyncCommittee(); | ||
| var sync_committee: types.altair.SyncCommittee.Type = undefined; | ||
| try sync_committee_view.toValue(allocator, &sync_committee); | ||
| break :blk try SyncCommitteeCacheAllForks.initSyncCommittee(allocator, &sync_committee, pubkey_to_index); | ||
| }; | ||
| errdefer sync_committee_cache.deinit(); | ||
|
|
||
| return try SyncCommitteeCacheRc.init(allocator, sync_committee_cache); | ||
| } |
There was a problem hiding this comment.
The functions initCurrentSyncCommitteeCacheRc and initNextSyncCommitteeCacheRc are nearly identical, with the only difference being the call to state.currentSyncCommittee() versus state.nextSyncCommittee(). To improve maintainability and reduce code duplication, consider refactoring this logic into a single helper function. This function could accept a function pointer to either currentSyncCommittee or nextSyncCommittee to handle both cases.
There was a problem hiding this comment.
Pull request overview
This PR improves error-path resource cleanup in EpochCache.createFromState by wrapping several intermediate allocations in reference-counted helpers and adding errdefer-based release chaining, so partially-initialized caches don’t leak when an error is thrown.
Changes:
- Added helper initializers to build
EffectiveBalanceIncrementsRc,EpochShufflingRc, andSyncCommitteeCacheRcwith consistenterrdefercleanup. - Updated
createFromStateto use the new RC helpers anderrdefer ...release()for safe unwinding on failure. - Simplified the final
EpochCachestruct initialization by directly storing the RC pointers created earlier.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ff33933ead
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Signed-off-by: Chen Kai <281165273grape@gmail.com>
Signed-off-by: Chen Kai <281165273grape@gmail.com>
spiral-ladder
left a comment
There was a problem hiding this comment.
This PR LGTM, but re-reading the implementation led me to a question in a comment
| @@ -214,19 +279,31 @@ pub const EpochCache = struct { | |||
| } | |||
|
|
|||
| // ownership of the active indices is transferred to EpochShuffling | |||
There was a problem hiding this comment.
Re-reading this comment and thinking about how memory ownership works here again, but isn't this inaccurate if we're copying the items of the ArrayList into the EpochShuffling? Shouldn't we just use toOwnedSlice() here to pass into initEpochShufflingRc and take ownership of the items?
There was a problem hiding this comment.
Re-reading the code, I feel you are right
There was a problem hiding this comment.
#229 fixing this here, but i think we can merge this current PR as is ![]()
there was a misleading comment saying that `EpochShuffling` took ownership of the `active_indices` even though the implementation copies. This PR fixes that
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| errdefer next_sync_committee_indexed.deinit(); | ||
|
|
||
| const next_sync_committee_indexed_rc = try SyncCommitteeCacheRc.init(self.allocator, next_sync_committee_indexed); | ||
| errdefer next_sync_committee_indexed_rc.release(); | ||
|
|
||
| var current_sync_committee_indexed = try SyncCommitteeCacheAllForks.initValidatorIndices(self.allocator, next_sync_committee_indices); | ||
| errdefer current_sync_committee_indexed.deinit(); | ||
|
|
||
| const current_sync_committee_indexed_rc = try SyncCommitteeCacheRc.init(self.allocator, current_sync_committee_indexed); |
There was a problem hiding this comment.
In setSyncCommitteesIndexed, next_sync_committee_indexed and current_sync_committee_indexed each have errdefer ...deinit() set before being wrapped into SyncCommitteeCacheRc. If a later try fails after SyncCommitteeCacheRc.init succeeds (e.g., allocating the second cache), both the value deinit() and the RC release() will run on the same underlying allocation (the union copy still points at the same *SyncCommitteeCacheAltair), causing a double-free. Consider moving the value->RC wrapping into a small helper (similar to initCurrentSyncCommitteeCacheRc) so ownership transfer and cleanup are handled in one place, or explicitly disarm the earlier errdefer after successful RC init (e.g., by resetting the union to initEmpty() before any subsequent fallible operations).
| errdefer next_sync_committee_indexed.deinit(); | |
| const next_sync_committee_indexed_rc = try SyncCommitteeCacheRc.init(self.allocator, next_sync_committee_indexed); | |
| errdefer next_sync_committee_indexed_rc.release(); | |
| var current_sync_committee_indexed = try SyncCommitteeCacheAllForks.initValidatorIndices(self.allocator, next_sync_committee_indices); | |
| errdefer current_sync_committee_indexed.deinit(); | |
| const current_sync_committee_indexed_rc = try SyncCommitteeCacheRc.init(self.allocator, current_sync_committee_indexed); | |
| const next_sync_committee_indexed_rc = SyncCommitteeCacheRc.init(self.allocator, next_sync_committee_indexed) catch |e| { | |
| next_sync_committee_indexed.deinit(); | |
| return e; | |
| }; | |
| errdefer next_sync_committee_indexed_rc.release(); | |
| var current_sync_committee_indexed = try SyncCommitteeCacheAllForks.initValidatorIndices(self.allocator, next_sync_committee_indices); | |
| const current_sync_committee_indexed_rc = SyncCommitteeCacheRc.init(self.allocator, current_sync_committee_indexed) catch |e| { | |
| current_sync_committee_indexed.deinit(); | |
| return e; | |
| }; |
Motivation
Make
createFromStaterelease resources correctly when error thrown