Fix fullstack render server context (#2139)

* Fix fullstack render server context

* only set the server context while polling futures

---------

Co-authored-by: Evan Almloff <evanalmloff@gmail.com>
This commit is contained in:
Emil BomanandEvan Almloff authored and GitHub committed 2024-03-26 09:59:25 -05:00
1 parent e464294c66
commit fb396b0448
3 files changed
+26 -35

No files matched your search

+3 -7
View File
@@ -60,8 +60,6 @@ use axum::{
extract::State, extract::State,
http::{Request, Response, StatusCode}, http::{Request, Response, StatusCode},
response::IntoResponse, response::IntoResponse,
routing::{get, post},
Router,
}; };
use dioxus_lib::prelude::VirtualDom; use dioxus_lib::prelude::VirtualDom;
use futures_util::Future; use futures_util::Future;
@@ -69,9 +67,7 @@ use http::header::*;
use std::sync::Arc; use std::sync::Arc;
use crate::{ use crate::prelude::*;
prelude::*, render::SSRState, serve_config::ServeConfig, server_context::DioxusServerContext,
};
/// A extension trait with utilities for integrating Dioxus with your Axum router. /// A extension trait with utilities for integrating Dioxus with your Axum router.
pub trait DioxusRouterExt<S> { pub trait DioxusRouterExt<S> {
@@ -508,8 +504,8 @@ async fn handle_server_fns_inner(
.unwrap_or(false); .unwrap_or(false);
let referrer = req.headers().get(REFERER).cloned(); let referrer = req.headers().get(REFERER).cloned();
// actually run the server fn // actually run the server fn (which may use the server context)
let mut res = service.run(req).await; let mut res = ProvideServerContext::new(service.run(req), server_context.clone()).await;
// it it accepts text/html (i.e., is a plain form post) and doesn't already have a // it it accepts text/html (i.e., is a plain form post) and doesn't already have a
// Location set, then redirect to Referer // Location set, then redirect to Referer
+15 -18
View File
@@ -1,9 +1,7 @@
//! A shared pool of renderers for efficient server side rendering. //! A shared pool of renderers for efficient server side rendering.
use crate::render::dioxus_core::NoOpMutations; use crate::{render::dioxus_core::NoOpMutations, server_context::with_server_context};
use crate::server_context::SERVER_CONTEXT;
use dioxus_lib::prelude::VirtualDom;
use dioxus_ssr::{ use dioxus_ssr::{
incremental::{IncrementalRendererConfig, RenderFreshness, WrapBody}, incremental::{RenderFreshness, WrapBody},
Renderer, Renderer,
}; };
use std::future::Future; use std::future::Future;
@@ -53,7 +51,7 @@ impl SsrRendererPool {
}; };
match self { match self {
Self::Renderer(pool) => { Self::Renderer(pool) => {
let server_context = Box::new(server_context.clone()); let server_context = server_context.clone();
let mut renderer = pool.write().unwrap().pop().unwrap_or_else(pre_renderer); let mut renderer = pool.write().unwrap().pop().unwrap_or_else(pre_renderer);
let (tx, rx) = tokio::sync::oneshot::channel(); let (tx, rx) = tokio::sync::oneshot::channel();
@@ -61,15 +59,13 @@ impl SsrRendererPool {
spawn_platform(move || async move { spawn_platform(move || async move {
let mut vdom = virtual_dom_factory(); let mut vdom = virtual_dom_factory();
let mut to = WriteBuffer { buffer: Vec::new() }; let mut to = WriteBuffer { buffer: Vec::new() };
// before polling the future, we need to set the context
let prev_context = SERVER_CONTEXT.with(|ctx| ctx.replace(server_context));
// poll the future, which may call server_context() // poll the future, which may call server_context()
tracing::info!("Rebuilding vdom"); tracing::info!("Rebuilding vdom");
block_in_place(|| vdom.rebuild(&mut NoOpMutations)); with_server_context(server_context.clone(), || {
vdom.wait_for_suspense().await; block_in_place(|| vdom.rebuild(&mut NoOpMutations));
});
ProvideServerContext::new(vdom.wait_for_suspense(), server_context).await;
tracing::info!("Suspense resolved"); tracing::info!("Suspense resolved");
// after polling the future, we need to restore the context
SERVER_CONTEXT.with(|ctx| ctx.replace(prev_context));
if let Err(err) = wrapper.render_before_body(&mut *to) { if let Err(err) = wrapper.render_before_body(&mut *to) {
let _ = tx.send(Err(err)); let _ = tx.send(Err(err));
@@ -120,16 +116,17 @@ impl SsrRendererPool {
&mut *to, &mut *to,
|vdom| { |vdom| {
Box::pin(async move { Box::pin(async move {
// before polling the future, we need to set the context
let prev_context = SERVER_CONTEXT
.with(|ctx| ctx.replace(Box::new(server_context)));
// poll the future, which may call server_context() // poll the future, which may call server_context()
tracing::info!("Rebuilding vdom"); tracing::info!("Rebuilding vdom");
block_in_place(|| vdom.rebuild(&mut NoOpMutations)); with_server_context(server_context.clone(), || {
vdom.wait_for_suspense().await; block_in_place(|| vdom.rebuild(&mut NoOpMutations));
});
ProvideServerContext::new(
vdom.wait_for_suspense(),
server_context,
)
.await;
tracing::info!("Suspense resolved"); tracing::info!("Suspense resolved");
// after polling the future, we need to restore the context
SERVER_CONTEXT.with(|ctx| ctx.replace(prev_context));
}) })
}, },
&wrapper, &wrapper,
+8 -10
View File
@@ -35,7 +35,7 @@ impl Default for DioxusServerContext {
mod server_fn_impl { mod server_fn_impl {
use super::*; use super::*;
use std::sync::LockResult; use std::sync::LockResult;
use std::sync::{Arc, PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard}; use std::sync::{PoisonError, RwLockReadGuard, RwLockWriteGuard};
use anymap::{any::Any, Map}; use anymap::{any::Any, Map};
type SendSyncAnyMap = Map<dyn Any + Send + Sync + 'static>; type SendSyncAnyMap = Map<dyn Any + Send + Sync + 'static>;
@@ -159,22 +159,20 @@ pub async fn extract<E: FromServerContext<I>, I>() -> Result<E, E::Rejection> {
E::from_request(&server_context()).await E::from_request(&server_context()).await
} }
pub(crate) fn with_server_context<O>( pub(crate) fn with_server_context<O>(context: DioxusServerContext, f: impl FnOnce() -> O) -> O {
context: Box<DioxusServerContext>,
f: impl FnOnce() -> O,
) -> (O, Box<DioxusServerContext>) {
// before polling the future, we need to set the context // before polling the future, we need to set the context
let prev_context = SERVER_CONTEXT.with(|ctx| ctx.replace(context)); let prev_context = SERVER_CONTEXT.with(|ctx| ctx.replace(Box::new(context)));
// poll the future, which may call server_context() // poll the future, which may call server_context()
let result = f(); let result = f();
// after polling the future, we need to restore the context // after polling the future, we need to restore the context
(result, SERVER_CONTEXT.with(|ctx| ctx.replace(prev_context))) SERVER_CONTEXT.with(|ctx| ctx.replace(prev_context));
result
} }
/// A future that provides the server context to the inner future /// A future that provides the server context to the inner future
#[pin_project::pin_project] #[pin_project::pin_project]
pub struct ProvideServerContext<F: std::future::Future> { pub struct ProvideServerContext<F: std::future::Future> {
context: Option<Box<DioxusServerContext>>, context: Option<DioxusServerContext>,
#[pin] #[pin]
f: F, f: F,
} }
@@ -183,7 +181,7 @@ impl<F: std::future::Future> ProvideServerContext<F> {
/// Create a new future that provides the server context to the inner future /// Create a new future that provides the server context to the inner future
pub fn new(f: F, context: DioxusServerContext) -> Self { pub fn new(f: F, context: DioxusServerContext) -> Self {
Self { Self {
context: Some(Box::new(context)), context: Some(context),
f, f,
} }
} }
@@ -198,7 +196,7 @@ impl<F: std::future::Future> std::future::Future for ProvideServerContext<F> {
) -> std::task::Poll<Self::Output> { ) -> std::task::Poll<Self::Output> {
let this = self.project(); let this = self.project();
let context = this.context.take().unwrap(); let context = this.context.take().unwrap();
let (result, context) = with_server_context(context, || this.f.poll(cx)); let result = with_server_context(context.clone(), || this.f.poll(cx));
*this.context = Some(context); *this.context = Some(context);
result result
} }