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::SegQueue;
29pub use join_handle::{JoinError, JoinHandle, ResumeUnwind};
30use util::panic_guard;
31
32cfg_select! {
33 loom => {
34 use loom::{cell::UnsafeCell, hint, sync::atomic::*};
35 }
36 _ => {
37 use std::{hint, sync::atomic::*};
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 local_queue_size: usize,
97
98 pub max_interval: u32,
100
101 pub waker: Option<Waker>,
106}
107
108impl Default for ExecutorConfig {
109 fn default() -> Self {
110 Self {
111 local_queue_size: 64,
112 max_interval: 61,
113 waker: None,
114 }
115 }
116}
117
118pub(crate) struct Shared {
119 waker: Option<Waker>,
120 sync: SegQueue<TaskId>,
121 pending: AtomicUsize,
122 queue: SendWrapper<TaskQueue>,
123}
124
125impl Shared {
126 #[inline]
132 pub(crate) fn drain_sync(&self, queue: &TaskQueue) {
133 if self.pending.load(Ordering::Acquire) == 0 {
134 return;
135 }
136
137 let mut drained: usize = 0;
138 while let Some(id) = self.sync.pop() {
139 queue.make_hot(id);
140 drained += 1;
141 }
142
143 if drained != 0 {
144 self.pending.fetch_sub(drained, Ordering::Release);
145 }
146 }
147}
148
149impl Executor {
150 pub fn new() -> Self {
152 Self::with_config(ExecutorConfig::default())
153 }
154
155 pub fn with_config(mut config: ExecutorConfig) -> Self {
157 let ptr = Box::into_raw(Box::new(Shared {
158 waker: config.waker.take(),
159 sync: SegQueue::new(),
160 pending: AtomicUsize::new(0),
161 queue: SendWrapper::new(TaskQueue::new(config.local_queue_size)),
162 }));
163
164 Self {
165 config,
166 ptr: unsafe { NonNull::new_unchecked(ptr) },
167 }
168 }
169
170 #[track_caller]
172 pub fn spawn<F: Future + 'static>(&self, fut: F) -> JoinHandle<F::Output> {
173 self.spawn_at(fut, SpawnMeta::capture())
174 }
175
176 pub fn spawn_at<F: Future + 'static>(&self, fut: F, meta: SpawnMeta) -> JoinHandle<F::Output> {
185 let shared = self.shared();
186 let tracker = shared.queue.tracker();
187 let queue = unsafe { shared.queue.get_unchecked() };
189 let task = queue.insert(self.ptr, tracker, fut, meta);
190
191 JoinHandle::new(task)
192 }
193
194 pub fn tick(&self) -> bool {
204 let queue = self.queue();
205
206 self.shared().drain_sync(queue);
207
208 for _ in 0..self.config.max_interval {
209 let Some(id) = queue.hot_head() else {
210 return false;
211 };
212
213 queue.make_cold(id);
214
215 let task = queue.take(id).expect("Task was not reset back");
216 let res = unsafe { task.run() };
219
220 if res.is_ready() {
221 unsafe { task.drop() };
226 queue.remove(id);
227 } else {
228 queue.reset(id, task);
229 }
230 }
231
232 queue.has_hot()
233 }
234
235 #[doc(hidden)]
237 pub fn has_task(&self) -> bool {
238 self.queue().has_hot()
239 }
240
241 pub fn num_alive_tasks(&self) -> usize {
249 self.queue().len()
250 }
251
252 pub fn clear(&self) {
259 instrument!(compio_log::Level::TRACE, "Executor::drop");
260 trace!("Dropping Executor");
261
262 while self.shared().sync.pop().is_some() {}
263 unsafe { self.queue().clear() };
264 }
265
266 #[inline(always)]
267 fn shared(&self) -> &Shared {
268 unsafe { self.ptr.as_ref() }
269 }
270
271 #[inline(always)]
272 fn queue(&self) -> &TaskQueue {
273 unsafe { self.shared().queue.get_unchecked() }
275 }
276}
277
278impl Drop for Executor {
279 fn drop(&mut self) {
280 self.clear();
281 unsafe { drop(Box::from_raw(self.ptr.as_ptr())) };
282 }
283}
284
285impl Default for Executor {
286 fn default() -> Self {
287 Self::new()
288 }
289}