fix(macros): Mutable program borrows can survive async dispatch#1365
Merged
Conversation
Contributor
There was a problem hiding this comment.
Code Review
This pull request modifies the #[program] macro and associated examples to ensure service constructors use shared references (&self) instead of mutable ones, mitigating risks of illegal borrows across asynchronous boundaries. The changes include macro logic updates to emit compile-time errors for &mut self constructors, refactoring demo applications to utilize interior mutability via Cell and RefCell, and adding documentation warnings. Feedback was provided to correct a technical error in the documentation regarding the appropriate RefCell method for obtaining a mutable guard.
osipov-mit
approved these changes
May 21, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Resolves:
Generated programs can now services borrowing program state mutably, and the generated async dispatch may hold those mutable borrows across
.awaitwhile future messages can obtain new mutable borrows from the samestatic mut PROGRAM.Sails stores the program instance in a generated
static mut PROGRAM. The generated dispatcher then constructs a service from that mutable program reference and awaitsservice.try_handle(...). Service dispatch also awaits async handlers. A service factory such asfn accounts(&mut self) -> AccountsService<'_>can return a service holding&mutreferences into the program. If an async handler on that service awaits a reply or other future, the future can retain the mutable reference while the Gear/Sails async message loop later processes another message and obtains another&mut PROGRAM. This violates Rust's exclusive aliasing assumptions for&mutreferences and can also create application-level reentrancy/state races, for example two withdrawals using stale state around an await. The issue is introduced by the&mut selfservice exposure support and the corresponding generated mutable borrow of the global program instance.