Pārlūkot izejas kodu

fix and test spawn_forever (#2216)

Evan Almloff 1 gadu atpakaļ
vecāks
revīzija
5df333fca2

+ 3 - 1
packages/core/src/global_context.rs

@@ -93,8 +93,10 @@ pub fn spawn(fut: impl Future<Output = ()> + 'static) -> Task {
 /// Spawn a future that Dioxus won't clean up when this component is unmounted
 ///
 /// This is good for tasks that need to be run after the component has been dropped.
+///
+/// **This will run the task in the root scope. Any calls to global methods inside the future (including `context`) will be run in the root scope.**
 pub fn spawn_forever(fut: impl Future<Output = ()> + 'static) -> Option<Task> {
-    Runtime::with_current_scope(|cx| cx.spawn_forever(fut))
+    Runtime::with_scope(ScopeId::ROOT, |cx| cx.spawn(fut))
 }
 
 /// Informs the scheduler that this task is no longer needed and should be removed.

+ 0 - 8
packages/core/src/scope_context.rs

@@ -265,14 +265,6 @@ impl Scope {
         id
     }
 
-    /// Spawn a future that Dioxus won't clean up when this component is unmounted
-    ///
-    /// This is good for tasks that need to be run after the component has been dropped.
-    pub fn spawn_forever(&self, fut: impl Future<Output = ()> + 'static) -> Task {
-        // The root scope will never be unmounted so we can just add the task at the top of the app
-        Runtime::with(|rt| rt.spawn(self.id, fut)).expect("Runtime to exist")
-    }
-
     /// Mark this component as suspended on a specific task and then return None
     pub fn suspend(&self, task: Task) -> Option<Element> {
         self.last_suspendable_task.set(Some(task));

+ 41 - 0
packages/core/tests/task.rs

@@ -49,6 +49,47 @@ async fn running_async() {
     );
 }
 
+#[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 {
+            loop {
+                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() => {}
+        _ = tokio::time::sleep(Duration::from_millis(500)) => {}
+    };
+
+    // 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() {