-
Notifications
You must be signed in to change notification settings - Fork 44
Add SyncCell type to support Sync fix in embassy upstream #365
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
JamesHuard
merged 6 commits into
OpenDevicePartnership:main
from
JamesHuard:jimi/sync-intrusive-list
Jun 23, 2025
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
caa059d
Add SyncCell type to support Sync fix in embassy upstream
JamesHuard 2a03448
Align with Cell APIs
JamesHuard 04d5458
Add Send impl
JamesHuard 4e37fc3
Update embedded-service/src/sync_cell.rs
JamesHuard f82bdd1
Cleanup APIs and documentation
JamesHuard 508baac
Adjust safety on as_ptr
JamesHuard File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,201 @@ | ||
| //! # SyncCell: a cell-like API for static interior mutability scenarios. Backed by a critical section, implying it's usage may delay or defer interrupts. Recommended to use sparingly. | ||
| use core::cell::Cell; | ||
|
|
||
| /// A critical section backed Cell for sync scenarios where you want Cell behaviors, but need it to be thread safe (such as used in statics). Backed by a critical section, use sparingly. | ||
| pub struct SyncCell<T: ?Sized> { | ||
| inner: Cell<T>, | ||
| } | ||
|
|
||
| impl<T> SyncCell<T> { | ||
| /// Constructs a SyncCell, initializing it with initial_value | ||
| pub const fn new(initial_value: T) -> Self { | ||
| Self { | ||
| inner: Cell::new(initial_value), | ||
| } | ||
| } | ||
|
|
||
| /// Sets the cell's content in a critical section. Note that this accounts | ||
| /// for read/write conditions but does not automatically handle logical data | ||
| /// race conditions. It is still possible for a user to read a value but have | ||
| /// it change after they've performed the read. This just ensures data integrity: | ||
| /// SyncCell<T> will always contain a valid T, even if it's been read "late" | ||
| pub fn set(&self, value: T) { | ||
| critical_section::with(|_cs| self.inner.set(value)) | ||
| } | ||
|
|
||
| /// Swap contents between two SyncCell's | ||
| /// # Panics | ||
| /// | ||
| /// This function will panic if `self` and `other` are different `Cell`s that partially overlap. | ||
| /// (Using just standard library methods, it is impossible to create such partially overlapping `Cell`s. | ||
| /// However, unsafe code is allowed to e.g. create two `&Cell<[i32; 2]>` that partially overlap.) | ||
| pub fn swap(&self, other: &Self) { | ||
| critical_section::with(|_cs| self.inner.swap(&other.inner)); | ||
| } | ||
|
|
||
| /// consume the Synccell and return the inner value T | ||
| pub fn into_inner(self) -> T { | ||
| self.inner.into_inner() | ||
| } | ||
| } | ||
|
|
||
| impl<T: Copy> SyncCell<T> { | ||
| /// Reads the cell's content (in a critical section) and returns a copy | ||
| pub fn get(&self) -> T { | ||
| critical_section::with(|_cs| self.inner.get()) | ||
| } | ||
| } | ||
|
|
||
| impl<T: ?Sized> SyncCell<T> { | ||
| /// Return an address to the backing type | ||
| /// Unsafe: allows reads and writes without critical section guard, violating Sync guarantees. | ||
JamesHuard marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| /// # Safety | ||
| /// This may be used safely if and only if the pointer is held during a critical section, or | ||
| /// all accessors to this Cell are blocked until the pointer is released. | ||
| pub const fn as_ptr(&self) -> *mut T { | ||
| self.inner.as_ptr() | ||
| } | ||
| } | ||
|
|
||
| impl<T: Default> SyncCell<T> { | ||
| /// consume the inner T, returning its value and replacing it with default() | ||
| pub fn take(&self) -> T { | ||
| critical_section::with(|_cs| self.inner.take()) | ||
| } | ||
| } | ||
|
|
||
| // SAFETY: Sync is implemented here for SyncCell as T is only accessed via nestable critical sections | ||
| unsafe impl<T> Sync for SyncCell<T> {} | ||
|
|
||
| // SAFETY: Can implement send here due to critical section without T being explicitly Send | ||
| unsafe impl<T> Send for SyncCell<T> where T: Send {} | ||
|
|
||
| impl<T: Copy> Clone for SyncCell<T> { | ||
| #[inline] | ||
| fn clone(&self) -> SyncCell<T> { | ||
| SyncCell::new(self.get()) | ||
| } | ||
| } | ||
|
|
||
| impl<T: Default> Default for SyncCell<T> { | ||
| /// Creates a `Cell<T>`, with the `Default` value for T. | ||
| #[inline] | ||
| fn default() -> SyncCell<T> { | ||
| SyncCell::new(Default::default()) | ||
| } | ||
| } | ||
|
|
||
| impl<T: PartialOrd + Copy> PartialOrd for SyncCell<T> { | ||
| #[inline] | ||
| fn partial_cmp(&self, other: &SyncCell<T>) -> Option<core::cmp::Ordering> { | ||
| self.get().partial_cmp(&other.get()) | ||
| } | ||
|
|
||
| #[inline] | ||
| fn lt(&self, other: &SyncCell<T>) -> bool { | ||
| self.get() < other.get() | ||
| } | ||
|
|
||
| #[inline] | ||
| fn le(&self, other: &SyncCell<T>) -> bool { | ||
| self.get() <= other.get() | ||
| } | ||
|
|
||
| #[inline] | ||
| fn gt(&self, other: &SyncCell<T>) -> bool { | ||
| self.get() > other.get() | ||
| } | ||
|
|
||
| #[inline] | ||
| fn ge(&self, other: &SyncCell<T>) -> bool { | ||
| self.get() >= other.get() | ||
| } | ||
| } | ||
|
|
||
| impl<T: PartialEq + Copy> PartialEq for SyncCell<T> { | ||
| #[inline] | ||
| fn eq(&self, other: &SyncCell<T>) -> bool { | ||
| self.get() == other.get() | ||
| } | ||
| } | ||
|
|
||
| impl<T: Eq + Copy> Eq for SyncCell<T> {} | ||
|
|
||
| impl<T: Ord + Copy> Ord for SyncCell<T> { | ||
| #[inline] | ||
| fn cmp(&self, other: &SyncCell<T>) -> core::cmp::Ordering { | ||
| self.get().cmp(&other.get()) | ||
| } | ||
| } | ||
|
|
||
| impl<T> From<T> for SyncCell<T> { | ||
| /// Creates a new `SyncCell<T>` containing the given value. | ||
| fn from(t: T) -> SyncCell<T> { | ||
| SyncCell::new(t) | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
|
|
||
| #[test] | ||
| fn test_empty() { | ||
| let sc = SyncCell::<()>::new(()); | ||
|
|
||
| assert_eq!(sc.get(), ()); | ||
| sc.set(()); | ||
| assert_eq!(sc.get(), ()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_primitive() { | ||
| let sc = SyncCell::new(0usize); | ||
|
|
||
| assert_eq!(sc.get(), 0); | ||
| sc.set(1); | ||
| assert_eq!(sc.get(), 1); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_struct() { | ||
| #[derive(Copy, Clone, PartialEq, Debug)] | ||
| struct Example { | ||
| a: u32, | ||
| b: u32, | ||
| } | ||
|
|
||
| let sc = SyncCell::new(Example { a: 0, b: 0 }); | ||
|
|
||
| assert_eq!(sc.get(), Example { a: 0, b: 0 }); | ||
| sc.set(Example { a: 1, b: 2 }); | ||
| assert_eq!(sc.get(), Example { a: 1, b: 2 }); | ||
| } | ||
|
|
||
| #[tokio::test] | ||
| async fn test_across_threads() { | ||
| static SC: SyncCell<bool> = SyncCell::new(false); | ||
| let scr = &SC; | ||
|
|
||
| let poller = tokio::spawn(async { | ||
| loop { | ||
| if scr.get() { | ||
| break; | ||
| } else { | ||
| let _ = tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; | ||
| } | ||
| } | ||
| }); | ||
|
|
||
| let updater = tokio::spawn(async { | ||
| let _ = tokio::time::sleep(tokio::time::Duration::from_millis(300)); | ||
| scr.set(true); | ||
| }); | ||
|
|
||
| let result = tokio::join!(poller, updater); | ||
| assert!(result.0.is_ok()); | ||
| assert!(result.1.is_ok()); | ||
|
|
||
| assert!(SC.get()); | ||
| } | ||
| } | ||
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.
Uh oh!
There was an error while loading. Please reload this page.