1#![allow(unused_features)]
4#![warn(missing_docs)]
5#![deny(rustdoc::broken_intra_doc_links)]
6#![doc(
7 html_logo_url = "https://github.com/compio-rs/compio-logo/raw/refs/heads/master/generated/colored-bold.svg"
8)]
9#![doc(
10 html_favicon_url = "https://github.com/compio-rs/compio-logo/raw/refs/heads/master/generated/colored-bold.svg"
11)]
12
13use std::{
14 collections::HashSet,
15 io,
16 num::NonZeroUsize,
17 panic::resume_unwind,
18 thread::{JoinHandle, available_parallelism},
19};
20
21use compio_driver::{AsyncifyPool, DispatchError, Dispatchable, ProactorBuilder};
22use compio_runtime::{JoinHandle as CompioJoinHandle, Runtime, SpawnMeta};
23use flume::{Sender, unbounded};
24use futures_channel::oneshot;
25
26struct Spawning {
29 task: Box<dyn Spawnable + Send>,
30 meta: SpawnMeta,
31}
32
33trait Spawnable {
34 fn spawn(self: Box<Self>, handle: &Runtime, meta: SpawnMeta) -> CompioJoinHandle<()>;
35}
36
37struct Concrete<F, R> {
39 callback: oneshot::Sender<R>,
40 func: F,
41}
42
43impl<F, R> Concrete<F, R> {
44 pub fn new(func: F) -> (Self, oneshot::Receiver<R>) {
45 let (tx, rx) = oneshot::channel();
46 (Self { callback: tx, func }, rx)
47 }
48}
49
50impl<F, R> Spawnable for Concrete<F, R>
51where
52 F: AsyncFnOnce() -> R + Send + 'static,
53 R: Send + 'static,
54{
55 fn spawn(self: Box<Self>, handle: &Runtime, meta: SpawnMeta) -> CompioJoinHandle<()> {
56 let Concrete { callback, func } = *self;
57 handle.spawn_at(
58 async move {
59 let res = func().await;
60 callback.send(res).ok();
61 },
62 meta,
63 )
64 }
65}
66
67impl<F, R> Dispatchable for Concrete<F, R>
68where
69 F: FnOnce() -> R + Send + 'static,
70 R: Send + 'static,
71{
72 fn run(self: Box<Self>) {
73 let Concrete { callback, func } = *self;
74 let res = func();
75 callback.send(res).ok();
76 }
77}
78
79#[derive(Debug)]
81pub struct Dispatcher {
82 sender: Sender<Spawning>,
83 threads: Vec<JoinHandle<()>>,
84 pool: AsyncifyPool,
85}
86
87impl Dispatcher {
88 #[track_caller]
90 pub(crate) fn new_impl(builder: DispatcherBuilder) -> io::Result<Self> {
91 let DispatcherBuilder {
92 nthreads,
93 concurrent,
94 stack_size,
95 mut thread_affinity,
96 mut names,
97 mut proactor_builder,
98 } = builder;
99 proactor_builder.force_reuse_thread_pool();
100 let pool = proactor_builder.create_or_get_thread_pool();
101 let (sender, receiver) = unbounded::<Spawning>();
102 let meta = SpawnMeta::capture().named("dispatcher::worker");
105
106 let threads = (0..nthreads)
107 .map({
108 |index| {
109 let proactor_builder = proactor_builder.clone();
110 let receiver = receiver.clone();
111
112 let thread_builder = std::thread::Builder::new();
113 let thread_builder = if let Some(s) = stack_size {
114 thread_builder.stack_size(s)
115 } else {
116 thread_builder
117 };
118 let thread_builder = if let Some(f) = &mut names {
119 thread_builder.name(f(index))
120 } else {
121 thread_builder
122 };
123
124 let cpus = if let Some(f) = &mut thread_affinity {
125 f(index)
126 } else {
127 HashSet::new()
128 };
129 thread_builder.spawn(move || {
130 Runtime::builder()
131 .with_proactor(proactor_builder)
132 .thread_affinity(cpus)
133 .build()
134 .expect("cannot create compio runtime")
135 .block_on_at(
136 async move {
137 while let Ok(Spawning { task: f, meta }) =
138 receiver.recv_async().await
139 {
140 let task = Runtime::with_current(|rt| f.spawn(rt, meta));
141 if concurrent {
142 task.detach()
143 } else {
144 task.await.ok();
145 }
146 }
147 },
148 meta,
149 );
150 })
151 }
152 })
153 .collect::<io::Result<Vec<_>>>()?;
154
155 Ok(Self {
156 sender,
157 threads,
158 pool,
159 })
160 }
161
162 #[track_caller]
164 pub fn new() -> io::Result<Self> {
165 Self::builder().build()
166 }
167
168 pub fn builder() -> DispatcherBuilder {
170 DispatcherBuilder::default()
171 }
172
173 #[track_caller]
184 pub fn dispatch<F, R>(&self, f: F) -> Result<oneshot::Receiver<R>, DispatchError<F>>
185 where
186 F: AsyncFnOnce() -> R + Send + 'static,
187 R: Send + 'static,
188 {
189 let (concrete, rx) = Concrete::new(f);
190
191 let meta = SpawnMeta::capture().named("dispatch");
192 match self.sender.send(Spawning {
193 task: Box::new(concrete),
194 meta,
195 }) {
196 Ok(_) => Ok(rx),
197 Err(err) => {
198 let recovered =
201 unsafe { Box::from_raw(Box::into_raw(err.0.task) as *mut Concrete<F, R>) };
202 Err(DispatchError(recovered.func))
203 }
204 }
205 }
206
207 pub fn dispatch_blocking<Fn, R>(&self, f: Fn) -> Result<oneshot::Receiver<R>, DispatchError<Fn>>
220 where
221 Fn: FnOnce() -> R + Send + 'static,
222 R: Send + 'static,
223 {
224 let (concrete, rx) = Concrete::new(f);
225
226 self.pool
227 .dispatch(concrete)
228 .map_err(|e| DispatchError(e.0.func))?;
229
230 Ok(rx)
231 }
232
233 pub async fn join(self) -> io::Result<()> {
236 drop(self.sender);
237 let (tx, rx) = oneshot::channel::<Vec<_>>();
238 if let Err(f) = self.pool.dispatch({
239 move || {
240 let results = self
241 .threads
242 .into_iter()
243 .map(|thread| thread.join())
244 .collect();
245 tx.send(results).ok();
246 }
247 }) {
248 std::thread::spawn(f.0);
249 }
250 let results = rx
251 .await
252 .map_err(|_| io::Error::other("the join task cancelled unexpectedly"))?;
253 for res in results {
254 res.unwrap_or_else(|e| resume_unwind(e));
255 }
256 Ok(())
257 }
258}
259
260pub struct DispatcherBuilder {
262 nthreads: usize,
263 concurrent: bool,
264 stack_size: Option<usize>,
265 thread_affinity: Option<Box<dyn FnMut(usize) -> HashSet<usize>>>,
266 names: Option<Box<dyn FnMut(usize) -> String>>,
267 proactor_builder: ProactorBuilder,
268}
269
270impl DispatcherBuilder {
271 pub fn new() -> Self {
273 Self {
274 nthreads: available_parallelism().map(|n| n.get()).unwrap_or(1),
275 concurrent: true,
276 stack_size: None,
277 thread_affinity: None,
278 names: None,
279 proactor_builder: ProactorBuilder::new(),
280 }
281 }
282
283 pub fn concurrent(mut self, concurrent: bool) -> Self {
288 self.concurrent = concurrent;
289 self
290 }
291
292 pub fn worker_threads(mut self, nthreads: NonZeroUsize) -> Self {
296 self.nthreads = nthreads.get();
297 self
298 }
299
300 pub fn stack_size(mut self, s: usize) -> Self {
302 self.stack_size = Some(s);
303 self
304 }
305
306 pub fn thread_affinity(mut self, f: impl FnMut(usize) -> HashSet<usize> + 'static) -> Self {
308 self.thread_affinity = Some(Box::new(f));
309 self
310 }
311
312 pub fn thread_names(mut self, f: impl (FnMut(usize) -> String) + 'static) -> Self {
314 self.names = Some(Box::new(f) as _);
315 self
316 }
317
318 pub fn proactor_builder(mut self, builder: ProactorBuilder) -> Self {
320 self.proactor_builder = builder;
321 self
322 }
323
324 #[track_caller]
326 pub fn build(self) -> io::Result<Dispatcher> {
327 Dispatcher::new_impl(self)
328 }
329}
330
331impl Default for DispatcherBuilder {
332 fn default() -> Self {
333 Self::new()
334 }
335}