Merge pull request #3644 from bugadani/lock_once
Only lock once to wake a task
This commit is contained in:
		
						commit
						47e96beff4
					
				@ -386,11 +386,11 @@ impl SyncExecutor {
 | 
				
			|||||||
    /// - `task` must be set up to run in this executor.
 | 
					    /// - `task` must be set up to run in this executor.
 | 
				
			||||||
    /// - `task` must NOT be already enqueued (in this executor or another one).
 | 
					    /// - `task` must NOT be already enqueued (in this executor or another one).
 | 
				
			||||||
    #[inline(always)]
 | 
					    #[inline(always)]
 | 
				
			||||||
    unsafe fn enqueue(&self, task: TaskRef) {
 | 
					    unsafe fn enqueue(&self, task: TaskRef, l: state::Token) {
 | 
				
			||||||
        #[cfg(feature = "trace")]
 | 
					        #[cfg(feature = "trace")]
 | 
				
			||||||
        trace::task_ready_begin(self, &task);
 | 
					        trace::task_ready_begin(self, &task);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self.run_queue.enqueue(task) {
 | 
					        if self.run_queue.enqueue(task, l) {
 | 
				
			||||||
            self.pender.pend();
 | 
					            self.pender.pend();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -401,7 +401,9 @@ impl SyncExecutor {
 | 
				
			|||||||
        #[cfg(feature = "trace")]
 | 
					        #[cfg(feature = "trace")]
 | 
				
			||||||
        trace::task_new(self, &task);
 | 
					        trace::task_new(self, &task);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.enqueue(task);
 | 
					        state::locked(|l| {
 | 
				
			||||||
 | 
					            self.enqueue(task, l);
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// # Safety
 | 
					    /// # Safety
 | 
				
			||||||
@ -544,13 +546,13 @@ impl Executor {
 | 
				
			|||||||
/// You can obtain a `TaskRef` from a `Waker` using [`task_from_waker`].
 | 
					/// You can obtain a `TaskRef` from a `Waker` using [`task_from_waker`].
 | 
				
			||||||
pub fn wake_task(task: TaskRef) {
 | 
					pub fn wake_task(task: TaskRef) {
 | 
				
			||||||
    let header = task.header();
 | 
					    let header = task.header();
 | 
				
			||||||
    if header.state.run_enqueue() {
 | 
					    header.state.run_enqueue(|l| {
 | 
				
			||||||
        // We have just marked the task as scheduled, so enqueue it.
 | 
					        // We have just marked the task as scheduled, so enqueue it.
 | 
				
			||||||
        unsafe {
 | 
					        unsafe {
 | 
				
			||||||
            let executor = header.executor.get().unwrap_unchecked();
 | 
					            let executor = header.executor.get().unwrap_unchecked();
 | 
				
			||||||
            executor.enqueue(task);
 | 
					            executor.enqueue(task, l);
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Wake a task by `TaskRef` without calling pend.
 | 
					/// Wake a task by `TaskRef` without calling pend.
 | 
				
			||||||
@ -558,11 +560,11 @@ pub fn wake_task(task: TaskRef) {
 | 
				
			|||||||
/// You can obtain a `TaskRef` from a `Waker` using [`task_from_waker`].
 | 
					/// You can obtain a `TaskRef` from a `Waker` using [`task_from_waker`].
 | 
				
			||||||
pub fn wake_task_no_pend(task: TaskRef) {
 | 
					pub fn wake_task_no_pend(task: TaskRef) {
 | 
				
			||||||
    let header = task.header();
 | 
					    let header = task.header();
 | 
				
			||||||
    if header.state.run_enqueue() {
 | 
					    header.state.run_enqueue(|l| {
 | 
				
			||||||
        // We have just marked the task as scheduled, so enqueue it.
 | 
					        // We have just marked the task as scheduled, so enqueue it.
 | 
				
			||||||
        unsafe {
 | 
					        unsafe {
 | 
				
			||||||
            let executor = header.executor.get().unwrap_unchecked();
 | 
					            let executor = header.executor.get().unwrap_unchecked();
 | 
				
			||||||
            executor.run_queue.enqueue(task);
 | 
					            executor.run_queue.enqueue(task, l);
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -45,7 +45,7 @@ impl RunQueue {
 | 
				
			|||||||
    ///
 | 
					    ///
 | 
				
			||||||
    /// `item` must NOT be already enqueued in any queue.
 | 
					    /// `item` must NOT be already enqueued in any queue.
 | 
				
			||||||
    #[inline(always)]
 | 
					    #[inline(always)]
 | 
				
			||||||
    pub(crate) unsafe fn enqueue(&self, task: TaskRef) -> bool {
 | 
					    pub(crate) unsafe fn enqueue(&self, task: TaskRef, _: super::state::Token) -> bool {
 | 
				
			||||||
        let mut was_empty = false;
 | 
					        let mut was_empty = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.head
 | 
					        self.head
 | 
				
			||||||
 | 
				
			|||||||
@ -44,13 +44,11 @@ impl RunQueue {
 | 
				
			|||||||
    ///
 | 
					    ///
 | 
				
			||||||
    /// `item` must NOT be already enqueued in any queue.
 | 
					    /// `item` must NOT be already enqueued in any queue.
 | 
				
			||||||
    #[inline(always)]
 | 
					    #[inline(always)]
 | 
				
			||||||
    pub(crate) unsafe fn enqueue(&self, task: TaskRef) -> bool {
 | 
					    pub(crate) unsafe fn enqueue(&self, task: TaskRef, cs: CriticalSection<'_>) -> bool {
 | 
				
			||||||
        critical_section::with(|cs| {
 | 
					 | 
				
			||||||
        let prev = self.head.borrow(cs).replace(Some(task));
 | 
					        let prev = self.head.borrow(cs).replace(Some(task));
 | 
				
			||||||
        task.header().run_queue_item.next.borrow(cs).set(prev);
 | 
					        task.header().run_queue_item.next.borrow(cs).set(prev);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        prev.is_none()
 | 
					        prev.is_none()
 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Empty the queue, then call `on_task` for each task that was in the queue.
 | 
					    /// Empty the queue, then call `on_task` for each task that was in the queue.
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,15 @@ use core::sync::atomic::{AtomicU32, Ordering};
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
use super::timer_queue::TimerEnqueueOperation;
 | 
					use super::timer_queue::TimerEnqueueOperation;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub(crate) struct Token(());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Creates a token and passes it to the closure.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// This is a no-op replacement for `CriticalSection::with` because we don't need any locking.
 | 
				
			||||||
 | 
					pub(crate) fn locked(f: impl FnOnce(Token)) {
 | 
				
			||||||
 | 
					    f(Token(()));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Task is spawned (has a future)
 | 
					/// Task is spawned (has a future)
 | 
				
			||||||
pub(crate) const STATE_SPAWNED: u32 = 1 << 0;
 | 
					pub(crate) const STATE_SPAWNED: u32 = 1 << 0;
 | 
				
			||||||
/// Task is in the executor run queue
 | 
					/// Task is in the executor run queue
 | 
				
			||||||
@ -34,10 +43,12 @@ impl State {
 | 
				
			|||||||
        self.state.fetch_and(!STATE_SPAWNED, Ordering::AcqRel);
 | 
					        self.state.fetch_and(!STATE_SPAWNED, Ordering::AcqRel);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Mark the task as run-queued if it's spawned and isn't already run-queued. Return true on success.
 | 
					    /// Mark the task as run-queued if it's spawned and isn't already run-queued. Run the given
 | 
				
			||||||
 | 
					    /// function if the task was successfully marked.
 | 
				
			||||||
    #[inline(always)]
 | 
					    #[inline(always)]
 | 
				
			||||||
    pub fn run_enqueue(&self) -> bool {
 | 
					    pub fn run_enqueue(&self, f: impl FnOnce(Token)) {
 | 
				
			||||||
        self.state
 | 
					        if self
 | 
				
			||||||
 | 
					            .state
 | 
				
			||||||
            .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |state| {
 | 
					            .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |state| {
 | 
				
			||||||
                // If already scheduled, or if not started,
 | 
					                // If already scheduled, or if not started,
 | 
				
			||||||
                if (state & STATE_RUN_QUEUED != 0) || (state & STATE_SPAWNED == 0) {
 | 
					                if (state & STATE_RUN_QUEUED != 0) || (state & STATE_SPAWNED == 0) {
 | 
				
			||||||
@ -48,6 +59,9 @@ impl State {
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
            .is_ok()
 | 
					            .is_ok()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            locked(f);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Unmark the task as run-queued. Return whether the task is spawned.
 | 
					    /// Unmark the task as run-queued. Return whether the task is spawned.
 | 
				
			||||||
 | 
				
			|||||||
@ -3,6 +3,15 @@ use core::sync::atomic::{compiler_fence, AtomicBool, AtomicU32, Ordering};
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
use super::timer_queue::TimerEnqueueOperation;
 | 
					use super::timer_queue::TimerEnqueueOperation;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub(crate) struct Token(());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Creates a token and passes it to the closure.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// This is a no-op replacement for `CriticalSection::with` because we don't need any locking.
 | 
				
			||||||
 | 
					pub(crate) fn locked(f: impl FnOnce(Token)) {
 | 
				
			||||||
 | 
					    f(Token(()));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Must be kept in sync with the layout of `State`!
 | 
					// Must be kept in sync with the layout of `State`!
 | 
				
			||||||
pub(crate) const STATE_SPAWNED: u32 = 1 << 0;
 | 
					pub(crate) const STATE_SPAWNED: u32 = 1 << 0;
 | 
				
			||||||
pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 8;
 | 
					pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 8;
 | 
				
			||||||
@ -57,9 +66,10 @@ impl State {
 | 
				
			|||||||
        self.spawned.store(false, Ordering::Relaxed);
 | 
					        self.spawned.store(false, Ordering::Relaxed);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Mark the task as run-queued if it's spawned and isn't already run-queued. Return true on success.
 | 
					    /// Mark the task as run-queued if it's spawned and isn't already run-queued. Run the given
 | 
				
			||||||
 | 
					    /// function if the task was successfully marked.
 | 
				
			||||||
    #[inline(always)]
 | 
					    #[inline(always)]
 | 
				
			||||||
    pub fn run_enqueue(&self) -> bool {
 | 
					    pub fn run_enqueue(&self, f: impl FnOnce(Token)) {
 | 
				
			||||||
        unsafe {
 | 
					        unsafe {
 | 
				
			||||||
            loop {
 | 
					            loop {
 | 
				
			||||||
                let state: u32;
 | 
					                let state: u32;
 | 
				
			||||||
@ -67,14 +77,15 @@ impl State {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                if (state & STATE_RUN_QUEUED != 0) || (state & STATE_SPAWNED == 0) {
 | 
					                if (state & STATE_RUN_QUEUED != 0) || (state & STATE_SPAWNED == 0) {
 | 
				
			||||||
                    asm!("clrex", options(nomem, nostack));
 | 
					                    asm!("clrex", options(nomem, nostack));
 | 
				
			||||||
                    return false;
 | 
					                    return;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                let outcome: usize;
 | 
					                let outcome: usize;
 | 
				
			||||||
                let new_state = state | STATE_RUN_QUEUED;
 | 
					                let new_state = state | STATE_RUN_QUEUED;
 | 
				
			||||||
                asm!("strex {}, {}, [{}]", out(reg) outcome, in(reg) new_state, in(reg) self, options(nostack));
 | 
					                asm!("strex {}, {}, [{}]", out(reg) outcome, in(reg) new_state, in(reg) self, options(nostack));
 | 
				
			||||||
                if outcome == 0 {
 | 
					                if outcome == 0 {
 | 
				
			||||||
                    return true;
 | 
					                    locked(f);
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
use core::cell::Cell;
 | 
					use core::cell::Cell;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use critical_section::Mutex;
 | 
					pub(crate) use critical_section::{with as locked, CriticalSection as Token};
 | 
				
			||||||
 | 
					use critical_section::{CriticalSection, Mutex};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use super::timer_queue::TimerEnqueueOperation;
 | 
					use super::timer_queue::TimerEnqueueOperation;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -23,13 +24,15 @@ impl State {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn update<R>(&self, f: impl FnOnce(&mut u32) -> R) -> R {
 | 
					    fn update<R>(&self, f: impl FnOnce(&mut u32) -> R) -> R {
 | 
				
			||||||
        critical_section::with(|cs| {
 | 
					        critical_section::with(|cs| self.update_with_cs(cs, f))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn update_with_cs<R>(&self, cs: CriticalSection<'_>, f: impl FnOnce(&mut u32) -> R) -> R {
 | 
				
			||||||
        let s = self.state.borrow(cs);
 | 
					        let s = self.state.borrow(cs);
 | 
				
			||||||
        let mut val = s.get();
 | 
					        let mut val = s.get();
 | 
				
			||||||
        let r = f(&mut val);
 | 
					        let r = f(&mut val);
 | 
				
			||||||
        s.set(val);
 | 
					        s.set(val);
 | 
				
			||||||
        r
 | 
					        r
 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// If task is idle, mark it as spawned + run_queued and return true.
 | 
					    /// If task is idle, mark it as spawned + run_queued and return true.
 | 
				
			||||||
@ -51,17 +54,22 @@ impl State {
 | 
				
			|||||||
        self.update(|s| *s &= !STATE_SPAWNED);
 | 
					        self.update(|s| *s &= !STATE_SPAWNED);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Mark the task as run-queued if it's spawned and isn't already run-queued. Return true on success.
 | 
					    /// Mark the task as run-queued if it's spawned and isn't already run-queued. Run the given
 | 
				
			||||||
 | 
					    /// function if the task was successfully marked.
 | 
				
			||||||
    #[inline(always)]
 | 
					    #[inline(always)]
 | 
				
			||||||
    pub fn run_enqueue(&self) -> bool {
 | 
					    pub fn run_enqueue(&self, f: impl FnOnce(Token)) {
 | 
				
			||||||
        self.update(|s| {
 | 
					        critical_section::with(|cs| {
 | 
				
			||||||
 | 
					            if self.update_with_cs(cs, |s| {
 | 
				
			||||||
                if (*s & STATE_RUN_QUEUED != 0) || (*s & STATE_SPAWNED == 0) {
 | 
					                if (*s & STATE_RUN_QUEUED != 0) || (*s & STATE_SPAWNED == 0) {
 | 
				
			||||||
                    false
 | 
					                    false
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    *s |= STATE_RUN_QUEUED;
 | 
					                    *s |= STATE_RUN_QUEUED;
 | 
				
			||||||
                    true
 | 
					                    true
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
        })
 | 
					            }) {
 | 
				
			||||||
 | 
					                f(cs);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Unmark the task as run-queued. Return whether the task is spawned.
 | 
					    /// Unmark the task as run-queued. Return whether the task is spawned.
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user