-
Notifications
You must be signed in to change notification settings - Fork 178
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
The llsc stack is not safe from ABA with how LL/SC is used #180
Comments
As a side not, I don't know which of the current sold Cortex-M based processors have that problem, neither do I know it there are any. So this is a specification related bug, but it might not be a bug in practice. The problem is it's hard to tell if it is or is not a problem in practice. |
No, it's absolutely a problem in practice. The load of next is outside of the llsc region, so it doesn't matter about the "might" in the spec. A preemption before that (or concurrent accesses in actual SMP hardware) is not going to trigger an abort. Taking advantage of LL/SC like this is just not possible with C11-based intrinsics - you need the LL(head) part of compare_exchange_weak before the load of next. This is doable in ASM, but not in the typical rust/C/etc intrinsics. The asm shown in the docs is clearly shows how this is incorrect, in order for it to be ok it would need to have reordered the
Meanwhile the warned-about x86-64 generation counter algorithm is safe in basically any realistic scenario. |
@japaric Do you know how to fix this? |
yes, I have written this stack before using
not a fan of the interrupt::free suggestion. if we are going to make the code device specific, I would look into writing the |
Hmm, what are you not a fan of? It will be a few instructions long (4?) and guaranteed execution time. I'm not seeing the problem you are seeing. |
the documentation states this a lock-free queue. adding a critical section breaks that guarantee and would be a breaking change in my opinion.
|
I think we disagree then, as long as you do not need mutable references it is lock free, as the pool works via immutable references. I'd also argue that using atomics here is over-engineered, as it will be an extremely small critical section which in worst case will give a few clocks of predictable latency in the system. |
Mutation via shared references ( To be clear, I'm not opposed to adding a general purpose fixed-capacity queue that works with mutable references but that's a different data structure. People can layer device-specific synchronization on top of that queue to get what you are describing, e.g. // crate: heapless
pub struct GeneralPurposeQueue<T> { /* .. */ }
impl GeneralPurposeQueue<T> {
pub fn enqueue(&mut self, item: T) -> Result<(), T> { /* .. */ }
pub fn dequeue(&mut self, item: T) -> Option<T> { /* .. */ }
}
// crate: cortex-m-queue
pub struct CortexMQueue<T>(interrupt::Mutex<RefCell<GeneralPurposeQueue<T>>>);
impl CortexMQueue<T> {
pub fn enqueue(&self, item: T) -> Result<(), T> {
interrupt::free(|cs| self.0.borrow(cs).borrow_mut().enqueue(item))
}
}
Bounded (fixed-capacity) lock-free SPSC queues based on atomics are a well-known data structure that date from ~80 (I think the original work is by Lamport). There's plenty of variants (bounded, unbounded, cache-friendly, etc.) by now but I would not call any of them "over enigneered"; they all have different trade offs. The other problem I see with a critical section based queue that has no atomics is that it's not multi-core safe, where as the lock-free queue here is. I see that your main line of argument is "bounded time execution". I can see the use case for that and I'm fully OK with a queue that has such property but like I said above that's a different data structure so we should not be modifying the existing |
I think we are discussing 2 different things, |
now that
I'll prepare a PR |
this changes the implementation of the underlying Treiber Stack to use LDREX and STREX on Cortex-v7A/R/M. other architectures are left unchanged in this PR. in principle, RISC-V could be implemented in a similar fashion but haven't tested that yet this does bump the MSRV so it's technically a breaking change ... to make this easier to maintain I'd like to drop the llsc.rs and make pool only available on targets where it implements Sync *and* it's sound but that's a API breaking change. there are other API breaking changes I'd like to make (e.g. remove the Uninit type state) so I'll write a RFC first fixes #180
But the load of
next
at8000136
is done outside of the LDREX...STREX sequence.So if the process is preempted between
8000136
and800013a
and during thepreemption we cause the ABA problem (e.g. given h->A->B->C->0, do pop A, pop B, push A resulting in h->A->C->0 with B being in use).
Now given Cortex-M implementations might not have that problem:
(quote from the ARM®v7-M ArchitectureReference Manual, linked in the sourcecode)
The problem is this is a might so depending on where you get your Cortex-M chip from this might or might not be a sound implementation.
https://github.com/japaric/heapless/blob/9ff3a5fa8968fb263cece456ccc9505dc913147e/src/pool/mod.rs#L123-L139
The text was updated successfully, but these errors were encountered: