From ecc6fc728674167e02592032d2ff53aa6d28f8a1 Mon Sep 17 00:00:00 2001 From: Zak Stucke Date: Fri, 29 Aug 2025 17:37:47 +0300 Subject: [PATCH 1/4] Standardize ScopedFuture::new_untracked like untrack() and untrack_with_diagnostics() --- reactive_graph/src/actions/action.rs | 15 +++++----- .../async_derived/arc_async_derived.rs | 7 +++-- .../src/computed/async_derived/mod.rs | 28 +++++++++++++++++-- 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/reactive_graph/src/actions/action.rs b/reactive_graph/src/actions/action.rs index 4e05f65cd3..938fd04ee2 100644 --- a/reactive_graph/src/actions/action.rs +++ b/reactive_graph/src/actions/action.rs @@ -1,6 +1,7 @@ use crate::{ computed::{ArcMemo, Memo, ScopedFuture}, diagnostics::is_suppressing_resource_load, + graph::untrack, owner::{ArcStoredValue, ArenaItem, Owner}, send_wrapper_ext::SendOption, signal::{ArcMappedSignal, ArcRwSignal, MappedSignal, RwSignal}, @@ -207,10 +208,9 @@ where version: Default::default(), dispatched: Default::default(), action_fn: Arc::new(move |input| { - Box::pin( - owner - .with(|| ScopedFuture::new_untracked(action_fn(input))), - ) + Box::pin(owner.with(|| { + ScopedFuture::new_untracked(untrack(|| action_fn(input))) + })) }), #[cfg(any(debug_assertions, leptos_debuginfo))] defined_at: Location::caller(), @@ -385,10 +385,9 @@ where version: Default::default(), dispatched: Default::default(), action_fn: Arc::new(move |input| { - Box::pin(SendWrapper::new( - owner - .with(|| ScopedFuture::new_untracked(action_fn(input))), - )) + Box::pin(SendWrapper::new(owner.with(|| { + ScopedFuture::new_untracked(untrack(|| action_fn(input))) + }))) }), #[cfg(any(debug_assertions, leptos_debuginfo))] defined_at: Location::caller(), diff --git a/reactive_graph/src/computed/async_derived/arc_async_derived.rs b/reactive_graph/src/computed/async_derived/arc_async_derived.rs index affa685774..c967fbc9eb 100644 --- a/reactive_graph/src/computed/async_derived/arc_async_derived.rs +++ b/reactive_graph/src/computed/async_derived/arc_async_derived.rs @@ -521,9 +521,10 @@ impl ArcAsyncDerived { { let fun = move || { let fut = fun(); - let fut = ScopedFuture::new_untracked(async move { - SendOption::new(Some(fut.await)) - }); + let fut = + ScopedFuture::new_untracked_with_diagnostics(async move { + SendOption::new(Some(fut.await)) + }); #[cfg(feature = "sandboxed-arenas")] let fut = Sandboxed::new(fut); fut diff --git a/reactive_graph/src/computed/async_derived/mod.rs b/reactive_graph/src/computed/async_derived/mod.rs index 6b43dbd6f6..da79412ef8 100644 --- a/reactive_graph/src/computed/async_derived/mod.rs +++ b/reactive_graph/src/computed/async_derived/mod.rs @@ -25,6 +25,7 @@ pin_project! { pub struct ScopedFuture { pub owner: Owner, pub observer: Option, + pub diagnostics: bool, #[pin] pub fut: Fut, } @@ -39,6 +40,7 @@ impl ScopedFuture { Self { owner, observer, + diagnostics: true, fut, } } @@ -51,6 +53,19 @@ impl ScopedFuture { Self { owner, observer: None, + diagnostics: false, + fut, + } + } + + #[doc(hidden)] + #[track_caller] + pub fn new_untracked_with_diagnostics(fut: Fut) -> Self { + let owner = Owner::current().unwrap_or_default(); + Self { + owner, + observer: None, + diagnostics: true, fut, } } @@ -61,8 +76,17 @@ impl Future for ScopedFuture { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.project(); - this.owner - .with(|| this.observer.with_observer(|| this.fut.poll(cx))) + this.owner.with(|| { + this.observer.with_observer(|| { + #[cfg(debug_assertions)] + let _maybe_guard = if *this.diagnostics { + None + } else { + Some(crate::diagnostics::SpecialNonReactiveZone::enter()) + }; + this.fut.poll(cx) + }) + }) } } From 9fd23cf83f29505e37ef70147042c539bed7e1c8 Mon Sep 17 00:00:00 2001 From: Zak Stucke Date: Fri, 29 Aug 2025 22:20:39 +0300 Subject: [PATCH 2/4] CI --- reactive_graph/src/computed/async_derived/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reactive_graph/src/computed/async_derived/mod.rs b/reactive_graph/src/computed/async_derived/mod.rs index da79412ef8..a48594b490 100644 --- a/reactive_graph/src/computed/async_derived/mod.rs +++ b/reactive_graph/src/computed/async_derived/mod.rs @@ -25,7 +25,7 @@ pin_project! { pub struct ScopedFuture { pub owner: Owner, pub observer: Option, - pub diagnostics: bool, + diagnostics: bool, #[pin] pub fut: Fut, } From df1b64376678737ac92d55ebf74f4baa77afafc3 Mon Sep 17 00:00:00 2001 From: Zak Stucke Date: Sun, 31 Aug 2025 09:24:39 +0300 Subject: [PATCH 3/4] Don't break semver --- .../src/computed/async_derived/mod.rs | 55 +++++++++++-------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/reactive_graph/src/computed/async_derived/mod.rs b/reactive_graph/src/computed/async_derived/mod.rs index a48594b490..6ceeb430dd 100644 --- a/reactive_graph/src/computed/async_derived/mod.rs +++ b/reactive_graph/src/computed/async_derived/mod.rs @@ -25,7 +25,6 @@ pin_project! { pub struct ScopedFuture { pub owner: Owner, pub observer: Option, - diagnostics: bool, #[pin] pub fut: Fut, } @@ -40,53 +39,61 @@ impl ScopedFuture { Self { owner, observer, - diagnostics: true, fut, } } - /// Wraps the given `Future` by taking the current [`Owner`] re-setting it as the - /// active owner every time the inner `Future` is polled. Always untracks, i.e., clears - /// the active [`Observer`] when polled. - pub fn new_untracked(fut: Fut) -> Self { + #[doc(hidden)] + #[track_caller] + pub fn new_untracked_with_diagnostics(fut: Fut) -> Self { let owner = Owner::current().unwrap_or_default(); Self { owner, observer: None, - diagnostics: false, fut, } } +} + +impl Future for ScopedFuture { + type Output = Fut::Output; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + this.owner + .with(|| this.observer.with_observer(|| this.fut.poll(cx))) + } +} +pin_project! { #[doc(hidden)] - #[track_caller] - pub fn new_untracked_with_diagnostics(fut: Fut) -> Self { + pub struct __NonReactiveZoneFut { + #[pin] + fut: Fut, + } +} + +impl ScopedFuture<__NonReactiveZoneFut> { + /// Wraps the given `Future` by taking the current [`Owner`] re-setting it as the + /// active owner every time the inner `Future` is polled. Always untracks, i.e., clears + /// the active [`Observer`] when polled. + pub fn new_untracked(fut: Fut) -> Self { let owner = Owner::current().unwrap_or_default(); Self { owner, observer: None, - diagnostics: true, - fut, + fut: __NonReactiveZoneFut { fut }, } } } -impl Future for ScopedFuture { +impl Future for __NonReactiveZoneFut { type Output = Fut::Output; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.project(); - this.owner.with(|| { - this.observer.with_observer(|| { - #[cfg(debug_assertions)] - let _maybe_guard = if *this.diagnostics { - None - } else { - Some(crate::diagnostics::SpecialNonReactiveZone::enter()) - }; - this.fut.poll(cx) - }) - }) + #[cfg(debug_assertions)] + let _guard = crate::diagnostics::SpecialNonReactiveZone::enter(); + self.project().fut.poll(cx) } } From 422d0c88826d07efa61222bea32988c645b1978f Mon Sep 17 00:00:00 2001 From: Zak Stucke Date: Sun, 31 Aug 2025 13:14:51 +0300 Subject: [PATCH 4/4] Semver --- .../src/computed/async_derived/mod.rs | 61 +++++++++++-------- 1 file changed, 37 insertions(+), 24 deletions(-) diff --git a/reactive_graph/src/computed/async_derived/mod.rs b/reactive_graph/src/computed/async_derived/mod.rs index 6ceeb430dd..8d911e9389 100644 --- a/reactive_graph/src/computed/async_derived/mod.rs +++ b/reactive_graph/src/computed/async_derived/mod.rs @@ -43,11 +43,25 @@ impl ScopedFuture { } } + /// Wraps the given `Future` by taking the current [`Owner`] re-setting it as the + /// active owner every time the inner `Future` is polled. Always untracks, i.e., clears + /// the active [`Observer`] when polled. + pub fn new_untracked(fut: Fut) -> Self { + let owner = Owner::current().unwrap_or_default(); + Self { + owner, + observer: None, + fut, + } + } + #[doc(hidden)] #[track_caller] - pub fn new_untracked_with_diagnostics(fut: Fut) -> Self { + pub fn new_untracked_with_diagnostics( + fut: Fut, + ) -> ScopedFutureUntrackedWithDiagnostics { let owner = Owner::current().unwrap_or_default(); - Self { + ScopedFutureUntrackedWithDiagnostics { owner, observer: None, fut, @@ -60,40 +74,39 @@ impl Future for ScopedFuture { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.project(); - this.owner - .with(|| this.observer.with_observer(|| this.fut.poll(cx))) + this.owner.with(|| { + #[cfg(debug_assertions)] + let _maybe_guard = if this.observer.is_none() { + Some(crate::diagnostics::SpecialNonReactiveZone::enter()) + } else { + None + }; + this.observer.with_observer(|| this.fut.poll(cx)) + }) } } pin_project! { - #[doc(hidden)] - pub struct __NonReactiveZoneFut { + /// A [`Future`] wrapper that sets the [`Owner`] and [`Observer`] before polling the inner + /// `Future`, output of [`ScopedFuture::new_untracked_with_diagnostics`]. + /// + /// In leptos 0.9 this will be replaced with `ScopedFuture` itself. + #[derive(Clone)] + pub struct ScopedFutureUntrackedWithDiagnostics { + owner: Owner, + observer: Option, #[pin] fut: Fut, } } -impl ScopedFuture<__NonReactiveZoneFut> { - /// Wraps the given `Future` by taking the current [`Owner`] re-setting it as the - /// active owner every time the inner `Future` is polled. Always untracks, i.e., clears - /// the active [`Observer`] when polled. - pub fn new_untracked(fut: Fut) -> Self { - let owner = Owner::current().unwrap_or_default(); - Self { - owner, - observer: None, - fut: __NonReactiveZoneFut { fut }, - } - } -} - -impl Future for __NonReactiveZoneFut { +impl Future for ScopedFutureUntrackedWithDiagnostics { type Output = Fut::Output; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - #[cfg(debug_assertions)] - let _guard = crate::diagnostics::SpecialNonReactiveZone::enter(); - self.project().fut.poll(cx) + let this = self.project(); + this.owner + .with(|| this.observer.with_observer(|| this.fut.poll(cx))) } }