diff --git a/packages/core/src/global_context.rs b/packages/core/src/global_context.rs index 9007a60b5..3c670a694 100644 --- a/packages/core/src/global_context.rs +++ b/packages/core/src/global_context.rs @@ -93,8 +93,10 @@ pub fn spawn(fut: impl Future + '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 + 'static) -> Option { - 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. diff --git a/packages/core/src/scope_context.rs b/packages/core/src/scope_context.rs index 623336263..431e9b714 100644 --- a/packages/core/src/scope_context.rs +++ b/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 + '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 { self.last_suspendable_task.set(Some(task)); diff --git a/packages/core/tests/task.rs b/packages/core/tests/task.rs index aa4cd8465..b72079b37 100644 --- a/packages/core/tests/task.rs +++ b/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() {