123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220 |
- //! Verify that tasks get polled by the virtualdom properly, and that we escape wait_for_work safely
- use std::{sync::atomic::AtomicUsize, time::Duration};
- use dioxus::prelude::*;
- async fn run_vdom(app: fn() -> Element) {
- let mut dom = VirtualDom::new(app);
- dom.rebuild(&mut dioxus_core::NoOpMutations);
- tokio::select! {
- _ = dom.wait_for_work() => {}
- _ = tokio::time::sleep(Duration::from_millis(500)) => {}
- };
- }
- #[tokio::test]
- async fn running_async() {
- static POLL_COUNT: AtomicUsize = AtomicUsize::new(0);
- fn app() -> Element {
- use_hook(|| {
- spawn(async {
- for x in 0..10 {
- tokio::time::sleep(Duration::from_micros(50)).await;
- POLL_COUNT.fetch_add(x, std::sync::atomic::Ordering::Relaxed);
- }
- });
- spawn(async {
- for x in 0..10 {
- tokio::time::sleep(Duration::from_micros(25)).await;
- POLL_COUNT.fetch_add(x * 2, std::sync::atomic::Ordering::Relaxed);
- }
- });
- });
- rsx!({})
- }
- run_vdom(app).await;
- // By the time the tasks are finished, we should've accumulated ticks from two tasks
- // Be warned that by setting the delay to too short, tokio might not schedule in the tasks
- assert_eq!(
- POLL_COUNT.fetch_add(0, std::sync::atomic::Ordering::Relaxed),
- 135
- );
- }
- #[tokio::test]
- async fn spawn_forever_persists() {
- use std::sync::atomic::Ordering;
- static POLL_COUNT: AtomicUsize = AtomicUsize::new(0);
- fn app() -> Element {
- if generation() > 0 {
- rsx!(div {})
- } else {
- needs_update();
- rsx!(Child {})
- }
- }
- #[component]
- fn Child() -> Element {
- spawn_forever(async move {
- for _ in 0..10 {
- POLL_COUNT.fetch_add(1, Ordering::Relaxed);
- tokio::time::sleep(Duration::from_millis(50)).await;
- }
- });
- rsx!(div {})
- }
- let mut dom = VirtualDom::new(app);
- dom.rebuild(&mut dioxus_core::NoOpMutations);
- dom.render_immediate(&mut dioxus_core::NoOpMutations);
- tokio::select! {
- _ = dom.wait_for_work() => {}
- // We intentionally wait a bit longer than 50ms*10 to make sure the test has time to finish
- // Without the extra time, the test can fail on windows
- _ = tokio::time::sleep(Duration::from_millis(1000)) => {}
- };
- // By the time the tasks are finished, we should've accumulated ticks from two tasks
- // Be warned that by setting the delay to too short, tokio might not schedule in the tasks
- assert_eq!(POLL_COUNT.load(Ordering::Relaxed), 10);
- }
- /// Prove that yield_now doesn't cause a deadlock
- #[tokio::test]
- async fn yield_now_works() {
- thread_local! {
- static SEQUENCE: std::cell::RefCell<Vec<usize>> = const { std::cell::RefCell::new(Vec::new()) };
- }
- fn app() -> Element {
- // these two tasks should yield to eachother
- use_hook(|| {
- spawn(async move {
- for _ in 0..10 {
- tokio::task::yield_now().await;
- SEQUENCE.with(|s| s.borrow_mut().push(1));
- }
- })
- });
- use_hook(|| {
- spawn(async move {
- for _ in 0..10 {
- tokio::task::yield_now().await;
- SEQUENCE.with(|s| s.borrow_mut().push(2));
- }
- })
- });
- rsx!({})
- }
- run_vdom(app).await;
- SEQUENCE.with(|s| assert_eq!(s.borrow().len(), 20));
- }
- /// Ensure that calling wait_for_flush waits for dioxus to finish its synchronous work
- #[tokio::test]
- async fn flushing() {
- thread_local! {
- static SEQUENCE: std::cell::RefCell<Vec<usize>> = const { std::cell::RefCell::new(Vec::new()) };
- static BROADCAST: (tokio::sync::broadcast::Sender<()>, tokio::sync::broadcast::Receiver<()>) = tokio::sync::broadcast::channel(1);
- }
- fn app() -> Element {
- if generation() > 0 {
- println!("App");
- SEQUENCE.with(|s| s.borrow_mut().push(0));
- }
- // The next two tasks mimic effects. They should only be run after the app has been rendered
- use_hook(|| {
- spawn(async move {
- let mut channel = BROADCAST.with(|b| b.1.resubscribe());
- for _ in 0..10 {
- wait_for_next_render().await;
- println!("Task 1 recved");
- channel.recv().await.unwrap();
- println!("Task 1");
- SEQUENCE.with(|s| s.borrow_mut().push(1));
- }
- })
- });
- use_hook(|| {
- spawn(async move {
- let mut channel = BROADCAST.with(|b| b.1.resubscribe());
- for _ in 0..10 {
- wait_for_next_render().await;
- println!("Task 2 recved");
- channel.recv().await.unwrap();
- println!("Task 2");
- SEQUENCE.with(|s| s.borrow_mut().push(2));
- }
- })
- });
- rsx! {}
- }
- let mut dom = VirtualDom::new(app);
- dom.rebuild(&mut dioxus_core::NoOpMutations);
- let fut = async {
- // Trigger the flush by waiting for work
- for i in 0..10 {
- BROADCAST.with(|b| b.0.send(()).unwrap());
- dom.mark_dirty(ScopeId(0));
- dom.wait_for_work().await;
- dom.render_immediate(&mut dioxus_core::NoOpMutations);
- println!("Flushed {}", i);
- }
- BROADCAST.with(|b| b.0.send(()).unwrap());
- dom.wait_for_work().await;
- };
- tokio::select! {
- _ = fut => {}
- _ = tokio::time::sleep(Duration::from_millis(500)) => {
- println!("Aborting due to timeout");
- }
- };
- SEQUENCE.with(|s| {
- let s = s.borrow();
- println!("{:?}", s);
- assert_eq!(s.len(), 30);
- // We need to check if every three elements look like [0, 1, 2] or [0, 2, 1]
- let mut has_seen_1 = false;
- for (i, &x) in s.iter().enumerate() {
- let stage = i % 3;
- if stage == 0 {
- assert_eq!(x, 0);
- } else if stage == 1 {
- assert!(x == 1 || x == 2);
- has_seen_1 = x == 1;
- } else if stage == 2 {
- if has_seen_1 {
- assert_eq!(x, 2);
- } else {
- assert_eq!(x, 1);
- }
- }
- }
- });
- }
|