Merge pull request #1223 from DioxusLabs/jk/fix-use-future

Fix race condition in use_future by using use_state directly.
This commit is contained in:
Jonathan Kelley 2023-07-19 20:01:15 -07:00 committed by GitHub
commit 79909060cd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -1,12 +1,8 @@
#![allow(missing_docs)] #![allow(missing_docs)]
use dioxus_core::{ScopeState, TaskId}; use dioxus_core::{ScopeState, TaskId};
use std::{ use std::{any::Any, cell::Cell, future::Future, rc::Rc, sync::Arc};
any::Any,
cell::{Cell, RefCell}, use crate::{use_state, UseState};
future::{Future, IntoFuture},
rc::Rc,
sync::Arc,
};
/// A future that resolves to a value. /// A future that resolves to a value.
/// ///
@ -31,43 +27,30 @@ where
F: Future<Output = T> + 'static, F: Future<Output = T> + 'static,
D: UseFutureDep, D: UseFutureDep,
{ {
let val = use_state(cx, || None);
let state = cx.use_hook(move || UseFuture { let state = cx.use_hook(move || UseFuture {
update: cx.schedule_update(), update: cx.schedule_update(),
needs_regen: Cell::new(true), needs_regen: Cell::new(true),
values: Default::default(), state: val.clone(),
task: Cell::new(None), task: Default::default(),
dependencies: Vec::new(), dependencies: Vec::new(),
waker: Default::default(),
}); });
*state.waker.borrow_mut() = None;
if dependencies.clone().apply(&mut state.dependencies) || state.needs_regen.get() { if dependencies.clone().apply(&mut state.dependencies) || state.needs_regen.get() {
// We don't need regen anymore // kill the old one, if it exists
state.needs_regen.set(false); if let Some(task) = state.task.take() {
cx.remove_future(task);
}
// Create the new future // Create the new future
let fut = future(dependencies.out()); let fut = future(dependencies.out());
let val = val.clone();
// Clone in our cells let task = state.task.clone();
let values = state.values.clone();
let schedule_update = state.update.clone();
let waker = state.waker.clone();
// Cancel the current future
if let Some(current) = state.task.take() {
cx.remove_future(current);
}
state.task.set(Some(cx.push_future(async move { state.task.set(Some(cx.push_future(async move {
let res = fut.await; val.set(Some(fut.await));
values.borrow_mut().push(Box::leak(Box::new(res))); task.take();
// if there's a waker, we dont re-render the component. Instead we just progress that future
match waker.borrow().as_ref() {
Some(waker) => waker.wake_by_ref(),
None => schedule_update(),
}
}))); })));
} }
@ -80,21 +63,12 @@ pub enum FutureState<'a, T> {
Regenerating(&'a T), // the old value Regenerating(&'a T), // the old value
} }
pub struct UseFuture<T> { pub struct UseFuture<T: 'static> {
update: Arc<dyn Fn()>, update: Arc<dyn Fn()>,
needs_regen: Cell<bool>, needs_regen: Cell<bool>,
task: Cell<Option<TaskId>>, task: Rc<Cell<Option<TaskId>>>,
dependencies: Vec<Box<dyn Any>>, dependencies: Vec<Box<dyn Any>>,
waker: Rc<RefCell<Option<std::task::Waker>>>, state: UseState<Option<T>>,
values: Rc<RefCell<Vec<*mut T>>>,
}
impl<T> Drop for UseFuture<T> {
fn drop(&mut self) {
for value in self.values.take().into_iter() {
drop(unsafe { Box::from_raw(value) })
}
}
} }
pub enum UseFutureState<'a, T> { pub enum UseFutureState<'a, T> {
@ -120,30 +94,16 @@ impl<T> UseFuture<T> {
} }
} }
// clears the value in the future slot without starting the future over
pub fn clear(&self) -> Option<T> {
todo!()
// (self.update)();
// self.slot.replace(None)
}
// Manually set the value in the future slot without starting the future over // Manually set the value in the future slot without starting the future over
pub fn set(&self, _new_value: T) { pub fn set(&self, new_value: T) {
// self.slot.set(Some(new_value)); self.state.set(Some(new_value));
// self.needs_regen.set(true);
// (self.update)();
todo!()
} }
/// Return any value, even old values if the future has not yet resolved. /// Return any value, even old values if the future has not yet resolved.
/// ///
/// If the future has never completed, the returned value will be `None`. /// If the future has never completed, the returned value will be `None`.
pub fn value(&self) -> Option<&T> { pub fn value(&self) -> Option<&T> {
self.values self.state.current_val.as_ref().as_ref()
.borrow_mut()
.last()
.cloned()
.map(|x| unsafe { &*x })
} }
/// Get the ID of the future in Dioxus' internal scheduler /// Get the ID of the future in Dioxus' internal scheduler
@ -169,35 +129,6 @@ impl<T> UseFuture<T> {
} }
} }
impl<'a, T> IntoFuture for &'a UseFuture<T> {
type Output = &'a T;
type IntoFuture = UseFutureAwait<'a, T>;
fn into_future(self) -> Self::IntoFuture {
UseFutureAwait { hook: self }
}
}
pub struct UseFutureAwait<'a, T> {
hook: &'a UseFuture<T>,
}
impl<'a, T> Future for UseFutureAwait<'a, T> {
type Output = &'a T;
fn poll(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Self::Output> {
match self.hook.values.borrow_mut().last().cloned() {
Some(value) => std::task::Poll::Ready(unsafe { &*value }),
None => {
self.hook.waker.replace(Some(cx.waker().clone()));
std::task::Poll::Pending
}
}
}
}
pub trait UseFutureDep: Sized + Clone { pub trait UseFutureDep: Sized + Clone {
type Out; type Out;
fn out(&self) -> Self::Out; fn out(&self) -> Self::Out;
@ -343,10 +274,6 @@ mod tests {
let blah = "asd"; let blah = "asd";
}); });
let g2 = a.await;
let g = fut.await;
todo!() todo!()
} }
} }