fix: more robust arena sandboxing (closes #2995) (#3026)

This commit is contained in:
Greg Johnston
2024-09-26 19:48:04 -04:00
committed by GitHub
parent f2582b6ac9
commit d29433b98d
26 changed files with 225 additions and 113 deletions

View File

@@ -24,7 +24,7 @@ use std::{future::Future, panic::Location, pin::Pin, sync::Arc};
/// # use reactive_graph::actions::*;
/// # use reactive_graph::prelude::*;
/// # tokio_test::block_on(async move {
/// # any_spawner::Executor::init_tokio();
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
/// async fn send_new_todo_to_api(task: String) -> usize {
/// // do something...
@@ -135,7 +135,7 @@ where
/// # use reactive_graph::actions::*;
/// # use reactive_graph::prelude::*;
/// # tokio_test::block_on(async move {
/// # any_spawner::Executor::init_tokio();
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
/// let act = ArcAction::new(|n: &u8| {
/// let n = n.to_owned();
@@ -367,7 +367,7 @@ impl<I, O> ArcAction<I, O> {
/// # use reactive_graph::actions::*;
/// # use reactive_graph::prelude::*;
/// # tokio_test::block_on(async move {
/// # any_spawner::Executor::init_tokio();
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
/// let act = ArcAction::new(|n: &u8| {
/// let n = n.to_owned();
@@ -395,7 +395,7 @@ impl<I, O> ArcAction<I, O> {
/// # use reactive_graph::actions::*;
/// # use reactive_graph::prelude::*;
/// # tokio_test::block_on(async move {
/// # any_spawner::Executor::init_tokio();
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
/// let act = ArcAction::new(|n: &u8| {
/// let n = n.to_owned();
@@ -425,7 +425,7 @@ impl<I, O> ArcAction<I, O> {
/// # use reactive_graph::actions::*;
/// # use reactive_graph::prelude::*;
/// # tokio_test::block_on(async move {
/// # any_spawner::Executor::init_tokio();
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
/// let act = ArcAction::new(|n: &u8| {
/// let n = n.to_owned();
@@ -456,7 +456,7 @@ impl<I, O> ArcAction<I, O> {
/// # use reactive_graph::actions::*;
/// # use reactive_graph::prelude::*;
/// # tokio_test::block_on(async move {
/// # any_spawner::Executor::init_tokio();
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
/// let act = ArcAction::new(|n: &u8| {
/// let n = n.to_owned();
@@ -510,7 +510,7 @@ where
/// # use reactive_graph::actions::*;
/// # use reactive_graph::prelude::*;
/// # tokio_test::block_on(async move {
/// # any_spawner::Executor::init_tokio();
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
/// async fn send_new_todo_to_api(task: String) -> usize {
/// // do something...
@@ -561,7 +561,7 @@ where
/// function, because it is stored in [Action::input] as well.
///
/// ```rust
/// # use reactive_graph::actions::*;
/// # use reactive_graph::actions::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// // if there's a single argument, just use that
/// let action1 = Action::new(|input: &String| {
/// let input = input.clone();
@@ -607,7 +607,7 @@ where
/// # use reactive_graph::actions::*;
/// # use reactive_graph::prelude::*;
/// # tokio_test::block_on(async move {
/// # any_spawner::Executor::init_tokio();
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
/// let act = Action::new(|n: &u8| {
/// let n = n.to_owned();
@@ -721,7 +721,7 @@ where
/// # use reactive_graph::actions::*;
/// # use reactive_graph::prelude::*;
/// # tokio_test::block_on(async move {
/// # any_spawner::Executor::init_tokio();
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
/// let act = Action::new(|n: &u8| {
/// let n = n.to_owned();
@@ -752,7 +752,7 @@ where
/// # use reactive_graph::actions::*;
/// # use reactive_graph::prelude::*;
/// # tokio_test::block_on(async move {
/// # any_spawner::Executor::init_tokio();
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
/// let act = Action::new(|n: &u8| {
/// let n = n.to_owned();
@@ -791,7 +791,7 @@ where
/// # use reactive_graph::actions::*;
/// # use reactive_graph::prelude::*;
/// # tokio_test::block_on(async move {
/// # any_spawner::Executor::init_tokio();
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
/// let act = ArcAction::new(|n: &u8| {
/// let n = n.to_owned();
@@ -847,7 +847,7 @@ where
/// # use reactive_graph::actions::*;
/// # use reactive_graph::prelude::*;
/// # tokio_test::block_on(async move {
/// # any_spawner::Executor::init_tokio();
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
/// let act = Action::new(|n: &u8| {
/// let n = n.to_owned();
@@ -1009,7 +1009,7 @@ impl<I, O, S> Copy for Action<I, O, S> {}
/// # use reactive_graph::actions::*;
/// # use reactive_graph::prelude::*;
/// # tokio_test::block_on(async move {
/// # any_spawner::Executor::init_tokio();
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
/// let act = create_action(|n: &u8| {
/// let n = n.to_owned();

View File

@@ -24,7 +24,7 @@ use std::{fmt::Debug, future::Future, panic::Location, pin::Pin, sync::Arc};
/// # use reactive_graph::actions::*;
/// # use reactive_graph::prelude::*;
/// # tokio_test::block_on(async move {
/// # any_spawner::Executor::init_tokio();
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
/// async fn send_new_todo_to_api(task: String) -> usize {
/// // do something...
@@ -105,7 +105,7 @@ where
/// # use reactive_graph::actions::*;
/// # use reactive_graph::prelude::*;
/// # tokio_test::block_on(async move {
/// # any_spawner::Executor::init_tokio();
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
/// // if there's a single argument, just use that
/// let action1 = MultiAction::new(|input: &String| {
@@ -151,7 +151,7 @@ where
/// # use reactive_graph::actions::*;
/// # use reactive_graph::prelude::*;
/// # tokio_test::block_on(async move {
/// # any_spawner::Executor::init_tokio();
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
/// async fn send_new_todo_to_api(task: String) -> usize {
/// // do something...
@@ -202,7 +202,7 @@ where
/// # use reactive_graph::actions::*;
/// # use reactive_graph::prelude::*;
/// # tokio_test::block_on(async move {
/// # any_spawner::Executor::init_tokio();
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
/// async fn send_new_todo_to_api(task: String) -> usize {
/// // do something...
@@ -245,7 +245,7 @@ where
/// # use reactive_graph::actions::*;
/// # use reactive_graph::prelude::*;
/// # tokio_test::block_on(async move {
/// # any_spawner::Executor::init_tokio();
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
/// async fn send_new_todo_to_api(task: String) -> usize {
/// // do something...
@@ -286,7 +286,7 @@ where
/// # use reactive_graph::actions::*;
/// # use reactive_graph::prelude::*;
/// # tokio_test::block_on(async move {
/// # any_spawner::Executor::init_tokio();
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
/// async fn send_new_todo_to_api(task: String) -> usize {
/// // do something...
@@ -336,7 +336,7 @@ where
/// # use reactive_graph::actions::*;
/// # use reactive_graph::prelude::*;
/// # tokio_test::block_on(async move {
/// # any_spawner::Executor::init_tokio();
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
/// async fn send_new_todo_to_api(task: String) -> usize {
/// // do something...
@@ -403,7 +403,7 @@ impl<I, O> ArcMultiAction<I, O> {
/// # use reactive_graph::actions::*;
/// # use reactive_graph::prelude::*;
/// # tokio_test::block_on(async move {
/// # any_spawner::Executor::init_tokio();
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
/// // if there's a single argument, just use that
/// let action1 = ArcMultiAction::new(|input: &String| {
@@ -452,7 +452,7 @@ where
/// # use reactive_graph::actions::*;
/// # use reactive_graph::prelude::*;
/// # tokio_test::block_on(async move {
/// # any_spawner::Executor::init_tokio();
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
/// async fn send_new_todo_to_api(task: String) -> usize {
/// // do something...
@@ -529,7 +529,7 @@ where
/// # use reactive_graph::actions::*;
/// # use reactive_graph::prelude::*;
/// # tokio_test::block_on(async move {
/// # any_spawner::Executor::init_tokio();
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
/// async fn send_new_todo_to_api(task: String) -> usize {
/// // do something...
@@ -579,7 +579,7 @@ impl<I, O> ArcMultiAction<I, O> {
/// # use reactive_graph::actions::*;
/// # use reactive_graph::prelude::*;
/// # tokio_test::block_on(async move {
/// # any_spawner::Executor::init_tokio();
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
/// async fn send_new_todo_to_api(task: String) -> usize {
/// // do something...
@@ -609,7 +609,7 @@ impl<I, O> ArcMultiAction<I, O> {
/// # use reactive_graph::actions::*;
/// # use reactive_graph::prelude::*;
/// # tokio_test::block_on(async move {
/// # any_spawner::Executor::init_tokio();
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
/// async fn send_new_todo_to_api(task: String) -> usize {
/// // do something...

View File

@@ -34,7 +34,7 @@ pub use selector::*;
/// In the example below, setting an auth token will only trigger
/// the token signal, but none of the other derived signals.
/// ```
/// # use reactive_graph::prelude::*;
/// # use reactive_graph::prelude::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::effect::Effect;
/// # use reactive_graph::signal::RwSignal;
/// # use reactive_graph::computed::*;

View File

@@ -40,7 +40,7 @@ use std::{
///
/// ## Examples
/// ```
/// # use reactive_graph::prelude::*;
/// # use reactive_graph::prelude::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::computed::*;
/// # use reactive_graph::signal::signal;
/// # fn really_expensive_computation(value: i32) -> i32 { value };

View File

@@ -53,10 +53,10 @@ use std::{
/// ## Examples
/// ```rust
/// # use reactive_graph::computed::*;
/// # use reactive_graph::signal::*;
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::prelude::*;
/// # tokio_test::block_on(async move {
/// # any_spawner::Executor::init_tokio();
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
///
/// let signal1 = RwSignal::new(0);

View File

@@ -29,10 +29,10 @@ use std::{future::Future, ops::DerefMut, panic::Location};
/// ## Examples
/// ```rust
/// # use reactive_graph::computed::*;
/// # use reactive_graph::signal::*;
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::prelude::*;
/// # tokio_test::block_on(async move {
/// # any_spawner::Executor::init_tokio();
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
///
/// let signal1 = RwSignal::new(0);

View File

@@ -36,7 +36,7 @@ use std::{fmt::Debug, hash::Hash, panic::Location};
/// # use reactive_graph::effect::Effect;
/// # use reactive_graph::signal::signal;
/// # tokio_test::block_on(async move {
/// # any_spawner::Executor::init_tokio();
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # tokio::task::LocalSet::new().run_until(async {
/// # fn really_expensive_computation(value: i32) -> i32 { value };
/// let (value, set_value) = signal(0);
@@ -161,7 +161,7 @@ where
/// # use reactive_graph::effect::Effect;
/// # use reactive_graph::signal::signal;
/// # tokio_test::block_on(async move {
/// # any_spawner::Executor::init_tokio();
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # fn really_expensive_computation(value: i32) -> i32 { value };
/// let (value, set_value) = signal(0);
///

View File

@@ -19,13 +19,13 @@ use std::{
///
/// ```
/// # use reactive_graph::computed::*;
/// # use reactive_graph::signal::*;
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::prelude::*;
/// # use reactive_graph::effect::Effect;
/// # use reactive_graph::owner::StoredValue;
/// # use reactive_graph::owner::StoredValue; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # tokio_test::block_on(async move {
/// # tokio::task::LocalSet::new().run_until(async move {
/// # any_spawner::Executor::init_tokio();
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # let _guard = reactive_graph::diagnostics::SpecialNonReactiveZone::enter();
/// let a = RwSignal::new(0);
/// let is_selected = Selector::new(move || a.get());

View File

@@ -37,13 +37,13 @@ use std::{
///
/// ```
/// # use reactive_graph::computed::*;
/// # use reactive_graph::signal::*;
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::prelude::*;
/// # use reactive_graph::effect::Effect;
/// # use reactive_graph::owner::ArenaItem;
/// # tokio_test::block_on(async move {
/// # tokio::task::LocalSet::new().run_until(async move {
/// # any_spawner::Executor::init_tokio();
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// let a = RwSignal::new(0);
/// let b = RwSignal::new(0);
///
@@ -185,7 +185,7 @@ impl Effect<LocalStorage> {
/// # use reactive_graph::signal::signal;
/// # tokio_test::block_on(async move {
/// # tokio::task::LocalSet::new().run_until(async move {
/// # any_spawner::Executor::init_tokio();
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// #
/// let (num, set_num) = signal(0);
///
@@ -217,7 +217,7 @@ impl Effect<LocalStorage> {
/// # use reactive_graph::signal::signal;
/// # tokio_test::block_on(async move {
/// # tokio::task::LocalSet::new().run_until(async move {
/// # any_spawner::Executor::init_tokio();
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// #
/// let (num, set_num) = signal(0);
/// let (cb_num, set_cb_num) = signal(0);
@@ -256,7 +256,7 @@ impl Effect<LocalStorage> {
/// # use reactive_graph::signal::signal;
/// # tokio_test::block_on(async move {
/// # tokio::task::LocalSet::new().run_until(async move {
/// # any_spawner::Executor::init_tokio();
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// #
/// let (num, set_num) = signal(0);
///

View File

@@ -104,11 +104,11 @@ impl Observer {
///
/// ```rust
/// # use reactive_graph::computed::*;
/// # use reactive_graph::signal::*;
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::prelude::*;
/// # use reactive_graph::untrack;
/// # tokio_test::block_on(async move {
/// # any_spawner::Executor::init_tokio();
/// # any_spawner::Executor::init_tokio(); let owner = reactive_graph::owner::Owner::new(); owner.set();
/// let (a, set_a) = signal(0);
/// let (b, set_b) = signal(0);
/// let c = Memo::new(move |_| {

View File

@@ -14,6 +14,7 @@
//!
//! ```rust
//! # any_spawner::Executor::init_futures_executor();
//! # let owner = reactive_graph::owner::Owner::new(); owner.set();
//! use reactive_graph::{
//! computed::ArcMemo,
//! effect::Effect,

View File

@@ -20,6 +20,8 @@ mod stored_value;
use self::arena::Arena;
#[cfg(feature = "sandboxed-arenas")]
pub use arena::sandboxed::Sandboxed;
#[cfg(feature = "sandboxed-arenas")]
use arena::ArenaMap;
use arena::NodeId;
pub use arena_item::*;
pub use context::*;
@@ -120,6 +122,12 @@ impl Owner {
contexts: Default::default(),
cleanups: Default::default(),
children: Default::default(),
#[cfg(feature = "sandboxed-arenas")]
arena: parent
.as_ref()
.and_then(|parent| parent.upgrade())
.map(|parent| parent.read().or_poisoned().arena.clone())
.unwrap_or_default(),
})),
#[cfg(feature = "hydration")]
shared_context,
@@ -140,11 +148,10 @@ impl Owner {
/// Only one `SharedContext` needs to be created per request, and will be automatically shared
/// by any other `Owner`s created under this one.
#[cfg(feature = "hydration")]
#[track_caller]
pub fn new_root(
shared_context: Option<Arc<dyn SharedContext + Send + Sync>>,
) -> Self {
Arena::enter_new();
let this = Self {
inner: Arc::new(RwLock::new(OwnerInner {
parent: None,
@@ -152,17 +159,21 @@ impl Owner {
contexts: Default::default(),
cleanups: Default::default(),
children: Default::default(),
#[cfg(feature = "sandboxed-arenas")]
arena: Default::default(),
})),
#[cfg(feature = "hydration")]
shared_context,
};
OWNER.with_borrow_mut(|owner| *owner = Some(this.clone()));
this.set();
this
}
/// Creates a new `Owner` that is the child of the current `Owner`, if any.
pub fn child(&self) -> Self {
let parent = Some(Arc::downgrade(&self.inner));
#[cfg(feature = "sandboxed-arenas")]
let arena = self.inner.read().or_poisoned().arena.clone();
let child = Self {
inner: Arc::new(RwLock::new(OwnerInner {
parent,
@@ -170,6 +181,8 @@ impl Owner {
contexts: Default::default(),
cleanups: Default::default(),
children: Default::default(),
#[cfg(feature = "sandboxed-arenas")]
arena,
})),
#[cfg(feature = "hydration")]
shared_context: self.shared_context.clone(),
@@ -185,6 +198,8 @@ impl Owner {
/// Sets this as the current `Owner`.
pub fn set(&self) {
OWNER.with_borrow_mut(|owner| *owner = Some(self.clone()));
#[cfg(feature = "sandboxed-arenas")]
Arena::set(&self.inner.read().or_poisoned().arena);
}
/// Runs the given function with this as the current `Owner`.
@@ -194,6 +209,8 @@ impl Owner {
mem::replace(&mut *o.borrow_mut(), Some(self.clone()))
})
};
#[cfg(feature = "sandboxed-arenas")]
Arena::set(&self.inner.read().or_poisoned().arena);
let val = fun();
OWNER.with(|o| {
*o.borrow_mut() = prev;
@@ -334,6 +351,8 @@ pub(crate) struct OwnerInner {
pub contexts: FxHashMap<TypeId, Box<dyn Any + Send + Sync>>,
pub cleanups: Vec<Box<dyn FnOnce() + Send + Sync>>,
pub children: Vec<Weak<RwLock<OwnerInner>>>,
#[cfg(feature = "sandboxed-arenas")]
arena: Arc<RwLock<ArenaMap>>,
}
impl Debug for OwnerInner {
@@ -361,11 +380,19 @@ impl Drop for OwnerInner {
let nodes = mem::take(&mut self.nodes);
if !nodes.is_empty() {
#[cfg(not(feature = "sandboxed-arenas"))]
Arena::with_mut(|arena| {
for node in nodes {
_ = arena.remove(node);
}
});
#[cfg(feature = "sandboxed-arenas")]
{
let mut arena = self.arena.write().or_poisoned();
for node in nodes {
_ = arena.remove(node);
}
}
}
}
}
@@ -394,11 +421,20 @@ impl Cleanup for RwLock<OwnerInner> {
}
if !nodes.is_empty() {
#[cfg(not(feature = "sandboxed-arenas"))]
Arena::with_mut(|arena| {
for node in nodes {
_ = arena.remove(node);
}
});
#[cfg(feature = "sandboxed-arenas")]
{
let arena = self.read().or_poisoned().arena.clone();
let mut arena = arena.write().or_poisoned();
for node in nodes {
_ = arena.remove(node);
}
}
}
}
}

View File

@@ -1,38 +1,42 @@
use or_poisoned::OrPoisoned;
use slotmap::{new_key_type, SlotMap};
#[cfg(feature = "sandboxed-arenas")]
use std::cell::RefCell;
#[cfg(not(feature = "sandboxed-arenas"))]
use std::sync::OnceLock;
use std::{any::Any, hash::Hash, sync::RwLock};
#[cfg(feature = "sandboxed-arenas")]
use std::{cell::RefCell, sync::Arc};
use std::sync::Weak;
use std::{
any::Any,
hash::Hash,
sync::{Arc, RwLock},
};
new_key_type! {
/// Unique identifier for an item stored in the arena.
pub struct NodeId;
}
pub(crate) struct Arena;
pub struct Arena;
type ArenaMap = SlotMap<NodeId, Box<dyn Any + Send + Sync>>;
pub type ArenaMap = SlotMap<NodeId, Box<dyn Any + Send + Sync>>;
#[cfg(not(feature = "sandboxed-arenas"))]
static MAP: OnceLock<RwLock<ArenaMap>> = OnceLock::new();
#[cfg(feature = "sandboxed-arenas")]
thread_local! {
pub(crate) static MAP: RefCell<Option<Arc<RwLock<ArenaMap>>>> = RefCell::new(Some(Default::default()));
pub(crate) static MAP: RefCell<Option<Weak<RwLock<ArenaMap>>>> = RefCell::new(Some(Default::default()));
}
impl Arena {
#[cfg(feature = "hydration")]
#[inline(always)]
pub fn enter_new() {
#[allow(unused)]
pub fn set(arena: &Arc<RwLock<ArenaMap>>) {
#[cfg(feature = "sandboxed-arenas")]
{
use std::sync::Arc;
let new_arena = Arc::downgrade(arena);
MAP.with_borrow_mut(|arena| {
*arena = Some(Arc::new(RwLock::new(
SlotMap::with_capacity_and_key(32),
)))
*arena = Some(new_arena);
})
}
}
@@ -48,6 +52,7 @@ impl Arena {
MAP.with_borrow(|arena| {
fun(&arena
.as_ref()
.and_then(Weak::upgrade)
.unwrap_or_else(|| {
panic!(
"at {}, the `sandboxed-arenas` feature is active, \
@@ -73,6 +78,7 @@ impl Arena {
MAP.with_borrow(|arena| {
fun(&mut arena
.as_ref()
.and_then(Weak::upgrade)
.unwrap_or_else(|| {
panic!(
"at {}, the `sandboxed-arenas` feature is active, \
@@ -94,20 +100,11 @@ pub mod sandboxed {
use pin_project_lite::pin_project;
use std::{
future::Future,
mem,
pin::Pin,
sync::{Arc, RwLock},
sync::{Arc, RwLock, Weak},
task::{Context, Poll},
};
impl Arena {
fn set(new_arena: Arc<RwLock<ArenaMap>>) -> UnsetArenaOnDrop {
MAP.with_borrow_mut(|arena| {
UnsetArenaOnDrop(mem::replace(arena, Some(new_arena)))
})
}
}
pin_project! {
/// A [`Future`] that restores its associated arena as the current arena whenever it is
/// polled.
@@ -117,7 +114,7 @@ pub mod sandboxed {
/// stored values. Wrapping a `Future` in `Sandboxed` ensures that it will always use the
/// same arena that it was created under.
pub struct Sandboxed<T> {
arena: Arc<RwLock<ArenaMap>>,
arena: Option<Arc<RwLock<ArenaMap>>>,
#[pin]
inner: T,
}
@@ -127,12 +124,7 @@ pub mod sandboxed {
/// Wraps the given [`Future`], ensuring that any [`ArenaItem`] created while it is being
/// polled will be associated with the same arena that was active when this was called.
pub fn new(inner: T) -> Self {
let arena = MAP.with_borrow(|current| {
Arc::clone(current.as_ref().expect(
"the `sandboxed-arenas` feature is active, but no Arena \
is active",
))
});
let arena = MAP.with_borrow(|n| n.as_ref().and_then(Weak::upgrade));
Self { arena, inner }
}
}
@@ -147,11 +139,11 @@ pub mod sandboxed {
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Self::Output> {
let unset = Arena::set(Arc::clone(&self.arena));
if let Some(arena) = self.arena.as_ref() {
Arena::set(arena);
}
let this = self.project();
let res = this.inner.poll(cx);
drop(unset);
res
this.inner.poll(cx)
}
}
@@ -165,22 +157,11 @@ pub mod sandboxed {
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Self::Item>> {
let unset = Arena::set(Arc::clone(&self.arena));
let this = self.project();
let res = this.inner.poll_next(cx);
drop(unset);
res
}
}
#[derive(Debug)]
struct UnsetArenaOnDrop(Option<Arc<RwLock<ArenaMap>>>);
impl Drop for UnsetArenaOnDrop {
fn drop(&mut self) {
if let Some(inner) = self.0.take() {
MAP.with_borrow_mut(|current_map| *current_map = Some(inner));
if let Some(arena) = self.arena.as_ref() {
Arena::set(arena);
}
let this = self.project();
this.inner.poll_next(cx)
}
}
}

View File

@@ -108,6 +108,7 @@ impl Owner {
/// ```rust
/// # use reactive_graph::prelude::*;
/// # use reactive_graph::owner::*;
/// # let owner = Owner::new(); owner.set();
/// # use reactive_graph::effect::Effect;
/// # futures::executor::block_on(async move {
/// # any_spawner::Executor::init_futures_executor();
@@ -140,6 +141,7 @@ impl Owner {
/// ```rust
/// # use reactive_graph::prelude::*;
/// # use reactive_graph::owner::*;
/// # let owner = Owner::new(); owner.set();
/// # use reactive_graph::effect::Effect;
/// # futures::executor::block_on(async move {
/// # any_spawner::Executor::init_futures_executor();
@@ -187,6 +189,7 @@ pub fn provide_context<T: Send + Sync + 'static>(value: T) {
/// ```rust
/// # use reactive_graph::prelude::*;
/// # use reactive_graph::owner::*;
/// # let owner = Owner::new(); owner.set();
/// # use reactive_graph::effect::Effect;
/// # futures::executor::block_on(async move {
/// # any_spawner::Executor::init_futures_executor();
@@ -232,6 +235,7 @@ pub fn use_context<T: Clone + 'static>() -> Option<T> {
/// ```rust
/// # use reactive_graph::prelude::*;
/// # use reactive_graph::owner::*;
/// # let owner = Owner::new(); owner.set();
/// # use reactive_graph::effect::Effect;
/// # futures::executor::block_on(async move {
/// # any_spawner::Executor::init_futures_executor();
@@ -286,6 +290,7 @@ pub fn expect_context<T: Clone + 'static>() -> T {
/// ```rust
/// # use reactive_graph::prelude::*;
/// # use reactive_graph::owner::*;
/// # let owner = Owner::new(); owner.set();
/// # use reactive_graph::effect::Effect;
/// # futures::executor::block_on(async move {
/// # any_spawner::Executor::init_futures_executor();

View File

@@ -120,7 +120,7 @@ impl<T, S: Storage<Arc<RwLock<T>>>> StoredValue<T, S> {
///
/// # Examples
/// ```rust
/// # use reactive_graph::owner::StoredValue;
/// # use reactive_graph::owner::StoredValue; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::traits::Dispose;
///
/// // Does not implement Clone
@@ -167,7 +167,7 @@ impl<T, S: Storage<Arc<RwLock<T>>>> StoredValue<T, S> {
///
/// # Examples
/// ```rust
/// # use reactive_graph::owner::StoredValue;
/// # use reactive_graph::owner::StoredValue; let owner = reactive_graph::owner::Owner::new(); owner.set();
///
/// // Does not implement Clone
/// struct Data {
@@ -251,7 +251,7 @@ impl<T, S: Storage<Arc<RwLock<T>>>> StoredValue<T, S> {
///
/// # Examples
/// ```rust
/// # use reactive_graph::owner::StoredValue;
/// # use reactive_graph::owner::StoredValue; let owner = reactive_graph::owner::Owner::new(); owner.set();
///
/// #[derive(Default)] // Does not implement Clone
/// struct Data {
@@ -295,7 +295,7 @@ impl<T, S: Storage<Arc<RwLock<T>>>> StoredValue<T, S> {
///
/// # Examples
/// ```rust
/// # use reactive_graph::owner::StoredValue;
/// # use reactive_graph::owner::StoredValue; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::traits::Dispose;
///
/// let data = StoredValue::new(String::default());
@@ -353,7 +353,7 @@ impl<T, S: Storage<Arc<RwLock<T>>>> StoredValue<T, S> {
///
/// # Examples
/// ```rust
/// # use reactive_graph::owner::StoredValue;
/// # use reactive_graph::owner::StoredValue; let owner = reactive_graph::owner::Owner::new(); owner.set();
///
/// let data = StoredValue::new(10);
///
@@ -394,7 +394,7 @@ where
///
/// # Examples
/// ```rust
/// # use reactive_graph::owner::StoredValue;
/// # use reactive_graph::owner::StoredValue; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::traits::Dispose;
///
/// // u8 is practically free to clone.
@@ -434,7 +434,7 @@ where
///
/// # Examples
/// ```rust
/// # use reactive_graph::owner::StoredValue;
/// # use reactive_graph::owner::StoredValue; let owner = reactive_graph::owner::Owner::new(); owner.set();
///
/// // u8 is practically free to clone.
/// let data: StoredValue<u8> = StoredValue::new(10);

View File

@@ -36,7 +36,7 @@ pub use write::*;
///
/// ```
/// # use reactive_graph::prelude::*;
/// # use reactive_graph::signal::*;
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// let (count, set_count) = arc_signal(0);
///
/// // ✅ calling the getter clones and returns the value
@@ -82,7 +82,7 @@ pub fn arc_signal<T>(value: T) -> (ArcReadSignal<T>, ArcWriteSignal<T>) {
/// as long as a reference to it is alive, see [`arc_signal`].
/// ```
/// # use reactive_graph::prelude::*;
/// # use reactive_graph::signal::*;
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// let (count, set_count) = signal(0);
///
/// // ✅ calling the getter clones and returns the value
@@ -142,7 +142,7 @@ pub fn signal_local<T: 'static>(
/// as long as a reference to it is alive, see [`arc_signal`].
/// ```
/// # use reactive_graph::prelude::*;
/// # use reactive_graph::signal::*;
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// let (count, set_count) = create_signal(0);
///
/// // ✅ calling the getter clones and returns the value

View File

@@ -63,7 +63,7 @@ use std::{
///
/// ```
/// # use reactive_graph::prelude::*;
/// # use reactive_graph::signal::*;
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// let count = ArcRwSignal::new(0);
///
/// // ✅ calling the getter clones and returns the value

View File

@@ -49,7 +49,7 @@ use std::{
///
/// ## Examples
/// ```
/// # use reactive_graph::prelude::*; use reactive_graph::signal::*;
/// # use reactive_graph::prelude::*; use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// let (count, set_count) = signal(0);
///
/// // calling .get() clones and returns the value

View File

@@ -69,7 +69,7 @@ use std::{
///
/// ```
/// # use reactive_graph::prelude::*;
/// # use reactive_graph::signal::*;
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// let count = ArcRwSignal::new(0);
///
/// // ✅ calling the getter clones and returns the value

View File

@@ -32,7 +32,7 @@ use std::{hash::Hash, ops::DerefMut, panic::Location, sync::Arc};
///
/// ## Examples
/// ```
/// # use reactive_graph::prelude::*; use reactive_graph::signal::*;
/// # use reactive_graph::prelude::*; use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// let (count, set_count) = signal(0);
///
/// // ✅ calling the setter sets the value

View File

@@ -131,7 +131,7 @@ pub mod read {
/// Wraps a derived signal, i.e., any computation that accesses one or more
/// reactive signals.
/// ```rust
/// # use reactive_graph::signal::*;
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::wrappers::read::ArcSignal;
/// # use reactive_graph::prelude::*;
/// let (count, set_count) = arc_signal(2);
@@ -397,7 +397,7 @@ pub mod read {
/// Wraps a derived signal, i.e., any computation that accesses one or more
/// reactive signals.
/// ```rust
/// # use reactive_graph::signal::*;
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::wrappers::read::Signal;
/// # use reactive_graph::prelude::*;
/// let (count, set_count) = signal(2);
@@ -647,7 +647,7 @@ pub mod read {
/// of the same type. This is especially useful for component properties.
///
/// ```
/// # use reactive_graph::signal::*;
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::wrappers::read::MaybeSignal;
/// # use reactive_graph::computed::Memo;
/// # use reactive_graph::prelude::*;
@@ -907,7 +907,7 @@ pub mod read {
///
/// ## Examples
/// ```rust
/// # use reactive_graph::signal::*;
/// # use reactive_graph::signal::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::wrappers::read::MaybeProp;
/// # use reactive_graph::computed::Memo;
/// # use reactive_graph::prelude::*;
@@ -1282,7 +1282,7 @@ pub mod write {
///
/// ## Examples
/// ```rust
/// # use reactive_graph::prelude::*;
/// # use reactive_graph::prelude::*; let owner = reactive_graph::owner::Owner::new(); owner.set();
/// # use reactive_graph::wrappers::write::SignalSetter;
/// # use reactive_graph::signal::signal;
/// let (count, set_count) = signal(2);

View File

@@ -1,6 +1,7 @@
use any_spawner::Executor;
use reactive_graph::{
computed::{ArcAsyncDerived, AsyncDerived},
owner::Owner,
signal::RwSignal,
traits::{Get, Read, Set, With, WithUntracked},
};
@@ -9,6 +10,8 @@ use std::future::pending;
#[tokio::test]
async fn arc_async_derived_calculates_eagerly() {
_ = Executor::init_tokio();
let owner = Owner::new();
owner.set();
let value = ArcAsyncDerived::new(|| async {
Executor::tick().await;
@@ -21,6 +24,8 @@ async fn arc_async_derived_calculates_eagerly() {
#[tokio::test]
async fn arc_async_derived_tracks_signal_change() {
_ = Executor::init_tokio();
let owner = Owner::new();
owner.set();
let signal = RwSignal::new(10);
let value = ArcAsyncDerived::new(move || async move {
@@ -40,6 +45,8 @@ async fn arc_async_derived_tracks_signal_change() {
#[tokio::test]
async fn async_derived_calculates_eagerly() {
_ = Executor::init_tokio();
let owner = Owner::new();
owner.set();
let value = AsyncDerived::new(|| async {
Executor::tick().await;
@@ -52,6 +59,8 @@ async fn async_derived_calculates_eagerly() {
#[tokio::test]
async fn async_derived_tracks_signal_change() {
_ = Executor::init_tokio();
let owner = Owner::new();
owner.set();
let signal = RwSignal::new(10);
let value = AsyncDerived::new(move || async move {
@@ -71,6 +80,8 @@ async fn async_derived_tracks_signal_change() {
#[tokio::test]
async fn read_signal_traits_on_arc() {
_ = Executor::init_tokio();
let owner = Owner::new();
owner.set();
let value = ArcAsyncDerived::new(pending::<()>);
assert_eq!(value.read(), None);
@@ -82,6 +93,8 @@ async fn read_signal_traits_on_arc() {
#[tokio::test]
async fn read_signal_traits_on_arena() {
_ = Executor::init_tokio();
let owner = Owner::new();
owner.set();
let value = AsyncDerived::new(pending::<()>);
println!("{:?}", value.read());
@@ -94,6 +107,8 @@ async fn read_signal_traits_on_arena() {
#[tokio::test]
async fn async_derived_with_initial() {
_ = Executor::init_tokio();
let owner = Owner::new();
owner.set();
let signal1 = RwSignal::new(0);
let signal2 = RwSignal::new(0);

View File

@@ -3,6 +3,7 @@ pub mod imports {
pub use any_spawner::Executor;
pub use reactive_graph::{
effect::{Effect, RenderEffect},
owner::Owner,
prelude::*,
signal::RwSignal,
};
@@ -19,6 +20,8 @@ async fn render_effect_runs() {
use imports::*;
_ = Executor::init_tokio();
let owner = Owner::new();
owner.set();
task::LocalSet::new()
.run_until(async {
let a = RwSignal::new(-1);
@@ -54,6 +57,8 @@ async fn effect_runs() {
use imports::*;
_ = Executor::init_tokio();
let owner = Owner::new();
owner.set();
task::LocalSet::new()
.run_until(async {
@@ -88,6 +93,8 @@ async fn dynamic_dependencies() {
use imports::*;
_ = Executor::init_tokio();
let owner = Owner::new();
owner.set();
task::LocalSet::new()
.run_until(async {
@@ -156,6 +163,8 @@ async fn recursive_effect_runs_recursively() {
use imports::*;
_ = Executor::init_tokio();
let owner = Owner::new();
owner.set();
task::LocalSet::new()
.run_until(async {
let s = RwSignal::new(0);

View File

@@ -1,5 +1,6 @@
use reactive_graph::{
computed::{ArcMemo, Memo},
owner::Owner,
prelude::*,
signal::RwSignal,
wrappers::read::Signal,
@@ -29,6 +30,9 @@ pub mod imports {
#[test]
fn memo_calculates_value() {
let owner = Owner::new();
owner.set();
let a = RwSignal::new(1);
let b = RwSignal::new(2);
let c = RwSignal::new(3);
@@ -42,6 +46,9 @@ fn memo_calculates_value() {
#[test]
fn arc_memo_readable() {
let owner = Owner::new();
owner.set();
let a = RwSignal::new(1);
let b = RwSignal::new(2);
let c = RwSignal::new(3);
@@ -52,6 +59,9 @@ fn arc_memo_readable() {
#[test]
fn memo_doesnt_repeat_calculation_per_get() {
let owner = Owner::new();
owner.set();
let calculations = Arc::new(RwLock::new(0));
let a = RwSignal::new(1);
@@ -78,6 +88,9 @@ fn memo_doesnt_repeat_calculation_per_get() {
#[test]
fn nested_memos() {
let owner = Owner::new();
owner.set();
let a = RwSignal::new(0); // 1
let b = RwSignal::new(0); // 2
let c = Memo::new(move |_| {
@@ -111,6 +124,9 @@ fn nested_memos() {
#[test]
fn memo_runs_only_when_inputs_change() {
let owner = Owner::new();
owner.set();
let call_count = Arc::new(RwLock::new(0));
let a = RwSignal::new(0);
let b = RwSignal::new(0);
@@ -150,6 +166,9 @@ fn memo_runs_only_when_inputs_change() {
#[test]
fn diamond_problem() {
let owner = Owner::new();
owner.set();
let name = RwSignal::new("Greg Johnston".to_string());
let first = Memo::new(move |_| {
println!("calculating first");
@@ -187,9 +206,14 @@ fn diamond_problem() {
#[cfg(feature = "effects")]
#[tokio::test]
async fn dynamic_dependencies() {
let owner = Owner::new();
owner.set();
use imports::*;
_ = Executor::init_tokio();
let owner = Owner::new();
owner.set();
let first = RwSignal::new("Greg");
let last = RwSignal::new("Johnston");
@@ -264,9 +288,14 @@ async fn dynamic_dependencies() {
#[cfg(feature = "effects")]
#[tokio::test]
async fn render_effect_doesnt_rerun_if_memo_didnt_change() {
let owner = Owner::new();
owner.set();
use imports::*;
_ = Executor::init_tokio();
let owner = Owner::new();
owner.set();
task::LocalSet::new()
.run_until(async {
@@ -307,9 +336,14 @@ async fn render_effect_doesnt_rerun_if_memo_didnt_change() {
#[cfg(feature = "effects")]
#[tokio::test]
async fn effect_doesnt_rerun_if_memo_didnt_change() {
let owner = Owner::new();
owner.set();
use imports::*;
_ = Executor::init_tokio();
let owner = Owner::new();
owner.set();
task::LocalSet::new()
.run_until(async {
@@ -343,9 +377,14 @@ async fn effect_doesnt_rerun_if_memo_didnt_change() {
#[cfg(feature = "effects")]
#[tokio::test]
async fn effect_depending_on_signal_and_memo_doesnt_rerun_unnecessarily() {
let owner = Owner::new();
owner.set();
use imports::*;
_ = Executor::init_tokio();
let owner = Owner::new();
owner.set();
task::LocalSet::new()
.run_until(async {
@@ -383,6 +422,9 @@ async fn effect_depending_on_signal_and_memo_doesnt_rerun_unnecessarily() {
#[test]
fn unsync_derived_signal_and_memo() {
let owner = Owner::new();
owner.set();
let a = RwSignal::new_local(Rc::new(1));
let b = RwSignal::new(2);
let c = RwSignal::new(3);

View File

@@ -1,4 +1,5 @@
use reactive_graph::{
owner::Owner,
signal::{arc_signal, signal, ArcRwSignal, RwSignal},
traits::{
Get, GetUntracked, Read, Set, Update, UpdateUntracked, With,
@@ -54,6 +55,9 @@ fn update_arc_signal() {
#[test]
fn create_rw_signal() {
let owner = Owner::new();
owner.set();
let a = RwSignal::new(0);
assert_eq!(a.read(), 0);
assert_eq!(a.get(), 0);
@@ -63,6 +67,9 @@ fn create_rw_signal() {
#[test]
fn update_rw_signal() {
let owner = Owner::new();
owner.set();
let a = RwSignal::new(1);
assert_eq!(a.read(), 1);
assert_eq!(a.get(), 1);
@@ -76,6 +83,9 @@ fn update_rw_signal() {
#[test]
fn create_signal() {
let owner = Owner::new();
owner.set();
let (a, _) = signal(0);
assert_eq!(a.read(), 0);
assert_eq!(a.get(), 0);
@@ -86,6 +96,9 @@ fn create_signal() {
#[test]
fn update_signal() {
let owner = Owner::new();
owner.set();
let (a, set_a) = signal(1);
assert_eq!(a.get(), 1);
set_a.update(|n| *n += 1);

View File

@@ -1,6 +1,8 @@
#[cfg(feature = "effects")]
use any_spawner::Executor;
#[cfg(feature = "effects")]
use reactive_graph::owner::Owner;
#[cfg(feature = "effects")]
use reactive_graph::{effect::Effect, prelude::*, signal::RwSignal};
#[cfg(feature = "effects")]
use std::sync::{Arc, RwLock};
@@ -11,6 +13,8 @@ use tokio::task;
#[tokio::test]
async fn watch_runs() {
_ = Executor::init_tokio();
let owner = Owner::new();
owner.set();
task::LocalSet::new()
.run_until(async {
@@ -71,6 +75,8 @@ async fn watch_runs() {
#[tokio::test]
async fn watch_runs_immediately() {
_ = Executor::init_tokio();
let owner = Owner::new();
owner.set();
task::LocalSet::new()
.run_until(async {
@@ -118,6 +124,8 @@ async fn watch_runs_immediately() {
#[tokio::test]
async fn watch_ignores_callback() {
_ = Executor::init_tokio();
let owner = Owner::new();
owner.set();
task::LocalSet::new()
.run_until(async {
@@ -174,6 +182,8 @@ async fn watch_ignores_callback() {
#[tokio::test]
async fn deprecated_watch_runs() {
_ = Executor::init_tokio();
let owner = Owner::new();
owner.set();
task::LocalSet::new()
.run_until(async {