forked from compio-rs/compio
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmod.rs
More file actions
175 lines (154 loc) · 5.38 KB
/
mod.rs
File metadata and controls
175 lines (154 loc) · 5.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
use std::{future::Future, marker::PhantomData, sync::Arc};
use async_task::{Runnable, Task};
use compio_driver::NotifyHandle;
use crossbeam_queue::SegQueue;
use crate::runtime::scheduler::{local_queue::LocalQueue, send_wrapper::SendWrapper};
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);
let thread_guard = SendWrapper::new(());
move |runnable| {
if let Some(task_queue) = task_queue.upgrade() {
task_queue.push(runnable, ¬ify);
} else if thread_guard.get().is_none() {
// It's not safe to drop the runnable in another thread.
std::mem::forget(runnable);
}
}
};
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() }
}
pub(crate) fn clear(&self) {
loop {
// SAFETY:
// `Scheduler` is `!Send` and `!Sync`, so this method is only called
// on `TaskQueue`'s creator thread.
let tasks = unsafe { self.task_queue.pop() };
if let (None, None) = tasks {
break;
}
}
}
}