mito2/schedule/
scheduler.rs1use std::future::Future;
16use std::pin::Pin;
17use std::sync::atomic::{AtomicU8, Ordering};
18use std::sync::{Arc, RwLock};
19
20use common_telemetry::warn;
21use snafu::{OptionExt, ResultExt, ensure};
22use tokio::sync::Mutex;
23use tokio::task::JoinHandle;
24use tokio_util::sync::CancellationToken;
25
26use crate::error::{InvalidSchedulerStateSnafu, InvalidSenderSnafu, Result, StopSchedulerSnafu};
27
28pub type Job = Pin<Box<dyn Future<Output = ()> + Send>>;
29
30const STATE_RUNNING: u8 = 0;
32const STATE_STOP: u8 = 1;
33const STATE_AWAIT_TERMINATION: u8 = 2;
34
35#[async_trait::async_trait]
37pub trait Scheduler: Send + Sync {
38 fn schedule(&self, job: Job) -> Result<()>;
40
41 fn schedule_with_completion(&self, job: Job, completion: Job) -> Result<()> {
43 self.schedule(Box::pin(async move {
44 job.await;
45 completion.await;
46 }))
47 }
48
49 async fn stop(&self, await_termination: bool) -> Result<()>;
51}
52
53pub type SchedulerRef = Arc<dyn Scheduler>;
54
55struct ScheduledJob {
57 job: Job,
58 completion: Option<Job>,
59}
60
61impl ScheduledJob {
62 async fn run(self) {
64 self.job.await;
65 if let Some(completion) = self.completion {
66 common_runtime::spawn_global(completion);
68 }
69 }
70}
71
72pub struct LocalScheduler {
74 sender: RwLock<Option<async_channel::Sender<ScheduledJob>>>,
76 handles: Mutex<Vec<JoinHandle<()>>>,
78 cancel_token: CancellationToken,
80 state: Arc<AtomicU8>,
82}
83
84impl LocalScheduler {
85 pub fn new(concurrency: usize) -> Self {
89 let (tx, rx) = async_channel::unbounded::<ScheduledJob>();
90 let token = CancellationToken::new();
91 let state = Arc::new(AtomicU8::new(STATE_RUNNING));
92
93 let mut handles = Vec::with_capacity(concurrency);
94
95 for _ in 0..concurrency {
96 let child = token.child_token();
97 let receiver = rx.clone();
98 let state_clone = state.clone();
99 let handle = common_runtime::spawn_global(async move {
100 while state_clone.load(Ordering::Relaxed) == STATE_RUNNING {
101 tokio::select! {
102 _ = child.cancelled() => {
103 break;
104 }
105 req_opt = receiver.recv() =>{
106 if let Ok(job) = req_opt {
107 job.run().await;
108 }
109 }
110 }
111 }
112 if state_clone.load(Ordering::Relaxed) == STATE_AWAIT_TERMINATION {
114 while let Ok(job) = receiver.recv().await {
116 job.run().await;
117 }
118 state_clone.store(STATE_STOP, Ordering::Relaxed);
119 }
120 });
121 handles.push(handle);
122 }
123
124 Self {
125 sender: RwLock::new(Some(tx)),
126 cancel_token: token,
127 handles: Mutex::new(handles),
128 state,
129 }
130 }
131
132 #[inline]
133 fn is_running(&self) -> bool {
134 self.state.load(Ordering::Relaxed) == STATE_RUNNING
135 }
136
137 fn submit(&self, job: ScheduledJob) -> Result<()> {
139 ensure!(self.is_running(), InvalidSchedulerStateSnafu);
140 self.sender
141 .read()
142 .unwrap()
143 .as_ref()
144 .context(InvalidSchedulerStateSnafu)?
145 .try_send(job)
146 .map_err(|_| InvalidSenderSnafu {}.build())
147 }
148}
149
150#[async_trait::async_trait]
151impl Scheduler for LocalScheduler {
152 fn schedule(&self, job: Job) -> Result<()> {
153 self.submit(ScheduledJob {
154 job,
155 completion: None,
156 })
157 }
158
159 fn schedule_with_completion(&self, job: Job, completion: Job) -> Result<()> {
161 self.submit(ScheduledJob {
162 job,
163 completion: Some(completion),
164 })
165 }
166
167 async fn stop(&self, await_termination: bool) -> Result<()> {
169 ensure!(self.is_running(), InvalidSchedulerStateSnafu);
170 let state = if await_termination {
171 STATE_AWAIT_TERMINATION
172 } else {
173 STATE_STOP
174 };
175 self.sender.write().unwrap().take();
176 self.state.store(state, Ordering::Relaxed);
177 self.cancel_token.cancel();
178
179 futures::future::join_all(self.handles.lock().await.drain(..))
180 .await
181 .into_iter()
182 .collect::<std::result::Result<Vec<_>, _>>()
183 .context(StopSchedulerSnafu)?;
184
185 Ok(())
186 }
187}
188
189impl Drop for LocalScheduler {
190 fn drop(&mut self) {
191 if self.state.load(Ordering::Relaxed) != STATE_STOP {
192 warn!(
193 "scheduler should be stopped before dropping, which means the state of scheduler must be STATE_STOP"
194 );
195
196 self.sender.write().unwrap().take();
198 self.cancel_token.cancel();
199 }
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use std::sync::Arc;
206 use std::sync::atomic::AtomicI32;
207
208 use tokio::sync::Barrier;
209 use tokio::time::Duration;
210
211 use super::*;
212
213 #[tokio::test]
214 async fn test_sum_cap() {
215 let task_size = 1000;
216 let sum = Arc::new(AtomicI32::new(0));
217 let local = LocalScheduler::new(task_size);
218
219 for _ in 0..task_size {
220 let sum_clone = sum.clone();
221 local
222 .schedule(Box::pin(async move {
223 sum_clone.fetch_add(1, Ordering::Relaxed);
224 }))
225 .unwrap();
226 }
227 local.stop(true).await.unwrap();
228 assert_eq!(sum.load(Ordering::Relaxed), 1000);
229 }
230
231 #[tokio::test]
232 async fn test_sum_consumer_num() {
233 let task_size = 1000;
234 let sum = Arc::new(AtomicI32::new(0));
235 let local = LocalScheduler::new(3);
236 let mut target = 0;
237 for _ in 0..task_size {
238 let sum_clone = sum.clone();
239 let ok = local
240 .schedule(Box::pin(async move {
241 sum_clone.fetch_add(1, Ordering::Relaxed);
242 }))
243 .is_ok();
244 if ok {
245 target += 1;
246 }
247 }
248 local.stop(true).await.unwrap();
249 assert_eq!(sum.load(Ordering::Relaxed), target);
250 }
251
252 #[tokio::test]
253 async fn test_scheduler_many() {
254 let task_size = 1000;
255
256 let barrier = Arc::new(Barrier::new(task_size + 1));
257 let local: LocalScheduler = LocalScheduler::new(task_size);
258
259 for _ in 0..task_size {
260 let barrier_clone = barrier.clone();
261 local
262 .schedule(Box::pin(async move {
263 barrier_clone.wait().await;
264 }))
265 .unwrap();
266 }
267 barrier.wait().await;
268 local.stop(true).await.unwrap();
269 }
270
271 #[tokio::test]
272 async fn test_scheduler_continuous_stop() {
273 let sum = Arc::new(AtomicI32::new(0));
274 let local = Arc::new(LocalScheduler::new(1000));
275
276 let barrier = Arc::new(Barrier::new(2));
277 let barrier_clone = barrier.clone();
278 let local_stop = local.clone();
279 tokio::spawn(async move {
280 tokio::time::sleep(Duration::from_millis(5)).await;
281 local_stop.stop(true).await.unwrap();
282 barrier_clone.wait().await;
283 });
284
285 let target = Arc::new(AtomicI32::new(0));
286 let local_task = local.clone();
287 let target_clone = target.clone();
288 let sum_clone = sum.clone();
289 tokio::spawn(async move {
290 loop {
291 let sum_c = sum_clone.clone();
292 let ok = local_task
293 .schedule(Box::pin(async move {
294 sum_c.fetch_add(1, Ordering::Relaxed);
295 }))
296 .is_ok();
297 if ok {
298 target_clone.fetch_add(1, Ordering::Relaxed);
299 } else {
300 break;
301 }
302 tokio::task::yield_now().await;
303 }
304 });
305 barrier.wait().await;
306 assert_eq!(sum.load(Ordering::Relaxed), target.load(Ordering::Relaxed));
307 }
308}