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 {
211 let started = Instant::now();
212 let mut stats = RunStats::default();
213 while started.elapsed() < budget {
214 let progress = self.try_drive_step();
215 if !progress.made_progress {
216 break;
217 }
218 stats.drive_steps += 1;
219 stats.inbox_commands += progress.inbox_commands;
220 }
221 stats.elapsed = started.elapsed();
222 stats
223 }
224
225 fn try_drive_step(&self) -> DriveProgress {
226 if let Ok(command) = self.inbox.try_recv() {
227 command.run(&self.executor, self.shared.lifecycle.load() != CLOSED);
228 let _ = self.executor.try_tick();
231 DriveProgress {
232 made_progress: true,
233 inbox_commands: 1,
234 }
235 } else {
236 DriveProgress {
237 made_progress: self.executor.try_tick(),
238 inbox_commands: 0,
239 }
240 }
241 }
242
243 pub async fn tick(&self) {
245 if self.try_tick() {
246 return;
247 }
248 future::race(async { self.executor.tick().await }, async {
249 if let Ok(command) = self.inbox.recv().await {
250 command.run(&self.executor, self.shared.lifecycle.load() != CLOSED);
251 }
252 })
253 .await;
254 }
255
256 pub async fn run<F: Future>(&self, future: F) -> F::Output {
258 future::race(future, async {
259 loop {
260 self.tick().await;
261 }
262 })
263 .await
264 }
265
266 pub async fn shutdown_graceful(mut self) {
268 self.begin_close();
269 while self.shared.accepted_tasks.load(Ordering::Acquire) != 0 {
270 self.tick().await;
271 }
272 self.shared.lifecycle.finish_close();
273 self.cancel_inbox();
274 }
275
276 pub async fn shutdown_until<D>(mut self, deadline: D) -> ShutdownOutcome
281 where
282 D: Future,
283 {
284 self.begin_close();
285 let drained = async {
286 while self.shared.accepted_tasks.load(Ordering::Acquire) != 0 {
287 self.tick().await;
288 }
289 };
290 let completed = future::race(
291 async {
292 drained.await;
293 true
294 },
295 async {
296 deadline.await;
297 false
298 },
299 )
300 .await;
301 if completed {
302 self.shared.lifecycle.finish_close();
303 self.cancel_inbox();
304 ShutdownOutcome::Completed
305 } else {
306 let remaining = self.shared.accepted_tasks.load(Ordering::Acquire);
307 self.shutdown_now_inner();
308 ShutdownOutcome::TimedOut {
309 remaining_tasks: remaining,
310 }
311 }
312 }
313
314 pub fn shutdown_now(mut self) {
316 self.shutdown_now_inner();
317 }
318
319 fn begin_close(&self) {
320 let _gate = self
321 .shared
322 .gate
323 .lock()
324 .expect("local lifecycle mutex poisoned");
325 self.shared.lifecycle.begin_close();
326 }
327
328 fn cancel_inbox(&mut self) {
329 while let Ok(command) = self.inbox.try_recv() {
330 command.cancel();
331 }
332 }
333
334 fn shutdown_now_inner(&mut self) {
335 self.begin_close();
336 self.cancel_inbox();
337 self.shared.lifecycle.finish_close();
338 }
339}
340
341impl Drop for LocalDomain {
342 fn drop(&mut self) {
343 self.shutdown_now_inner();
344 }
345}
346
347impl LocalSpawner {
348 pub fn spawn<F, T>(&self, future: F) -> Result<Task<T>, SpawnError>
359 where
360 F: Future<Output = T> + Send + 'static,
361 T: Send + 'static,
362 {
363 let Some(shared) = self.shared.upgrade() else {
364 return Err(SpawnError::Closed);
365 };
366 let _gate = shared.gate.lock().expect("local lifecycle mutex poisoned");
367 if shared.lifecycle.load() != RUNNING {
368 return Err(SpawnError::Closed);
369 }
370
371 shared.accepted_tasks.fetch_add(1, Ordering::AcqRel);
372 let completed_shared = Arc::clone(&shared);
373 let (task, driver) = Task::bridge(move || completed_shared.complete_one());
374 let command = remote_command(future, driver);
375 match self.sender.try_send(command) {
376 Ok(()) => Ok(task),
377 Err(error) => {
378 error.into_inner().cancel();
379 Err(SpawnError::Closed)
380 }
381 }
382 }
383
384 pub fn dispatch<F>(&self, callback: F) -> Result<(), SpawnError>
399 where
400 F: FnOnce() + Send + 'static,
401 {
402 self.dispatch_future(async move { callback() })
403 }
404
405 pub fn dispatch_future<F>(&self, future: F) -> Result<(), SpawnError>
420 where
421 F: Future<Output = ()> + Send + 'static,
422 {
423 let Some(shared) = self.shared.upgrade() else {
424 return Err(SpawnError::Closed);
425 };
426 let _gate = shared.gate.lock().expect("local lifecycle mutex poisoned");
427 if shared.lifecycle.load() != RUNNING {
428 return Err(SpawnError::Closed);
429 }
430
431 shared.accepted_tasks.fetch_add(1, Ordering::AcqRel);
432 let guard = AcceptedGuard::new(Arc::clone(&shared));
433 let command = dispatch_command(future, guard);
434 match self.sender.try_send(command) {
435 Ok(()) => Ok(()),
436 Err(error) => {
437 error.into_inner().cancel();
438 Err(SpawnError::Closed)
439 }
440 }
441 }
442}
443
444fn dispatch_command<F>(future: F, guard: AcceptedGuard) -> InboxCommand
445where
446 F: Future<Output = ()> + Send + 'static,
447{
448 InboxCommand {
449 run: Some(Box::new(move |executor| {
450 executor
451 .spawn(async move {
452 let _guard = guard;
453 let _ = AssertUnwindSafe(future).catch_unwind().await;
454 })
455 .detach();
456 })),
457 cancel: None,
460 }
461}
462
463fn remote_command<F, T>(future: F, driver: BridgeDriver<T>) -> InboxCommand
464where
465 F: Future<Output = T> + Send + 'static,
466 T: Send + 'static,
467{
468 let state = Arc::new(Mutex::new(Some((future, driver))));
469 let run_state = Arc::clone(&state);
470 let cancel_state = Arc::clone(&state);
471 InboxCommand {
472 run: Some(Box::new(move |executor| {
473 let Some((future, driver)) = run_state
474 .lock()
475 .expect("remote command mutex poisoned")
476 .take()
477 else {
478 return;
479 };
480 if driver.is_cancel_requested() {
481 driver.complete(Completion::Cancelled);
482 return;
483 }
484 executor
485 .spawn(async move {
486 let guard = BridgeCompletionGuard::new(driver.clone());
487 let user = async move {
488 match AssertUnwindSafe(future).catch_unwind().await {
489 Ok(value) => Completion::Completed(value),
490 Err(payload) => Completion::Panicked(payload),
491 }
492 };
493 let cancelled = async move {
494 driver.clone().cancelled().await;
495 Completion::Cancelled
496 };
497 guard.finish(user.race(cancelled).await);
498 })
499 .detach();
500 })),
501 cancel: Some(Box::new(move || {
502 if let Some((_future, driver)) = cancel_state
503 .lock()
504 .expect("remote command mutex poisoned")
505 .take()
506 {
507 driver.complete(Completion::Cancelled);
508 }
509 })),
510 }
511}
512
513impl Default for LocalDomain {
514 fn default() -> Self {
515 Self::new()
516 }
517}
518
519#[allow(dead_code)]
521fn _local_domain_is_not_send_or_sync(_: &RefCell<LocalDomain>, _: NonZeroUsize) {}