1#![cfg_attr(docsrs, feature(doc_cfg))]
4#![allow(unused_features)]
5#![warn(missing_docs)]
6#![deny(rustdoc::broken_intra_doc_links)]
7#![doc(
8 html_logo_url = "https://github.com/compio-rs/compio-logo/raw/refs/heads/master/generated/colored-bold.svg"
9)]
10#![doc(
11 html_favicon_url = "https://github.com/compio-rs/compio-logo/raw/refs/heads/master/generated/colored-bold.svg"
12)]
13
14use std::{any::Any, fmt::Debug, ptr::NonNull, task::Waker};
15
16use crate::queue::{TaskId, TaskQueue};
17
18pub mod console;
19mod join_handle;
20mod queue;
21mod task;
22mod util;
23mod waker;
24
25use compio_log::{instrument, trace};
26use compio_send_wrapper::SendWrapper;
27pub use console::SpawnMeta;
28use crossbeam_queue::ArrayQueue;
29pub use join_handle::{JoinError, JoinHandle, ResumeUnwind};
30use util::panic_guard;
31
32cfg_select! {
33 loom => {
34 use loom::{cell::UnsafeCell, hint, sync::atomic::*, thread::yield_now};
35 }
36 _ => {
37 use std::{hint, sync::atomic::*, thread::yield_now};
38
39 #[repr(transparent)]
40 struct UnsafeCell<T>(std::cell::UnsafeCell<T>);
41
42 impl<T> UnsafeCell<T> {
43 pub fn new(value: T) -> Self {
44 Self(std::cell::UnsafeCell::new(value))
45 }
46
47 #[inline(always)]
48 pub fn with_mut<F, R>(&self, f: F) -> R
49 where
50 F: FnOnce(*mut T) -> R,
51 {
52 f(self.0.get())
53 }
54
55 #[inline(always)]
56 pub fn with<F, R>(&self, f: F) -> R
57 where
58 F: FnOnce(*const T) -> R,
59 {
60 f(self.0.get())
61 }
62 }
63 }
64}
65
66pub(crate) type PanicResult<T> = Result<T, Panic>;
67pub(crate) type Panic = Box<dyn Any + Send + 'static>;
68
69#[derive(Debug)]
84pub struct Executor {
85 ptr: NonNull<Shared>,
86 config: ExecutorConfig,
87}
88
89#[derive(Debug, Clone)]
91pub struct ExecutorConfig {
92 pub sync_queue_size: usize,
97
98 pub local_queue_size: usize,
103
104 pub max_interval: u32,
106
107 pub waker: Option<Waker>,
112}
113
114impl Default for ExecutorConfig {
115 fn default() -> Self {
116 Self {
117 sync_queue_size: 64,
118 local_queue_size: 64,
119 max_interval: 61,
120 waker: None,
121 }
122 }
123}
124
125pub(crate) struct Shared {
126 waker: Option<Waker>,
127 sync: ArrayQueue<TaskId>,
128 pending: AtomicUsize,
129 queue: SendWrapper<TaskQueue>,
130}
131
132impl Shared {
133 #[inline]
139 pub(crate) fn drain_sync(&self, queue: &TaskQueue) {
140 if self.pending.load(Ordering::Acquire) == 0 {
141 return;
142 }
143
144 let mut drained: usize = 0;
145 while let Some(id) = self.sync.pop() {
146 queue.make_hot(id);
147 drained += 1;
148 }
149
150 if drained != 0 {
151 self.pending.fetch_sub(drained, Ordering::Release);
152 }
153 }
154}
155
156impl Executor {
157 pub fn new() -> Self {
159 Self::with_config(ExecutorConfig::default())
160 }
161
162 pub fn with_config(mut config: ExecutorConfig) -> Self {
164 let ptr = Box::into_raw(Box::new(Shared {
165 waker: config.waker.take(),
166 sync: ArrayQueue::new(config.sync_queue_size),
167 pending: AtomicUsize::new(0),
168 queue: SendWrapper::new(TaskQueue::new(config.local_queue_size)),
169 }));
170
171 Self {
172 config,
173 ptr: unsafe { NonNull::new_unchecked(ptr) },
174 }
175 }
176
177 #[track_caller]
179 pub fn spawn<F: Future + 'static>(&self, fut: F) -> JoinHandle<F::Output> {
180 self.spawn_at(fut, SpawnMeta::capture())
181 }
182
183 pub fn spawn_at<F: Future + 'static>(&self, fut: F, meta: SpawnMeta) -> JoinHandle<F::Output> {
192 let shared = self.shared();
193 let tracker = shared.queue.tracker();
194 let queue = unsafe { shared.queue.get_unchecked() };
196 let task = queue.insert(self.ptr, tracker, fut, meta);
197
198 JoinHandle::new(task)
199 }
200
201 pub fn tick(&self) -> bool {
211 let queue = self.queue();
212
213 self.shared().drain_sync(queue);
214
215 for id in queue.iter_hot().take(self.config.max_interval as _) {
216 queue.make_cold(id);
217 let task = queue.take(id).expect("Task was not reset back");
218 let res = unsafe { task.run() };
219 if res.is_ready() {
220 unsafe { task.drop() };
224 queue.remove(id);
225 } else {
226 queue.reset(id, task);
227 }
228 }
229
230 queue.has_hot()
231 }
232
233 #[doc(hidden)]
235 pub fn has_task(&self) -> bool {
236 self.queue().hot_head().is_some()
237 }
238
239 pub fn clear(&self) {
246 instrument!(compio_log::Level::TRACE, "Executor::drop");
247 trace!("Dropping Executor");
248
249 while self.shared().sync.pop().is_some() {}
250 unsafe { self.queue().clear() };
251 }
252
253 #[inline(always)]
254 fn shared(&self) -> &Shared {
255 unsafe { self.ptr.as_ref() }
256 }
257
258 #[inline(always)]
259 fn queue(&self) -> &TaskQueue {
260 unsafe { self.shared().queue.get_unchecked() }
262 }
263}
264
265impl Drop for Executor {
266 fn drop(&mut self) {
267 self.clear();
268 unsafe { drop(Box::from_raw(self.ptr.as_ptr())) };
269 }
270}
271
272impl Default for Executor {
273 fn default() -> Self {
274 Self::new()
275 }
276}