-
Notifications
You must be signed in to change notification settings - Fork 858
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
Mutex: Only track a single locked flag #3665
base: main
Are you sure you want to change the base?
Conversation
embassy-sync/src/mutex.rs
Outdated
|
||
fn lock(&self, waker: &core::task::Waker) -> bool { | ||
if self.locked.replace(true) { | ||
unsafe { (&mut *self.waker.get()).register(waker) }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is sound. register()
can drop or wake wakers, which will call waker methods through the vtable which is basically running arbitrary code. This code could call back into the same Mutex, causing two &mut
s to the same WakerRegistration to exist at the same time, which is UB.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add a !Sync version of WakerRegistration, that would only need &self
to register/wake?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it might be possible if it uses Cell<Option<Waker>>
, but it'd mean you'd have to constantly take out and put back the waker so it might be less efficient, not sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can look into it through a pointer unsafely I think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think so. for example to wake the waker you'd need a &Waker
pointing to the contents. The wake can run arbitrary code which can reentrantly call into the same WakerRegistration and replace the waker (with either &mut
or ptr::write
), which would be UB because you'd be mutating the data while a &Waker
pointing to that location exists.
The only way is to really ensure you never get any &
or &mut
pointing to the contents, which is what Cell does basically.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Uh, I might be dense here to not understand, but given the following impl, how can a reference exist while we're mutating the cell?
fn register(&self, w: &Waker) {
match self.inner_as_ref() { // may need to be unsafe but that's beside the question
Some(ref w2) if (w2.will_wake(w)) => return,
_ => {}
}
// clone the new waker and store it
if let Some(old_waker) = self.waker.replace(Some(w.clone())) {
old_waker.wake() // this may call reentrantly into `register`
}
}
Running the following example on an ESP32-S2 (just to reduce the instructions from the critical section) with the latest commit, I see a slight speedup, so I'd like to hammer on this until it becomes correct :) #![no_std]
#![no_main]
use embassy_executor::Spawner;
use esp_backtrace as _;
use esp_hal::{
prelude::*,
time::Duration,
timer::{systimer::SystemTimer, OneShotTimer},
};
use esp_println::println;
static mut COUNTER: u32 = 0;
const CLOCK: CpuClock = CpuClock::max();
const TEST_MILLIS: u64 = 500;
#[handler]
fn timer_handler() {
let c = unsafe { COUNTER } as u64;
let cpu_clock = CLOCK.hz() as u64;
let timer_ticks_per_second = SystemTimer::ticks_per_second();
let cpu_cycles_per_timer_ticks = cpu_clock / timer_ticks_per_second;
println!(
"Test OK, count={}, cycles={}/100",
c,
(100 * timer_ticks_per_second * cpu_cycles_per_timer_ticks * TEST_MILLIS / 1000) / c
);
loop {}
}
#[embassy_executor::task]
async fn task() {
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
let mutex = embassy_sync::mutex::Mutex::<CriticalSectionRawMutex, ()>::new(());
loop {
unsafe { COUNTER += 1 };
let _g = mutex.lock().await;
embassy_futures::yield_now().await;
}
}
#[esp_hal_embassy::main]
async fn main(spawner: Spawner) {
let config = esp_hal::Config::default().with_cpu_clock(CLOCK);
let peripherals = esp_hal::init(config);
let systimer = SystemTimer::new(peripherals.SYSTIMER);
esp_hal_embassy::init(systimer.alarm0);
println!("Embassy initialized!");
spawner.spawn(task()).unwrap();
println!("Starting test");
let mut timer = OneShotTimer::new(systimer.alarm1);
timer.set_interrupt_handler(timer_handler);
timer.enable_interrupt(true);
timer.schedule(Duration::millis(TEST_MILLIS)).unwrap();
} Before (1adee53): Test's binary size shrinks from |
No description provided.