fix memos during suspense
This commit is contained in:
1 parent
da4d9c70e8
commit
3d7f419636
6 files changed
+71
-17
No files matched your search
@@ -37,9 +37,9 @@ fn suspended_child() -> Element {
|
|||||||
panic!("Non-suspended task was polled");
|
panic!("Non-suspended task was polled");
|
||||||
});
|
});
|
||||||
|
|
||||||
// // Memos should still work like normal
|
// Memos should still work like normal
|
||||||
// let memo = use_memo(move || val * 2);
|
let memo = use_memo(move || val * 2);
|
||||||
// assert_eq!(memo, val * 2);
|
assert_eq!(memo, val * 2);
|
||||||
|
|
||||||
if val() < 3 {
|
if val() < 3 {
|
||||||
let task = spawn(async move {
|
let task = spawn(async move {
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ async fn running_async() {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn yield_now_works() {
|
async fn yield_now_works() {
|
||||||
thread_local! {
|
thread_local! {
|
||||||
static SEQUENCE: std::cell::RefCell<Vec<usize>> = std::cell::RefCell::new(Vec::new());
|
static SEQUENCE: std::cell::RefCell<Vec<usize>> = const { std::cell::RefCell::new(Vec::new()) };
|
||||||
}
|
}
|
||||||
|
|
||||||
fn app() -> Element {
|
fn app() -> Element {
|
||||||
@@ -88,7 +88,7 @@ async fn yield_now_works() {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn flushing() {
|
async fn flushing() {
|
||||||
thread_local! {
|
thread_local! {
|
||||||
static SEQUENCE: std::cell::RefCell<Vec<usize>> = std::cell::RefCell::new(Vec::new());
|
static SEQUENCE: std::cell::RefCell<Vec<usize>> = const { std::cell::RefCell::new(Vec::new()) };
|
||||||
static BROADCAST: (tokio::sync::broadcast::Sender<()>, tokio::sync::broadcast::Receiver<()>) = tokio::sync::broadcast::channel(1);
|
static BROADCAST: (tokio::sync::broadcast::Sender<()>, tokio::sync::broadcast::Receiver<()>) = tokio::sync::broadcast::channel(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -237,14 +237,17 @@ impl<T: 'static, S: Storage<T>> Writable for CopyValue<T, S> {
|
|||||||
S::try_map_mut(mut_, f)
|
S::try_map_mut(mut_, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
fn try_write(&mut self) -> Result<Self::Mut<T>, generational_box::BorrowMutError> {
|
fn try_write(&mut self) -> Result<Self::Mut<T>, generational_box::BorrowMutError> {
|
||||||
self.value.try_write()
|
self.value.try_write()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
fn write(&mut self) -> Self::Mut<T> {
|
fn write(&mut self) -> Self::Mut<T> {
|
||||||
self.value.write()
|
self.value.write()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
fn set(&mut self, value: T) {
|
fn set(&mut self, value: T) {
|
||||||
self.value.set(value);
|
self.value.set(value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use crate::write::Writable;
|
use crate::write::Writable;
|
||||||
use crate::{read::Readable, ReactiveContext, ReadableRef, Signal};
|
use crate::{read::Readable, ReactiveContext, ReadableRef, Signal};
|
||||||
use crate::{CopyValue, ReadOnlySignal};
|
use crate::{CopyValue, ReadOnlySignal};
|
||||||
|
use std::rc::Rc;
|
||||||
use std::{
|
use std::{
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
ops::Deref,
|
ops::Deref,
|
||||||
@@ -11,6 +12,33 @@ use std::{
|
|||||||
use dioxus_core::prelude::*;
|
use dioxus_core::prelude::*;
|
||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
use generational_box::UnsyncStorage;
|
use generational_box::UnsyncStorage;
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
|
|
||||||
|
/// A thread local that can only be read from the thread it was created on.
|
||||||
|
pub struct ThreadLocal<T> {
|
||||||
|
value: T,
|
||||||
|
owner: std::thread::ThreadId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> ThreadLocal<T> {
|
||||||
|
/// Create a new thread local.
|
||||||
|
pub fn new(value: T) -> Self {
|
||||||
|
ThreadLocal {
|
||||||
|
value,
|
||||||
|
owner: std::thread::current().id(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the value of the thread local.
|
||||||
|
pub fn get(&self) -> Option<&T> {
|
||||||
|
(self.owner == std::thread::current().id()).then_some(&self.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: This is safe because the thread local can only be read from the thread it was created on.
|
||||||
|
unsafe impl<T> Send for ThreadLocal<T> {}
|
||||||
|
unsafe impl<T> Sync for ThreadLocal<T> {}
|
||||||
|
|
||||||
struct UpdateInformation<T> {
|
struct UpdateInformation<T> {
|
||||||
dirty: Arc<AtomicBool>,
|
dirty: Arc<AtomicBool>,
|
||||||
callback: RefCell<Box<dyn FnMut() -> T>>,
|
callback: RefCell<Box<dyn FnMut() -> T>>,
|
||||||
@@ -41,11 +69,26 @@ impl<T: 'static> Memo<T> {
|
|||||||
let dirty = Arc::new(AtomicBool::new(true));
|
let dirty = Arc::new(AtomicBool::new(true));
|
||||||
let (tx, mut rx) = futures_channel::mpsc::unbounded();
|
let (tx, mut rx) = futures_channel::mpsc::unbounded();
|
||||||
|
|
||||||
|
let myself: Rc<OnceCell<Memo<T>>> = Rc::new(OnceCell::new());
|
||||||
|
let thread_local = ThreadLocal::new(myself.clone());
|
||||||
|
|
||||||
let callback = {
|
let callback = {
|
||||||
let dirty = dirty.clone();
|
let dirty = dirty.clone();
|
||||||
move || {
|
move || match thread_local.get() {
|
||||||
dirty.store(true, std::sync::atomic::Ordering::Relaxed);
|
Some(memo) => match memo.get() {
|
||||||
tx.unbounded_send(()).unwrap();
|
Some(memo) => {
|
||||||
|
memo.recompute();
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
tracing::error!("Memo was not initialized in the same thread it was created in. This is likely a bug in dioxus");
|
||||||
|
dirty.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
let _ = tx.unbounded_send(());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
dirty.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
let _ = tx.unbounded_send(());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let rc = ReactiveContext::new_with_callback(
|
let rc = ReactiveContext::new_with_callback(
|
||||||
@@ -55,8 +98,9 @@ impl<T: 'static> Memo<T> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Create a new signal in that context, wiring up its dependencies and subscribers
|
// Create a new signal in that context, wiring up its dependencies and subscribers
|
||||||
let value = rc.run_in(&mut f);
|
let mut recompute = move || rc.run_in(&mut f);
|
||||||
let recompute = RefCell::new(Box::new(f) as Box<dyn FnMut() -> T>);
|
let value = recompute();
|
||||||
|
let recompute = RefCell::new(Box::new(recompute) as Box<dyn FnMut() -> T>);
|
||||||
let update = CopyValue::new(UpdateInformation {
|
let update = CopyValue::new(UpdateInformation {
|
||||||
dirty,
|
dirty,
|
||||||
callback: recompute,
|
callback: recompute,
|
||||||
@@ -67,9 +111,12 @@ impl<T: 'static> Memo<T> {
|
|||||||
inner: state,
|
inner: state,
|
||||||
update,
|
update,
|
||||||
};
|
};
|
||||||
|
let _ = myself.set(memo);
|
||||||
|
|
||||||
spawn(async move {
|
spawn(async move {
|
||||||
while rx.next().await.is_some() {
|
while rx.next().await.is_some() {
|
||||||
|
// Remove any pending updates
|
||||||
|
while rx.try_next().is_ok() {}
|
||||||
memo.recompute();
|
memo.recompute();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -78,6 +125,7 @@ impl<T: 'static> Memo<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Rerun the computation and update the value of the memo if the result has changed.
|
/// Rerun the computation and update the value of the memo if the result has changed.
|
||||||
|
#[tracing::instrument(skip(self))]
|
||||||
fn recompute(&self)
|
fn recompute(&self)
|
||||||
where
|
where
|
||||||
T: PartialEq,
|
T: PartialEq,
|
||||||
@@ -89,8 +137,7 @@ impl<T: 'static> Memo<T> {
|
|||||||
if new_value != *peak {
|
if new_value != *peak {
|
||||||
drop(peak);
|
drop(peak);
|
||||||
let mut copy = self.inner;
|
let mut copy = self.inner;
|
||||||
let mut write = copy.write();
|
copy.set(new_value);
|
||||||
*write = new_value;
|
|
||||||
update_write
|
update_write
|
||||||
.dirty
|
.dirty
|
||||||
.store(false, std::sync::atomic::Ordering::Relaxed);
|
.store(false, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
|||||||
@@ -24,10 +24,12 @@ thread_local! {
|
|||||||
|
|
||||||
impl std::fmt::Display for ReactiveContext {
|
impl std::fmt::Display for ReactiveContext {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
let read = self.inner.read();
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
return write!(f, "ReactiveContext created at {}", read.origin);
|
{
|
||||||
#[cfg(not(debug_assertions))]
|
if let Ok(read) = self.inner.try_read() {
|
||||||
|
return write!(f, "ReactiveContext created at {}", read.origin);
|
||||||
|
}
|
||||||
|
}
|
||||||
write!(f, "ReactiveContext")
|
write!(f, "ReactiveContext")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,8 +150,10 @@ impl<T: 'static, S: Storage<SignalData<T>>> Signal<T, S> {
|
|||||||
{
|
{
|
||||||
let inner = self.inner.read();
|
let inner = self.inner.read();
|
||||||
|
|
||||||
let mut subscribers = inner.subscribers.lock().unwrap();
|
// We cannot hold the subscribers lock while calling mark_dirty, because mark_dirty can run user code which may cause a new subscriber to be added. If we hold the lock, we will deadlock.
|
||||||
subscribers.retain(|reactive_context| reactive_context.mark_dirty())
|
let mut subscribers = std::mem::take(&mut *inner.subscribers.lock().unwrap());
|
||||||
|
subscribers.retain(|reactive_context| reactive_context.mark_dirty());
|
||||||
|
*inner.subscribers.lock().unwrap() = subscribers;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user