@@ -26,6 +26,7 @@ use std::{
2626    rc:: Rc , 
2727    task:: Poll , 
2828} ; 
29+ use  thiserror:: Error ; 
2930
3031pub ( crate )  type  PinnedFuture < T >  = Pin < Box < dyn  Future < Output  = T > > > ; 
3132
@@ -97,9 +98,6 @@ pub struct Owner(pub(crate) NodeId);
9798
9899impl  Owner  { 
99100    /// Returns the current reactive owner. 
100- /// 
101- /// ## Panics 
102- /// Panics if there is no current reactive runtime. 
103101pub  fn  current ( )  -> Option < Owner >  { 
104102        with_runtime ( |runtime| runtime. owner . get ( ) ) 
105103            . ok ( ) 
@@ -686,16 +684,19 @@ impl Debug for Runtime {
686684    instrument( level = "trace" ,  skip_all, )  
687685) ] 
688686#[ inline( always) ]   // it monomorphizes anyway 
689- pub ( crate )  fn  with_runtime < T > ( f :  impl  FnOnce ( & Runtime )  -> T )  -> Result < T ,  ( ) >  { 
687+ pub ( crate )  fn  with_runtime < T > ( 
688+     f :  impl  FnOnce ( & Runtime )  -> T , 
689+ )  -> Result < T ,  ReactiveSystemError >  { 
690690    // in the browser, everything should exist under one runtime 
691691    cfg_if !  { 
692692        if  #[ cfg( any( feature = "csr" ,  feature = "hydrate" ) ) ]  { 
693693            Ok ( RUNTIME . with( |runtime| f( runtime) ) ) 
694694        }  else { 
695695            RUNTIMES . with( |runtimes| { 
696696                let  runtimes = runtimes. borrow( ) ; 
697-                 match  runtimes. get( Runtime :: current( ) )  { 
698-                     None  => Err ( ( ) ) , 
697+                 let  rt = Runtime :: current( ) ; 
698+                 match  runtimes. get( rt)  { 
699+                     None  => Err ( ReactiveSystemError :: RuntimeDisposed ( rt) ) , 
699700                    Some ( runtime)  => Ok ( f( runtime) ) 
700701                } 
701702            } ) 
@@ -820,38 +821,49 @@ where
820821/// ## Panics 
821822/// Panics if there is no current reactive runtime. 
822823pub  fn  with_owner < T > ( owner :  Owner ,  f :  impl  FnOnce ( )  -> T )  -> T  { 
823-     try_with_owner ( owner,  f) 
824-         . expect ( "runtime/scope should be alive when with_owner runs" ) 
824+     try_with_owner ( owner,  f) . unwrap ( ) 
825+ } 
826+ 
827+ #[ derive( Error ,  Debug ) ]  
828+ pub  enum  ReactiveSystemError  { 
829+     #[ error( "Runtime {0:?} has been disposed." ) ]  
830+     RuntimeDisposed ( RuntimeId ) , 
831+     #[ error( "Owner {0:?} has been disposed." ) ]  
832+     OwnerDisposed ( Owner ) , 
833+     #[ error( "Error borrowing runtime.nodes {0:?}" ) ]  
834+     Borrow ( std:: cell:: BorrowError ) , 
825835} 
826836
827837/// Runs the given code with the given reactive owner. 
828- pub  fn  try_with_owner < T > ( owner :  Owner ,  f :  impl  FnOnce ( )  -> T )  -> Option < T >  { 
838+ pub  fn  try_with_owner < T > ( 
839+     owner :  Owner , 
840+     f :  impl  FnOnce ( )  -> T , 
841+ )  -> Result < T ,  ReactiveSystemError >  { 
829842    with_runtime ( |runtime| { 
830-         runtime
831-             . nodes 
832-             . try_borrow ( ) 
833-             . map ( |nodes| nodes. contains_key ( owner. 0 ) ) 
834-             . map ( |scope_exists| { 
835-                 scope_exists. then ( || { 
836-                     let  prev_observer = runtime. observer . take ( ) ; 
837-                     let  prev_owner = runtime. owner . take ( ) ; 
843+         let  scope_exists = { 
844+             let  nodes = runtime
845+                 . nodes 
846+                 . try_borrow ( ) 
847+                 . map_err ( ReactiveSystemError :: Borrow ) ?; 
848+             nodes. contains_key ( owner. 0 ) 
849+         } ; 
850+         if  scope_exists { 
851+             let  prev_observer = runtime. observer . take ( ) ; 
852+             let  prev_owner = runtime. owner . take ( ) ; 
838853
839-                      runtime. owner . set ( Some ( owner. 0 ) ) ; 
840-                      runtime. observer . set ( Some ( owner. 0 ) ) ; 
854+             runtime. owner . set ( Some ( owner. 0 ) ) ; 
855+             runtime. observer . set ( Some ( owner. 0 ) ) ; 
841856
842-                      let  v = f ( ) ; 
857+             let  v = f ( ) ; 
843858
844-                      runtime. observer . set ( prev_observer) ; 
845-                      runtime. owner . set ( prev_owner) ; 
859+             runtime. observer . set ( prev_observer) ; 
860+             runtime. owner . set ( prev_owner) ; 
846861
847-                     v
848-                 } ) 
849-             } ) 
850-             . ok ( ) 
851-             . flatten ( ) 
852-     } ) 
853-     . ok ( ) 
854-     . flatten ( ) 
862+             Ok ( v) 
863+         }  else  { 
864+             Err ( ReactiveSystemError :: OwnerDisposed ( owner) ) 
865+         } 
866+     } ) ?
855867} 
856868
857869/// Runs the given function as a child of the current Owner, once. 
@@ -1494,23 +1506,25 @@ pub struct ScopedFuture<Fut: Future> {
14941506    future :  Fut , 
14951507} 
14961508
1509+ /// Errors that can occur when trying to spawn a [`ScopedFuture`]. 
1510+ #[ derive( Error ,  Debug ,  Clone ) ]  
1511+ pub  enum  ScopedFutureError  { 
1512+     #[ error(  
1513+         "Tried to spawn a scoped Future without a current reactive Owner."  
1514+     ) ]  
1515+     NoCurrentOwner , 
1516+ } 
1517+ 
14971518impl < Fut :  Future  + ' static >  Future  for  ScopedFuture < Fut >  { 
14981519    type  Output  = Option < Fut :: Output > ; 
14991520
15001521    fn  poll ( 
15011522        self :  Pin < & mut  Self > , 
15021523        cx :  & mut  std:: task:: Context < ' _ > , 
15031524    )  -> Poll < Self :: Output >  { 
1504-         // TODO: we need to think about how to make this 
1505-         // not panic for scopes that have been cleaned up... 
1506-         // or perhaps we can force the scope to not be cleaned 
1507-         // up until all futures that have a handle to them are 
1508-         // dropped... 
1509- 
15101525        let  this = self . project ( ) ; 
15111526
1512-         if  let  Some ( poll)  = try_with_owner ( * this. owner ,  || this. future . poll ( cx) ) 
1513-         { 
1527+         if  let  Ok ( poll)  = try_with_owner ( * this. owner ,  || this. future . poll ( cx) )  { 
15141528            match  poll { 
15151529                Poll :: Ready ( res)  => Poll :: Ready ( Some ( res) ) , 
15161530                Poll :: Pending  => Poll :: Pending , 
@@ -1529,33 +1543,31 @@ impl<Fut: Future> ScopedFuture<Fut> {
15291543    } 
15301544
15311545    /// Runs the future in the current [`Owner`]'s scope context. 
1532- /// 
1533- /// # Panics 
1534- /// Panics if there is no current [`Owner`] context available. 
15351546#[ track_caller]  
1536-     pub  fn  new_current ( fut :  Fut )  -> Self  { 
1537-         Self  { 
1538-             owner :  Owner :: current ( ) . expect ( 
1539-                 "`ScopedFuture::new_current()` to be called within an `Owner` \  
1540- , 
1541-             ) , 
1542-             future :  fut, 
1543-         } 
1547+     pub  fn  new_current ( fut :  Fut )  -> Result < Self ,  ScopedFutureError >  { 
1548+         Owner :: current ( ) 
1549+             . map ( |owner| Self  {  owner,  future :  fut } ) 
1550+             . ok_or ( ScopedFutureError :: NoCurrentOwner ) 
15441551    } 
15451552} 
15461553
15471554/// Runs a future that has access to the provided [`Owner`]'s 
15481555/// scope context. 
1556+ #[ track_caller]  
15491557pub  fn  spawn_local_with_owner ( 
15501558    owner :  Owner , 
15511559    fut :  impl  Future < Output  = ( ) >  + ' static , 
15521560)  { 
15531561    let  scoped_future = ScopedFuture :: new ( owner,  fut) ; 
1562+     #[ cfg( debug_assertions) ]  
1563+     let  loc = std:: panic:: Location :: caller ( ) ; 
15541564
15551565    crate :: spawn_local ( async  move  { 
15561566        if  scoped_future. await . is_none ( )  { 
1557-             // TODO: should we warn here? 
1558-             //     /* warning message */ 
1567+             crate :: macros:: debug_warn!( 
1568+                 "`spawn_local_with_owner` called at {loc} returned `None`, \  
1569+ 
1570+             ) ; 
15591571        } 
15601572    } ) ; 
15611573} 
@@ -1566,15 +1578,23 @@ pub fn spawn_local_with_owner(
15661578/// # Panics 
15671579/// Panics if there is no [`Owner`] context available. 
15681580#[ track_caller]  
1569- pub  fn  spawn_local_with_current_owner ( fut :  impl  Future < Output  = ( ) >  + ' static )  { 
1570-     let  scoped_future = ScopedFuture :: new_current ( fut) ; 
1581+ pub  fn  spawn_local_with_current_owner ( 
1582+     fut :  impl  Future < Output  = ( ) >  + ' static , 
1583+ )  -> Result < ( ) ,  ScopedFutureError >  { 
1584+     let  scoped_future = ScopedFuture :: new_current ( fut) ?; 
1585+     #[ cfg( debug_assertions) ]  
1586+     let  loc = std:: panic:: Location :: caller ( ) ; 
15711587
15721588    crate :: spawn_local ( async  move  { 
15731589        if  scoped_future. await . is_none ( )  { 
1574-             // TODO: should we warn here? 
1575-             //     /* warning message */ 
1590+             crate :: macros:: debug_warn!( 
1591+                 "`spawn_local_with_owner` called at {loc} returned `None`, \  
1592+ 
1593+             ) ; 
15761594        } 
15771595    } ) ; 
1596+ 
1597+     Ok ( ( ) ) 
15781598} 
15791599
15801600/// Runs a future that has access to the provided [`Owner`]'s 
@@ -1616,12 +1636,14 @@ pub fn try_spawn_local_with_owner(
16161636pub  fn  try_spawn_local_with_current_owner ( 
16171637    fut :  impl  Future < Output  = ( ) >  + ' static , 
16181638    on_cancelled :  impl  FnOnce ( )  + ' static , 
1619- )  { 
1620-     let  scoped_future = ScopedFuture :: new_current ( fut) ; 
1639+ )  ->  Result < ( ) ,   ScopedFutureError >   { 
1640+     let  scoped_future = ScopedFuture :: new_current ( fut) ? ; 
16211641
16221642    crate :: spawn_local ( async  move  { 
16231643        if  scoped_future. await . is_none ( )  { 
16241644            on_cancelled ( ) ; 
16251645        } 
16261646    } ) ; 
1647+ 
1648+     Ok ( ( ) ) 
16271649} 
0 commit comments