fix: require Suspend::new() to ensure the Future is properly scoped at creation time, not at render time

This commit is contained in:
Greg Johnston 2024-07-16 14:09:59 -04:00
parent b24ae7a5e3
commit 93e6456e19
3 changed files with 36 additions and 18 deletions

View file

@ -204,11 +204,11 @@ mod view_implementations {
type State = RenderEffectState<SuspendState<T, R>>;
fn build(self) -> Self::State {
(move || Suspend(async move { self.await })).build()
(move || Suspend::new(async move { self.await })).build()
}
fn rebuild(self, state: &mut Self::State) {
(move || Suspend(async move { self.await })).rebuild(state)
(move || Suspend::new(async move { self.await })).rebuild(state)
}
}
@ -239,7 +239,7 @@ mod view_implementations {
where
Self::Output<NewAttr>: RenderHtml<R>,
{
(move || Suspend(async move { self.await })).add_any_attr(attr)
(move || Suspend::new(async move { self.await })).add_any_attr(attr)
}
}
@ -258,7 +258,7 @@ mod view_implementations {
}
fn resolve(self) -> impl Future<Output = Self::AsyncOutput> + Send {
(move || Suspend(async move { self.await })).resolve()
(move || Suspend::new(async move { self.await })).resolve()
}
fn to_html_with_buf(
@ -267,7 +267,7 @@ mod view_implementations {
position: &mut Position,
escape: bool,
) {
(move || Suspend(async move { self.await }))
(move || Suspend::new(async move { self.await }))
.to_html_with_buf(buf, position, escape);
}
@ -279,7 +279,7 @@ mod view_implementations {
) where
Self: Sized,
{
(move || Suspend(async move { self.await }))
(move || Suspend::new(async move { self.await }))
.to_html_async_with_buf::<OUT_OF_ORDER>(buf, position, escape);
}
@ -288,7 +288,7 @@ mod view_implementations {
cursor: &Cursor<R>,
position: &PositionState,
) -> Self::State {
(move || Suspend(async move { self.await }))
(move || Suspend::new(async move { self.await }))
.hydrate::<FROM_SERVER>(cursor, position)
}
}

View file

@ -20,11 +20,13 @@ use std::{
pin_project! {
/// A [`Future`] wrapper that sets the [`Owner`] and [`Observer`] before polling the inner
/// `Future`.
#[derive(Clone)]
#[allow(missing_docs)]
pub struct ScopedFuture<Fut> {
owner: Option<Owner>,
observer: Option<AnySubscriber>,
pub owner: Option<Owner>,
pub observer: Option<AnySubscriber>,
#[pin]
fut: Fut,
pub fut: Fut,
}
}

View file

@ -21,7 +21,14 @@ use std::{cell::RefCell, fmt::Debug, future::Future, pin::Pin, rc::Rc};
/// A suspended `Future`, which can be used in the view.
#[derive(Clone)]
pub struct Suspend<Fut>(pub Fut);
pub struct Suspend<Fut>(pub ScopedFuture<Fut>); // TODO probably shouldn't be pub
impl<Fut> Suspend<Fut> {
/// Creates a new suspended view.
pub fn new(fut: Fut) -> Self {
Self(ScopedFuture::new(fut))
}
}
impl<Fut> Debug for Suspend<Fut> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@ -69,7 +76,7 @@ where
// poll the future once immediately
// if it's already available, start in the ready state
// otherwise, start with the fallback
let mut fut = Box::pin(ScopedFuture::new(self.0));
let mut fut = Box::pin(self.0);
let initial = fut.as_mut().now_or_never();
let initially_pending = initial.is_none();
let inner = Rc::new(RefCell::new(initial.build()));
@ -96,7 +103,7 @@ where
fn rebuild(self, state: &mut Self::State) {
// get a unique ID if there's a SuspenseContext
let fut = ScopedFuture::new(self.0);
let fut = self.0;
let id = use_context::<SuspenseContext>().map(|sc| sc.task_id());
// spawn the future, and rebuild the state when it resolves
@ -137,10 +144,19 @@ where
Self::Output<NewAttr>: RenderHtml<Rndr>,
{
let attr = attr.into_cloneable_owned();
Suspend(Box::pin(async move {
let this = self.0.await;
this.add_any_attr(attr)
}))
let ScopedFuture {
owner,
observer,
fut,
} = self.0;
Suspend(ScopedFuture {
owner,
observer,
fut: Box::pin(async move {
let this = fut.await;
this.add_any_attr(attr)
}),
})
}
}
@ -234,7 +250,7 @@ where
// poll the future once immediately
// if it's already available, start in the ready state
// otherwise, start with the fallback
let mut fut = Box::pin(ScopedFuture::new(self.0));
let mut fut = Box::pin(self.0);
let initial = fut.as_mut().now_or_never();
let initially_pending = initial.is_none();
let inner = Rc::new(RefCell::new(