@@ -14349,6 +14349,17 @@ dependencies = [
"windows-sys 0.59.0",
]
+[[package]]
+name = "scheduler"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "async-task",
+ "parking_lot",
+ "rand 0.8.5",
+ "rand_chacha 0.3.1",
+]
+
[[package]]
name = "schema_generator"
version = "0.1.0"
@@ -140,6 +140,7 @@ members = [
"crates/rpc",
"crates/rules_library",
"crates/schema_generator",
+ "crates/scheduler",
"crates/search",
"crates/semantic_index",
"crates/semantic_version",
@@ -0,0 +1,17 @@
+[package]
+name = "scheduler"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+path = "src/scheduler.rs"
+
+[dependencies]
+anyhow = "1.0"
+async-task = "4.5"
+rand = { version = "0.8", features = ["small_rng"] }
+rand_chacha = "0.3"
+parking_lot = "0.12"
+
+[features]
+test-support = []
@@ -0,0 +1,144 @@
+use anyhow::Result;
+use async_task::Runnable;
+use parking_lot::Mutex;
+use rand_chacha::rand_core::SeedableRng;
+use rand_chacha::ChaCha8Rng;
+use std::any::Any;
+use std::collections::VecDeque;
+use std::future::Future;
+use std::marker::PhantomData;
+use std::sync::Arc;
+
+use std::thread::{self, ThreadId};
+
+pub trait Scheduler: Send + Sync + Any {
+ fn schedule_foreground(&self, runnable: Runnable);
+ fn is_main_thread(&self) -> bool;
+}
+
+#[derive(Clone, Copy, PartialEq, Eq, Hash)]
+pub struct TaskId(usize);
+
+pub struct Task<R>(async_task::Task<R>);
+
+impl<R> Task<R> {
+ pub fn id(&self) -> TaskId {
+ TaskId(0) // Placeholder
+ }
+}
+
+pub struct SchedulerConfig {
+ pub randomize_order: bool,
+ pub seed: u64,
+}
+
+impl Default for SchedulerConfig {
+ fn default() -> Self {
+ Self {
+ randomize_order: true,
+ seed: 0,
+ }
+ }
+}
+
+pub struct TestScheduler {
+ inner: Mutex<TestSchedulerInner>,
+}
+
+struct TestSchedulerInner {
+ rng: ChaCha8Rng,
+ foreground_queue: VecDeque<Runnable>,
+ creation_thread_id: ThreadId,
+}
+
+impl TestScheduler {
+ pub fn new(config: SchedulerConfig) -> Self {
+ Self {
+ inner: Mutex::new(TestSchedulerInner {
+ rng: ChaCha8Rng::seed_from_u64(config.seed),
+ foreground_queue: VecDeque::new(),
+ creation_thread_id: thread::current().id(),
+ }),
+ }
+ }
+
+ pub fn tick(&self, background_only: bool) -> bool {
+ let mut inner = self.inner.lock();
+ if !background_only {
+ if let Some(runnable) = inner.foreground_queue.pop_front() {
+ drop(inner); // Unlock while running
+ runnable.run();
+ return true;
+ }
+ }
+ false
+ }
+
+ pub fn run(&self) {
+ while self.tick(false) {}
+ }
+}
+
+impl Scheduler for TestScheduler {
+ fn schedule_foreground(&self, runnable: Runnable) {
+ self.inner.lock().foreground_queue.push_back(runnable);
+ }
+
+ fn is_main_thread(&self) -> bool {
+ thread::current().id() == self.inner.lock().creation_thread_id
+ }
+}
+
+pub struct ForegroundExecutor {
+ scheduler: Arc<dyn Scheduler>,
+ _phantom: PhantomData<()>,
+}
+
+impl ForegroundExecutor {
+ pub fn new(scheduler: Arc<dyn Scheduler>) -> Result<Self> {
+ Ok(Self {
+ scheduler,
+ _phantom: PhantomData,
+ })
+ }
+
+ pub fn spawn<R: 'static + Send>(
+ &self,
+ future: impl Future<Output = R> + Send + 'static,
+ ) -> Task<R> {
+ let scheduler = self.scheduler.clone();
+ let (runnable, task) = async_task::spawn_local(future, move |runnable| {
+ scheduler.schedule_foreground(runnable);
+ });
+ runnable.schedule();
+ Task(task)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::sync::atomic::{AtomicBool, Ordering};
+ use std::sync::Arc;
+
+ #[test]
+ fn test_basic_spawn_and_run() {
+ let scheduler = Arc::new(TestScheduler::new(SchedulerConfig::default()));
+ let executor = ForegroundExecutor::new(scheduler.clone()).unwrap();
+
+ let flag = Arc::new(AtomicBool::new(false));
+ assert!(!flag.load(Ordering::SeqCst));
+ let _task = executor.spawn({
+ let flag = flag.clone();
+ async move {
+ flag.store(true, Ordering::SeqCst);
+ }
+ });
+
+ assert!(!flag.load(Ordering::SeqCst));
+
+ scheduler.run();
+
+ assert!(flag.load(Ordering::SeqCst));
+ }
+}