1use std::cell::{Cell, RefCell};
13use std::collections::HashMap;
14use std::future::Future;
15use std::pin::Pin;
16use std::rc::Rc;
17use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
18
19use day_spec::present::{PresentResult, PresentSpec};
20
21use crate::with_tree;
22
23type LocalFuture = Pin<Box<dyn Future<Output = ()> + 'static>>;
24
25day_reactive::tls_slots! {
26 present;
27 static TASKS: RefCell<HashMap<u64, Option<LocalFuture>>> = RefCell::new(HashMap::new());
29 static NEXT_TASK: Cell<u64> = const { Cell::new(1) };
30 static SHUTTING_DOWN: Cell<bool> = const { Cell::new(false) };
31 static REWAKE: RefCell<Vec<u64>> = const { RefCell::new(Vec::new()) };
34 static PENDING: RefCell<HashMap<u64, PendingEntry>> = RefCell::new(HashMap::new());
35 static NEXT_REQ: Cell<u64> = const { Cell::new(1) };
36 static PRESENTATION_MODE: Cell<Option<PresentationMode>> = const { Cell::new(None) };
37}
38
39pub use day_spec::present::app_temp_dir;
42
43pub fn sleep(ms: u32) -> Sleep {
47 Sleep {
48 ms,
49 scheduled: false,
50 shared: std::sync::Arc::new(SleepShared {
51 done: std::sync::atomic::AtomicBool::new(false),
52 waker: std::sync::Mutex::new(None),
53 }),
54 }
55}
56
57struct SleepShared {
58 done: std::sync::atomic::AtomicBool,
59 waker: std::sync::Mutex<Option<Waker>>,
60}
61
62pub struct Sleep {
64 ms: u32,
65 scheduled: bool,
66 shared: std::sync::Arc<SleepShared>,
67}
68
69impl Future for Sleep {
70 type Output = ();
71
72 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
73 use std::sync::atomic::Ordering;
74 if self.shared.done.load(Ordering::Acquire) {
75 return Poll::Ready(());
76 }
77 if let Ok(mut w) = self.shared.waker.lock() {
78 *w = Some(cx.waker().clone());
79 }
80 if !self.scheduled {
81 self.scheduled = true;
82 let shared = self.shared.clone();
83 day_reactive::on_main_delayed(self.ms, move || {
84 shared.done.store(true, Ordering::Release);
85 if let Ok(mut w) = shared.waker.lock()
86 && let Some(w) = w.take()
87 {
88 w.wake();
89 }
90 });
91 }
92 Poll::Pending
93 }
94}
95
96struct PendingEntry {
97 shared: Rc<PendingShared>,
98 spec: PresentSpec,
99 native: bool,
100}
101
102struct PendingShared {
103 result: RefCell<Option<PresentResult>>,
104 waker: RefCell<Option<Waker>>,
105}
106
107#[derive(Clone, Copy)]
116pub struct TaskHandle {
117 id: u64,
118 _not_send: std::marker::PhantomData<*const ()>,
119}
120
121impl TaskHandle {
122 pub fn abort(self) {
125 let fut = TASKS.try_with(|t| t.borrow_mut().remove(&self.id));
131 drop(fut);
132 }
133
134 pub fn is_finished(self) -> bool {
136 !TASKS
137 .try_with(|t| t.borrow().contains_key(&self.id))
138 .unwrap_or(false)
139 }
140}
141
142pub fn task(fut: impl Future<Output = ()> + 'static) -> TaskHandle {
149 if SHUTTING_DOWN.try_with(Cell::get).unwrap_or(true) {
150 return TaskHandle {
151 id: 0,
152 _not_send: std::marker::PhantomData,
153 };
154 }
155 let id = NEXT_TASK.with(|c| {
156 let v = c.get();
157 c.set(v + 1);
158 v
159 });
160 TASKS.with(|t| t.borrow_mut().insert(id, Some(Box::pin(fut))));
161 poll_task(id);
162 TaskHandle {
163 id,
164 _not_send: std::marker::PhantomData,
165 }
166}
167
168pub(crate) fn shutdown_tasks() {
171 if SHUTTING_DOWN
172 .try_with(|closing| closing.replace(true))
173 .unwrap_or(true)
174 {
175 return;
176 }
177 let tasks = TASKS.with(|tasks| std::mem::take(&mut *tasks.borrow_mut()));
178 REWAKE.with(|wake| wake.borrow_mut().clear());
179 for future in tasks.into_values().flatten() {
180 if std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| drop(future))).is_err() {
181 log::warn!("an async task panicked during shutdown; continuing task cleanup");
182 day_reactive::recover_from_panic();
183 crate::notify_contained_panic();
184 }
185 }
186}
187
188fn poll_task(id: u64) {
189 if SHUTTING_DOWN.try_with(Cell::get).unwrap_or(true) {
190 return;
191 }
192 let fut = TASKS.with(|t| t.borrow_mut().get_mut(&id).and_then(|s| s.take()));
193 let Some(mut fut) = fut else {
194 let live = TASKS.with(|t| t.borrow().contains_key(&id));
198 if live {
199 REWAKE.with(|r| r.borrow_mut().push(id));
200 }
201 return;
202 };
203 let waker = task_waker(id);
204 let mut cx = Context::from_waker(&waker);
205 loop {
206 match fut.as_mut().poll(&mut cx) {
207 Poll::Ready(()) => {
208 TASKS.with(|t| {
209 t.borrow_mut().remove(&id);
210 });
211 REWAKE.with(|r| r.borrow_mut().retain(|w| *w != id));
212 return;
213 }
214 Poll::Pending => {
215 let again = REWAKE.with(|r| {
216 let mut r = r.borrow_mut();
217 let had = r.contains(&id);
218 r.retain(|w| *w != id);
219 had
220 });
221 if !again {
222 TASKS.with(|t| {
223 if let Some(slot) = t.borrow_mut().get_mut(&id) {
224 *slot = Some(fut);
225 }
226 });
227 return;
228 }
229 }
230 }
231 }
232}
233
234fn task_waker(id: u64) -> Waker {
236 unsafe { Waker::from_raw(raw_waker(id as usize as *const ())) }
239}
240
241fn raw_waker(data: *const ()) -> RawWaker {
242 RawWaker::new(data, &VTABLE)
243}
244
245static VTABLE: RawWakerVTable = RawWakerVTable::new(
246 raw_waker, |d| wake_task(d as usize as u64),
248 |d| wake_task(d as usize as u64),
249 |_| {},
250);
251
252fn wake_task(id: u64) {
253 day_reactive::on_main(move || poll_task(id));
254}
255
256#[derive(Clone, Copy, Debug, PartialEq, Eq)]
263pub enum PresentationMode {
264 Native,
265 Scripted,
266}
267
268pub fn presentation_mode() -> PresentationMode {
270 PRESENTATION_MODE.with(|slot| {
271 if let Some(mode) = slot.get() {
272 return mode;
273 }
274 let mode = if std::env::var("DAY_TEST_DIALOGS").as_deref() == Ok("scripted") {
275 PresentationMode::Scripted
276 } else {
277 PresentationMode::Native
278 };
279 slot.set(Some(mode));
280 mode
281 })
282}
283
284pub fn init_presentation_mode(mode: PresentationMode) {
287 PRESENTATION_MODE.with(|slot| {
288 if slot.get().is_none() {
289 slot.set(Some(mode));
290 }
291 });
292}
293
294pub fn set_presentation_mode(mode: PresentationMode) -> Result<(), &'static str> {
297 if mode != presentation_mode() && PENDING.with(|p| !p.borrow().is_empty()) {
298 return Err("answer pending dialogs with respond before changing dialog_mode");
299 }
300 PRESENTATION_MODE.with(|slot| slot.set(Some(mode)));
301 Ok(())
302}
303
304pub fn present(spec: PresentSpec) -> PresentFuture {
308 let req = NEXT_REQ.with(|c| {
309 let v = c.get();
310 c.set(v + 1);
311 v
312 });
313 PresentFuture {
314 req,
315 spec: Some(spec),
316 shared: Rc::new(PendingShared {
317 result: RefCell::new(None),
318 waker: RefCell::new(None),
319 }),
320 presented: false,
321 }
322}
323
324pub struct PresentFuture {
325 req: u64,
326 spec: Option<PresentSpec>,
327 shared: Rc<PendingShared>,
328 presented: bool,
329}
330
331impl Future for PresentFuture {
332 type Output = PresentResult;
333
334 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<PresentResult> {
335 if let Some(result) = self.shared.result.borrow_mut().take() {
336 return Poll::Ready(result);
337 }
338 if !self.presented {
339 self.presented = true;
340 let req = self.req;
341 let spec = self.spec.take().expect("present spec");
342 let native = presentation_mode() == PresentationMode::Native;
343 PENDING.with(|p| {
344 p.borrow_mut().insert(
345 req,
346 PendingEntry {
347 shared: self.shared.clone(),
348 spec: spec.clone(),
349 native,
350 },
351 )
352 });
353 if native {
354 with_tree(|t| t.present(req, &spec));
355 }
356 }
357 *self.shared.waker.borrow_mut() = Some(cx.waker().clone());
358 Poll::Pending
359 }
360}
361
362pub fn resolve_presentation(req: u64, result: PresentResult) {
365 let entry = PENDING.with(|p| p.borrow_mut().remove(&req));
366 if let Some(entry) = entry {
367 *entry.shared.result.borrow_mut() = Some(result);
368 if let Some(waker) = entry.shared.waker.borrow_mut().take() {
369 waker.wake();
370 }
371 }
372}
373
374pub fn respond_presentation(req: u64, result: PresentResult) -> bool {
380 let native = PENDING.with(|p| p.borrow().get(&req).map(|entry| entry.native));
381 let Some(native) = native else {
382 return false;
383 };
384 resolve_presentation(req, result);
385 if native {
386 with_tree(|t| t.dismiss(req));
387 }
388 true
389}
390
391pub fn pending_presentation() -> Option<(u64, PresentSpec)> {
393 PENDING.with(|p| {
394 p.borrow()
395 .iter()
396 .max_by_key(|(req, _)| **req)
397 .map(|(req, entry)| (*req, entry.spec.clone()))
398 })
399}
400
401#[cfg(test)]
406mod task_tests {
407 use super::*;
408 use std::cell::Cell;
409 use std::task::Waker;
410
411 #[test]
412 fn scripted_dialogs_preserve_answers_without_calling_the_toolkit() {
413 use day_spec::present::{ButtonRole, PresentButton};
414 let (toolkit, probe) = day_mock::MockToolkit::new();
415 crate::tree::install_tree(Box::new(crate::Tree::new(
416 toolkit,
417 day_mock::MockHandle(0),
418 day_spec::Size::new(100.0, 100.0),
419 )));
420 let cases = [
421 (
422 PresentSpec::Dialog {
423 title: "confirm".into(),
424 message: None,
425 buttons: vec![PresentButton {
426 label: "Yes".into(),
427 role: ButtonRole::Default,
428 }],
429 sheet: false,
430 },
431 PresentResult::Button(0),
432 ),
433 (
434 PresentSpec::Prompt {
435 title: "prompt".into(),
436 message: None,
437 placeholder: String::new(),
438 initial: String::new(),
439 ok: "OK".into(),
440 cancel: "Cancel".into(),
441 },
442 PresentResult::Text("Ada".into()),
443 ),
444 (
445 PresentSpec::OpenFile {
446 title: "open".into(),
447 filters: vec![],
448 multiple: false,
449 },
450 PresentResult::Files(vec!["fixture.txt".into()]),
451 ),
452 (
453 PresentSpec::OpenFile {
454 title: "open many".into(),
455 filters: vec![],
456 multiple: true,
457 },
458 PresentResult::Files(vec!["a.txt".into(), "b.txt".into()]),
459 ),
460 (
461 PresentSpec::OpenFolder {
462 title: "folder".into(),
463 },
464 PresentResult::Files(vec!["music".into()]),
465 ),
466 (
467 PresentSpec::SaveFile {
468 title: "save".into(),
469 suggested_name: "copy.txt".into(),
470 src_path: "staged.txt".into(),
471 filters: vec![],
472 },
473 PresentResult::Files(vec!["copy.txt".into()]),
474 ),
475 ];
476 let mut cx = Context::from_waker(Waker::noop());
477 for mode in [PresentationMode::Native, PresentationMode::Scripted] {
478 set_presentation_mode(mode).unwrap();
479 init_presentation_mode(PresentationMode::Native);
481 assert_eq!(presentation_mode(), mode);
482 for (spec, answer) in &cases {
483 for result in [answer.clone(), PresentResult::Dismissed] {
484 probe.clear_log();
485 let mut future = Box::pin(present(spec.clone()));
486 assert!(future.as_mut().poll(&mut cx).is_pending());
487 let (req, pending) = pending_presentation().unwrap();
488 assert_eq!(&pending, spec);
489 let other = if mode == PresentationMode::Native {
490 PresentationMode::Scripted
491 } else {
492 PresentationMode::Native
493 };
494 assert!(set_presentation_mode(other).is_err());
495 assert_eq!(presentation_mode(), mode);
496 assert!(respond_presentation(req, result.clone()));
497 resolve_presentation(req, PresentResult::Dismissed);
499 assert!(!respond_presentation(req, PresentResult::Dismissed));
500 assert_eq!(future.as_mut().poll(&mut cx), Poll::Ready(result));
501 assert!(pending_presentation().is_none());
502 let log = probe.log();
503 if mode == PresentationMode::Native {
504 assert_eq!(log.len(), 2, "{log:?}");
505 assert!(log[0].starts_with("present req="));
506 assert!(log[1].starts_with("dismiss req="));
507 } else {
508 assert!(
509 log.is_empty(),
510 "scripted requests reached the toolkit: {log:?}"
511 );
512 }
513 }
514 }
515 }
516 set_presentation_mode(PresentationMode::Native).unwrap();
517 crate::uninstall_tree();
518 }
519
520 fn init() {
524 day_reactive::install_main_poster(|f| f());
525 }
526
527 struct DropFlag(Rc<Cell<bool>>);
529 impl Drop for DropFlag {
530 fn drop(&mut self) {
531 self.0.set(true);
532 }
533 }
534
535 fn pending_forever(flag: Rc<Cell<bool>>) -> impl Future<Output = ()> {
536 let guard = DropFlag(flag);
537 std::future::poll_fn(move |_| {
538 let _ = &guard;
539 Poll::Pending
540 })
541 }
542
543 #[test]
544 fn task_runs_to_completion() {
545 init();
546 let ran = Rc::new(Cell::new(false));
547 let r = ran.clone();
548 let h = task(async move { r.set(true) });
549 assert!(ran.get());
550 assert!(h.is_finished());
551 }
552
553 #[test]
554 fn abort_drops_pending_future() {
555 init();
556 let dropped = Rc::new(Cell::new(false));
557 let h = task(pending_forever(dropped.clone()));
558 assert!(!h.is_finished());
559 assert!(!dropped.get());
560 h.abort();
561 assert!(dropped.get());
562 assert!(h.is_finished());
563 }
564
565 #[test]
566 fn abort_after_completion_is_noop() {
567 init();
568 let h = task(async {});
569 assert!(h.is_finished());
570 h.abort();
571 assert!(h.is_finished());
572 }
573
574 #[test]
578 fn abort_self_while_polling() {
579 init();
580 let handle: Rc<Cell<Option<TaskHandle>>> = Rc::new(Cell::new(None));
581 let dropped = Rc::new(Cell::new(false));
582 let waker_slot: Rc<RefCell<Option<Waker>>> = Rc::new(RefCell::new(None));
583
584 let h_cell = handle.clone();
585 let w_slot = waker_slot.clone();
586 let guard = DropFlag(dropped.clone());
587 let h = task(std::future::poll_fn(move |cx| {
588 let _ = &guard;
589 match h_cell.get() {
590 None => {
591 *w_slot.borrow_mut() = Some(cx.waker().clone());
592 Poll::Pending
593 }
594 Some(h) => {
595 h.abort();
596 Poll::Pending
597 }
598 }
599 }));
600 handle.set(Some(h));
601 assert!(!dropped.get());
602 let waker = waker_slot
603 .borrow_mut()
604 .take()
605 .expect("waker from first poll");
606 waker.wake(); assert!(dropped.get());
608 assert!(h.is_finished());
609 }
610
611 #[test]
614 fn abort_reentrancy_from_drop() {
615 init();
616 struct SpawnOnDrop;
617 impl Drop for SpawnOnDrop {
618 fn drop(&mut self) {
619 task(async {});
620 }
621 }
622 let guard = SpawnOnDrop;
623 let h = task(std::future::poll_fn(move |_| {
624 let _ = &guard;
625 Poll::<()>::Pending
626 }));
627 h.abort();
628 assert!(h.is_finished());
629 }
630
631 #[test]
632 fn termination_drops_tasks_before_reactive_tls_and_dependency_shutdown() {
633 const CHILD: &str = "DAY_TASK_SHUTDOWN_CHILD";
635 if std::env::var_os(CHILD).is_some() {
636 std::thread::spawn(|| {
637 init();
638 assert!(!crate::has_tree()); struct Updating(day_reactive::Signal<bool>);
640 impl Drop for Updating {
641 fn drop(&mut self) {
642 self.0.set(false);
643 }
644 }
645 let active = day_reactive::Signal::new(true);
646 let guard = Updating(active);
647 let handle = task(std::future::poll_fn(move |_| {
648 let _ = &guard;
649 Poll::Pending
650 }));
651 crate::on_lifecycle(day_spec::Lifecycle::WillTerminate, move || {
652 assert!(!active.get_untracked());
653 assert!(handle.is_finished());
654 });
655 crate::dispatch_lifecycle(day_spec::Lifecycle::WillTerminate);
656 crate::dispatch_lifecycle(day_spec::Lifecycle::WillTerminate);
657 assert!(!active.get_untracked());
658 })
659 .join()
660 .unwrap();
661 return;
662 }
663 let output = std::process::Command::new(std::env::current_exe().unwrap())
664 .args([
665 "--exact",
666 "present::task_tests::termination_drops_tasks_before_reactive_tls_and_dependency_shutdown",
667 "--nocapture",
668 ])
669 .env(CHILD, "1")
670 .output()
671 .unwrap();
672 assert!(
673 output.status.success(),
674 "{}",
675 String::from_utf8_lossy(&output.stderr)
676 );
677 assert!(String::from_utf8_lossy(&output.stdout).contains("1 passed"));
678 }
679
680 #[test]
681 fn shutdown_rejects_reentrant_tasks_and_late_wakes() {
682 init();
683 struct SpawnOnDrop(Rc<Cell<bool>>);
684 impl Drop for SpawnOnDrop {
685 fn drop(&mut self) {
686 let polled = self.0.clone();
687 let h = task(async move { polled.set(true) });
688 assert!(h.is_finished());
689 h.abort();
690 }
691 }
692 let polled = Rc::new(Cell::new(false));
693 let guard = SpawnOnDrop(polled.clone());
694 let wake = Rc::new(RefCell::new(None));
695 let saved = wake.clone();
696 let polls = Rc::new(Cell::new(0));
697 let count = polls.clone();
698 let h = task(std::future::poll_fn(move |cx| {
699 let _ = &guard;
700 count.set(count.get() + 1);
701 *saved.borrow_mut() = Some(cx.waker().clone());
702 Poll::Pending
703 }));
704 shutdown_tasks();
705 wake.borrow_mut().take().unwrap().wake();
706 shutdown_tasks();
707 assert!(h.is_finished());
708 assert!(!polled.get());
709 assert_eq!(polls.get(), 1);
710 assert!(TASKS.with(|tasks| tasks.borrow().is_empty()));
711 }
712
713 #[test]
714 fn shutdown_continues_after_a_task_destructor_panics() {
715 init();
716 struct Panics;
717 impl Drop for Panics {
718 fn drop(&mut self) {
719 panic!("synthetic task destructor failure");
720 }
721 }
722 let guard = Panics;
723 task(std::future::poll_fn(move |_| {
724 let _ = &guard;
725 Poll::Pending
726 }));
727 let dropped = Rc::new(Cell::new(false));
728 let handle = task(pending_forever(dropped.clone()));
729 shutdown_tasks();
730 assert!(dropped.get());
731 assert!(handle.is_finished());
732 assert!(TASKS.with(|tasks| tasks.borrow().is_empty()));
733 }
734}