1use std::cell::RefCell;
4use std::future::Future;
5use std::marker::PhantomData;
6use std::num::NonZeroUsize;
7use std::panic::AssertUnwindSafe;
8use std::rc::Rc;
9use std::sync::atomic::{AtomicUsize, Ordering};
10use std::sync::{Arc, Mutex, Weak};
11use std::time::{Duration, Instant};
12
13use async_channel::{Receiver, Sender};
14use async_executor::LocalExecutor;
15use futures_lite::future::{self, FutureExt};
16
17use crate::error::{ShutdownOutcome, SpawnError};
18use crate::lifecycle::{Lifecycle, CLOSED, RUNNING};
19use crate::task::{BridgeCompletionGuard, BridgeDriver, Completion, Task};
20
21pub struct LocalDomain {
26 executor: LocalExecutor<'static>,
27 inbox: Receiver<InboxCommand>,
28 sender: Sender<InboxCommand>,
29 shared: Arc<Shared>,
30 _not_send_or_sync: PhantomData<Rc<()>>,
31}
32
33#[derive(Clone)]
35pub struct LocalSpawner {
36 sender: Sender<InboxCommand>,
37 shared: Weak<Shared>,
38}
39
40#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
47#[non_exhaustive]
48pub struct RunStats {
49 pub drive_steps: usize,
51 pub elapsed: Duration,
53 pub inbox_commands: usize,
55}
56
57struct Shared {
58 lifecycle: Lifecycle,
59 gate: Mutex<()>,
61 accepted_tasks: AtomicUsize,
62}
63
64impl Shared {
65 fn complete_one(&self) {
66 let previous = self.accepted_tasks.fetch_sub(1, Ordering::AcqRel);
67 debug_assert!(previous > 0, "local accepted task count underflow");
68 }
69}
70
71struct AcceptedGuard(Option<Arc<Shared>>);
72
73type RunCommand = Box<dyn FnOnce(&LocalExecutor<'static>) + Send + 'static>;
74
75impl AcceptedGuard {
76 fn new(shared: Arc<Shared>) -> Self {
77 Self(Some(shared))
78 }
79}
80
81impl Drop for AcceptedGuard {
82 fn drop(&mut self) {
83 if let Some(shared) = self.0.take() {
84 shared.complete_one();
85 }
86 }
87}
88
89struct InboxCommand {
92 run: Option<RunCommand>,
93 cancel: Option<Box<dyn FnOnce() + Send + 'static>>,
94}
95
96#[derive(Clone, Copy)]
97struct DriveProgress {
98 made_progress: bool,
99 inbox_commands: usize,
100}
101
102impl InboxCommand {
103 fn run(mut self, executor: &LocalExecutor<'static>, running: bool) {
104 if running {
105 if let Some(run) = self.run.take() {
106 run(executor);
107 }
108 } else if let Some(cancel) = self.cancel.take() {
109 cancel();
110 }
111 }
112
113 fn cancel(mut self) {
114 if let Some(cancel) = self.cancel.take() {
115 cancel();
116 }
117 }
118}
119
120impl LocalDomain {
121 pub fn new() -> Self {
123 let (sender, inbox) = async_channel::unbounded();
124 Self {
125 executor: LocalExecutor::new(),
126 inbox,
127 sender,
128 shared: Arc::new(Shared {
129 lifecycle: Lifecycle::new(),
130 gate: Mutex::new(()),
131 accepted_tasks: AtomicUsize::new(0),
132 }),
133 _not_send_or_sync: PhantomData,
134 }
135 }
136
137 pub fn spawner(&self) -> LocalSpawner {
139 LocalSpawner {
140 sender: self.sender.clone(),
141 shared: Arc::downgrade(&self.shared),
142 }
143 }
144
145 pub fn spawn_local<F, T>(&self, future: F) -> Result<Task<T>, SpawnError>
155 where
156 F: Future<Output = T> + 'static,
157 T: 'static,
158 {
159 let _gate = self
160 .shared
161 .gate
162 .lock()
163 .expect("local lifecycle mutex poisoned");
164 if self.shared.lifecycle.load() != RUNNING {
165 return Err(SpawnError::Closed);
166 }
167 self.shared.accepted_tasks.fetch_add(1, Ordering::AcqRel);
168 let guard = AcceptedGuard::new(Arc::clone(&self.shared));
169 let task = self.executor.spawn(async move {
170 let _guard = guard;
171 future.await
172 });
173 Ok(Task::direct(task))
174 }
175
176 pub fn is_empty(&self) -> bool {
178 self.shared.accepted_tasks.load(Ordering::Acquire) == 0
179 && self.inbox.is_empty()
180 && self.executor.is_empty()
181 }
182
183 pub fn try_tick(&self) -> bool {
185 self.try_drive_step().made_progress
186 }
187
188 pub fn run_n(&self, max_steps: usize) -> usize {
194 let mut drive_steps = 0;
195 while drive_steps < max_steps {
196 if !self.try_drive_step().made_progress {
197 break;
198 }
199 drive_steps += 1;
200 }
201 drive_steps
202 }
203
204 pub fn run_for(&self, budget: Duration) -> RunStats {
210 let started = Instant::now();
211 let mut stats = RunStats::default();
212 while started.elapsed() < budget {
213 let progress = self.try_drive_step();
214 if !progress.made_progress {
215 break;
216 }
217 stats.drive_steps += 1;
218 stats.inbox_commands += progress.inbox_commands;
219 }
220 stats.elapsed = started.elapsed();
221 stats
222 }
223
224 fn try_drive_step(&self) -> DriveProgress {
225 if let Ok(command) = self.inbox.try_recv() {
226 command.run(&self.executor, self.shared.lifecycle.load() != CLOSED);
227 let _ = self.executor.try_tick();
230 DriveProgress {
231 made_progress: true,
232 inbox_commands: 1,
233 }
234 } else {
235 DriveProgress {
236 made_progress: self.executor.try_tick(),
237 inbox_commands: 0,
238 }
239 }
240 }
241
242 pub async fn tick(&self) {
244 if self.try_tick() {
245 return;
246 }
247 future::race(async { self.executor.tick().await }, async {
248 if let Ok(command) = self.inbox.recv().await {
249 command.run(&self.executor, self.shared.lifecycle.load() != CLOSED);
250 }
251 })
252 .await;
253 }
254
255 pub async fn run<F: Future>(&self, future: F) -> F::Output {
257 future::race(future, async {
258 loop {
259 self.tick().await;
260 }
261 })
262 .await
263 }
264
265 pub async fn shutdown_graceful(mut self) {
267 self.begin_close();
268 while self.shared.accepted_tasks.load(Ordering::Acquire) != 0 {
269 self.tick().await;
270 }
271 self.shared.lifecycle.finish_close();
272 self.cancel_inbox();
273 }
274
275 pub async fn shutdown_until<D>(mut self, deadline: D) -> ShutdownOutcome
280 where
281 D: Future,
282 {
283 self.begin_close();
284 let drained = async {
285 while self.shared.accepted_tasks.load(Ordering::Acquire) != 0 {
286 self.tick().await;
287 }
288 };
289 let completed = future::race(
290 async {
291 drained.await;
292 true
293 },
294 async {
295 deadline.await;
296 false
297 },
298 )
299 .await;
300 if completed {
301 self.shared.lifecycle.finish_close();
302 self.cancel_inbox();
303 ShutdownOutcome::Completed
304 } else {
305 let remaining = self.shared.accepted_tasks.load(Ordering::Acquire);
306 self.shutdown_now_inner();
307 ShutdownOutcome::TimedOut {
308 remaining_tasks: remaining,
309 }
310 }
311 }
312
313 pub fn shutdown_now(mut self) {
315 self.shutdown_now_inner();
316 }
317
318 fn begin_close(&self) {
319 let _gate = self
320 .shared
321 .gate
322 .lock()
323 .expect("local lifecycle mutex poisoned");
324 self.shared.lifecycle.begin_close();
325 }
326
327 fn cancel_inbox(&mut self) {
328 while let Ok(command) = self.inbox.try_recv() {
329 command.cancel();
330 }
331 }
332
333 fn shutdown_now_inner(&mut self) {
334 self.begin_close();
335 self.cancel_inbox();
336 self.shared.lifecycle.finish_close();
337 }
338}
339
340impl Drop for LocalDomain {
341 fn drop(&mut self) {
342 self.shutdown_now_inner();
343 }
344}
345
346impl LocalSpawner {
347 pub fn spawn<F, T>(&self, future: F) -> Result<Task<T>, SpawnError>
358 where
359 F: Future<Output = T> + Send + 'static,
360 T: Send + 'static,
361 {
362 let Some(shared) = self.shared.upgrade() else {
363 return Err(SpawnError::Closed);
364 };
365 let _gate = shared.gate.lock().expect("local lifecycle mutex poisoned");
366 if shared.lifecycle.load() != RUNNING {
367 return Err(SpawnError::Closed);
368 }
369
370 shared.accepted_tasks.fetch_add(1, Ordering::AcqRel);
371 let completed_shared = Arc::clone(&shared);
372 let (task, driver) = Task::bridge(move || completed_shared.complete_one());
373 let command = remote_command(future, driver);
374 match self.sender.try_send(command) {
375 Ok(()) => Ok(task),
376 Err(error) => {
377 error.into_inner().cancel();
378 Err(SpawnError::Closed)
379 }
380 }
381 }
382
383 pub fn dispatch<F>(&self, callback: F) -> Result<(), SpawnError>
398 where
399 F: FnOnce() + Send + 'static,
400 {
401 self.dispatch_future(async move { callback() })
402 }
403
404 pub fn dispatch_future<F>(&self, future: F) -> Result<(), SpawnError>
419 where
420 F: Future<Output = ()> + Send + 'static,
421 {
422 let Some(shared) = self.shared.upgrade() else {
423 return Err(SpawnError::Closed);
424 };
425 let _gate = shared.gate.lock().expect("local lifecycle mutex poisoned");
426 if shared.lifecycle.load() != RUNNING {
427 return Err(SpawnError::Closed);
428 }
429
430 shared.accepted_tasks.fetch_add(1, Ordering::AcqRel);
431 let guard = AcceptedGuard::new(Arc::clone(&shared));
432 let command = dispatch_command(future, guard);
433 match self.sender.try_send(command) {
434 Ok(()) => Ok(()),
435 Err(error) => {
436 error.into_inner().cancel();
437 Err(SpawnError::Closed)
438 }
439 }
440 }
441}
442
443fn dispatch_command<F>(future: F, guard: AcceptedGuard) -> InboxCommand
444where
445 F: Future<Output = ()> + Send + 'static,
446{
447 InboxCommand {
448 run: Some(Box::new(move |executor| {
449 executor
450 .spawn(async move {
451 let _guard = guard;
452 let _ = AssertUnwindSafe(future).catch_unwind().await;
453 })
454 .detach();
455 })),
456 cancel: None,
459 }
460}
461
462fn remote_command<F, T>(future: F, driver: BridgeDriver<T>) -> InboxCommand
463where
464 F: Future<Output = T> + Send + 'static,
465 T: Send + 'static,
466{
467 let state = Arc::new(Mutex::new(Some((future, driver))));
468 let run_state = Arc::clone(&state);
469 let cancel_state = Arc::clone(&state);
470 InboxCommand {
471 run: Some(Box::new(move |executor| {
472 let Some((future, driver)) = run_state
473 .lock()
474 .expect("remote command mutex poisoned")
475 .take()
476 else {
477 return;
478 };
479 if driver.is_cancel_requested() {
480 driver.complete(Completion::Cancelled);
481 return;
482 }
483 executor
484 .spawn(async move {
485 let guard = BridgeCompletionGuard::new(driver.clone());
486 let user = async move {
487 match AssertUnwindSafe(future).catch_unwind().await {
488 Ok(value) => Completion::Completed(value),
489 Err(payload) => Completion::Panicked(payload),
490 }
491 };
492 let cancelled = async move {
493 driver.clone().cancelled().await;
494 Completion::Cancelled
495 };
496 guard.finish(user.race(cancelled).await);
497 })
498 .detach();
499 })),
500 cancel: Some(Box::new(move || {
501 if let Some((_future, driver)) = cancel_state
502 .lock()
503 .expect("remote command mutex poisoned")
504 .take()
505 {
506 driver.complete(Completion::Cancelled);
507 }
508 })),
509 }
510}
511
512impl Default for LocalDomain {
513 fn default() -> Self {
514 Self::new()
515 }
516}
517
518#[allow(dead_code)]
520fn _local_domain_is_not_send_or_sync(_: &RefCell<LocalDomain>, _: NonZeroUsize) {}