Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(nns): Switch NNS Governance global state from static to thre…
…ad_local (#2844) # Why `governance()` and `governance_mut()` are bad Representing the canister global state with `thread_local!` avoids using unsafe blocks to access it. When using unsafe blocks to access it, one can easily write code with undefined behavior by retaining references across await boundary (more precisely, after an inter-canister call). # Why this PR The NNS Governance heavily depends on the accessing the global state as `static`, and there will be a lot of refactoring needed in order to get away with the current pattern. This PR is the first step towards getting rid of those bad access patterns - with this change, we can gradually migrate from using `governance()`/`governance_mut()` to using `GOVERNANCE.with()`. When all the usage of `governance()`/`governance_mut()` are replaced, we can delete them and declare victory. # What Define the global state with `thread_local!` (`LocalKey<RefCell<Governance>>`) while returning the raw pointer for the existing access pattern. # Why it is safe The `LocalKey<RefCell<T>>` is set once and only once during `post_upgrade` or `init`, so the pointer should remain constant, given that the canister code is single threaded. When accessing through `governance()` or `governance_mut()` one can still write code with undefined behavior, but it is the same danger as what we have now. # Why `Governance::new_uninitialized` This is needed in order for the `thread_local!` state to be `Governance` instead of `Option<Governance>`. We know the "uninitialized" version won't be used except for the code in the init/post_upgrade before the "initialized" state is set. However, there doesn't seem to be a good way to express that understanding. An alternative is to still use `Option<Governance>` and `unwrap()` every time, but it seems more cumbersome.
- Loading branch information