-
Notifications
You must be signed in to change notification settings - Fork 72
refactor(runtime): introduce Scheduler for task scheduling
#491
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
Merged
Changes from all commits
Commits
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| use std::{cell::UnsafeCell, collections::VecDeque}; | ||
|
|
||
| /// A queue that is `!Sync` with interior mutability. | ||
| pub(crate) struct LocalQueue<T> { | ||
| queue: UnsafeCell<VecDeque<T>>, | ||
| } | ||
|
|
||
| impl<T> LocalQueue<T> { | ||
| /// Creates an empty `LocalQueue`. | ||
| pub(crate) const fn new() -> Self { | ||
| Self { | ||
| queue: UnsafeCell::new(VecDeque::new()), | ||
| } | ||
| } | ||
|
|
||
| /// Pushes an item to the back of the queue. | ||
| pub(crate) fn push(&self, item: T) { | ||
| // SAFETY: | ||
| // Exclusive mutable access because: | ||
| // - The mutable reference is created and used immediately within this scope. | ||
| // - `LocalQueue` is `!Sync`, so no other threads can access it concurrently. | ||
| let queue = unsafe { &mut *self.queue.get() }; | ||
| queue.push_back(item); | ||
| } | ||
|
|
||
| /// Pops an item from the front of the queue, returning `None` if empty. | ||
| pub(crate) fn pop(&self) -> Option<T> { | ||
| // SAFETY: | ||
| // Exclusive mutable access because: | ||
| // - The mutable reference is created and used immediately within this scope. | ||
| // - `LocalQueue` is `!Sync`, so no other threads can access it concurrently. | ||
| let queue = unsafe { &mut *self.queue.get() }; | ||
| queue.pop_front() | ||
| } | ||
|
|
||
| /// Returns `true` if the queue is empty. | ||
| pub(crate) fn is_empty(&self) -> bool { | ||
| // SAFETY: | ||
| // Exclusive mutable access because: | ||
| // - The mutable reference is created and used immediately within this scope. | ||
| // - `LocalQueue` is `!Sync`, so no other threads can access it concurrently. | ||
| let queue = unsafe { &mut *self.queue.get() }; | ||
| queue.is_empty() | ||
| } | ||
| } | ||
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,154 @@ | ||
| use crate::runtime::scheduler::{local_queue::LocalQueue, send_wrapper::SendWrapper}; | ||
| use async_task::{Runnable, Task}; | ||
| use compio_driver::NotifyHandle; | ||
| use crossbeam_queue::SegQueue; | ||
| use std::{future::Future, marker::PhantomData, sync::Arc}; | ||
|
|
||
| mod local_queue; | ||
| mod send_wrapper; | ||
|
|
||
| /// A task queue consisting of a local queue and a synchronized queue. | ||
| struct TaskQueue { | ||
| local_queue: SendWrapper<LocalQueue<Runnable>>, | ||
| sync_queue: SegQueue<Runnable>, | ||
| } | ||
|
|
||
| impl TaskQueue { | ||
| /// Creates a new `TaskQueue`. | ||
| fn new() -> Self { | ||
| Self { | ||
| local_queue: SendWrapper::new(LocalQueue::new()), | ||
| sync_queue: SegQueue::new(), | ||
| } | ||
| } | ||
|
|
||
| /// Pushes a `Runnable` task to the appropriate queue. | ||
| /// | ||
| /// If the current thread is the same as the creator thread, push to the local queue. | ||
| /// Otherwise, push to the sync queue. | ||
| fn push(&self, runnable: Runnable, notify: &NotifyHandle) { | ||
| if let Some(local_queue) = self.local_queue.get() { | ||
| local_queue.push(runnable); | ||
| #[cfg(feature = "notify-always")] | ||
| notify.notify().ok(); | ||
| } else { | ||
| self.sync_queue.push(runnable); | ||
| notify.notify().ok(); | ||
| } | ||
| } | ||
|
|
||
| /// Pops at most one task from each queue and returns them as `(local_task, sync_task)`. | ||
| /// | ||
| /// # Safety | ||
| /// | ||
| /// Call this method in the same thread as the creator. | ||
| unsafe fn pop(&self) -> (Option<Runnable>, Option<Runnable>) { | ||
| // SAFETY: See the safety comment of this method. | ||
| let local_queue = unsafe { self.local_queue.get_unchecked() }; | ||
|
|
||
| let local_task = local_queue.pop(); | ||
|
|
||
| // Perform an empty check as a fast path, since `SegQueue::pop()` is more expensive. | ||
| let sync_task = if self.sync_queue.is_empty() { | ||
| None | ||
| } else { | ||
| self.sync_queue.pop() | ||
| }; | ||
|
|
||
| (local_task, sync_task) | ||
| } | ||
|
|
||
| /// Returns `true` if both queues are empty. | ||
| /// | ||
| /// # Safety | ||
| /// | ||
| /// Call this method in the same thread as the creator. | ||
| unsafe fn is_empty(&self) -> bool { | ||
| // SAFETY: See the safety comment of this method. | ||
| let local_queue = unsafe { self.local_queue.get_unchecked() }; | ||
| local_queue.is_empty() && self.sync_queue.is_empty() | ||
| } | ||
| } | ||
|
|
||
| /// A scheduler for managing and executing tasks. | ||
| pub(crate) struct Scheduler { | ||
| task_queue: Arc<TaskQueue>, | ||
| event_interval: usize, | ||
| // `Scheduler` is `!Send` and `!Sync`. | ||
| _local_marker: PhantomData<*const ()>, | ||
| } | ||
|
|
||
| impl Scheduler { | ||
| /// Creates a new `Scheduler`. | ||
| pub(crate) fn new(event_interval: usize) -> Self { | ||
| Self { | ||
| task_queue: Arc::new(TaskQueue::new()), | ||
| event_interval, | ||
| _local_marker: PhantomData, | ||
| } | ||
| } | ||
|
|
||
| /// Spawns a new asynchronous task, returning a [`Task`] for it. | ||
| /// | ||
| /// # Safety | ||
| /// | ||
| /// The caller should ensure the captured lifetime long enough. | ||
| pub(crate) unsafe fn spawn_unchecked<F>( | ||
| &self, | ||
| future: F, | ||
| notify: NotifyHandle, | ||
| ) -> Task<F::Output> | ||
| where | ||
| F: Future, | ||
| { | ||
| let schedule = { | ||
| // Use `Weak` to break reference cycle. | ||
| // `TaskQueue` -> `Runnable` -> `TaskQueue` | ||
| let task_queue = Arc::downgrade(&self.task_queue); | ||
|
|
||
| move |runnable| { | ||
| if let Some(task_queue) = task_queue.upgrade() { | ||
| task_queue.push(runnable, ¬ify); | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| let (runnable, task) = async_task::spawn_unchecked(future, schedule); | ||
| runnable.schedule(); | ||
| task | ||
| } | ||
|
|
||
| /// Run the scheduled tasks. | ||
| /// | ||
| /// The return value indicates whether there are still tasks in the queue. | ||
| pub(crate) fn run(&self) -> bool { | ||
| for _ in 0..self.event_interval { | ||
| // SAFETY: | ||
| // `Scheduler` is `!Send` and `!Sync`, so this method is only called | ||
| // on `TaskQueue`'s creator thread. | ||
| let tasks = unsafe { self.task_queue.pop() }; | ||
|
|
||
| // Run the tasks, which will poll the futures. | ||
| // Since spawned tasks are not required to be `Send`, they must always be polled | ||
| // on the same thread. Because `Scheduler` is `!Send` and `!Sync`, this is safe. | ||
| match tasks { | ||
| (Some(local), Some(sync)) => { | ||
| local.run(); | ||
| sync.run(); | ||
| } | ||
| (Some(local), None) => { | ||
| local.run(); | ||
| } | ||
| (None, Some(sync)) => { | ||
| sync.run(); | ||
| } | ||
| (None, None) => break, | ||
| } | ||
| } | ||
|
|
||
| // SAFETY: | ||
| // `Scheduler` is `!Send` and `!Sync`, so this method is only called | ||
| // on `TaskQueue`'s creator thread. | ||
| !unsafe { self.task_queue.is_empty() } | ||
| } | ||
| } |
File renamed without changes.
Oops, something went wrong.
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.