11use crate :: messages:: prelude:: * ;
22use graph_craft:: application_io:: { ResourceFuture , ResourceHash , ResourceStorage , Resources } ;
3- use std:: sync:: { Arc , RwLock } ;
3+ use std:: sync:: { OnceLock , RwLock } ;
44
5- const RESOURCE_STORAGE : Arc < RwLock < Option < Box < dyn ResourceStorage > > > > = Arc :: new ( RwLock :: new ( None ) ) ;
5+ /// Process-wide replaceable resource storage. The `RwLock` lets the message handler take exclusive
6+ /// write access (`&mut dyn ResourceStorage`) when processing a `ResourceMessage`, while any number
7+ /// of [`ResourceReadHandle`] clones can take the read side concurrently to call `Resources::load`.
8+ static RESOURCE_STORAGE : OnceLock < RwLock < Option < Box < dyn ResourceStorage > > > > = OnceLock :: new ( ) ;
69
7- pub ( crate ) fn set_resource_storage ( storage : Box < dyn ResourceStorage > ) {
8- let binding = RESOURCE_STORAGE ;
9- let mut guard = binding. write ( ) . unwrap ( ) ;
10- * guard = Some ( storage) ;
10+ fn cell ( ) -> & ' static RwLock < Option < Box < dyn ResourceStorage > > > {
11+ RESOURCE_STORAGE . get_or_init ( || RwLock :: new ( None ) )
1112}
1213
13- pub fn resources ( ) -> Box < dyn Resources > {
14- let binding = RESOURCE_STORAGE ;
15- let guard = binding. read ( ) . unwrap ( ) ;
16- Box :: new ( ResourceReadHandle {
17- storage : guard. as_ref ( ) . expect ( "Resource storage not initialized" ) . clone ( ) ,
18- } )
14+ /// Install (or replace) the underlying storage. Existing `ResourceReadHandle` clones automatically
15+ /// observe the new backing on their next `load`.
16+ pub fn set_resource_storage ( storage : Box < dyn ResourceStorage > ) {
17+ * cell ( ) . write ( ) . unwrap_or_else ( |p| p. into_inner ( ) ) = Some ( storage) ;
18+ }
19+
20+ /// Write-side accessor for the message handler only. Returns a write-locked guard that derefs to
21+ /// `&mut Option<Box<dyn ResourceStorage>>`; private on purpose — the read handle below is the only
22+ /// surface exposed to the rest of the editor.
23+ fn resources_mut ( ) -> std:: sync:: RwLockWriteGuard < ' static , Option < Box < dyn ResourceStorage > > > {
24+ cell ( ) . write ( ) . unwrap_or_else ( |p| p. into_inner ( ) )
25+ }
26+
27+ /// Unlimited read-only handle. Each `Resources::load` call independently takes the static's read
28+ /// lock, invokes the backend's `load` (which returns a `'static` owned future), then releases the
29+ /// lock before awaiting. Crucially the lock is never held across an `.await`, so a concurrent
30+ /// write/GC from the message handler can never deadlock against a pending IDB fetch.
31+ pub fn resources ( ) -> ResourceReadHandle {
32+ ResourceReadHandle
33+ }
34+
35+ #[ derive( Clone , Copy , Default ) ]
36+ pub struct ResourceReadHandle ;
37+
38+ impl Resources for ResourceReadHandle {
39+ fn load ( & self , hash : ResourceHash ) -> ResourceFuture {
40+ let future = cell ( ) . read ( ) . unwrap_or_else ( |p| p. into_inner ( ) ) . as_deref ( ) . map ( |storage| storage. load ( hash) ) ;
41+ Box :: pin ( async move {
42+ match future {
43+ Some ( future) => future. await ,
44+ None => None ,
45+ }
46+ } )
47+ }
1948}
2049
2150#[ derive( Default , ExtractField ) ]
@@ -27,40 +56,35 @@ impl std::fmt::Debug for ResourceMessageHandler {
2756 }
2857}
2958
30- /// Read-only access to a `ResourceMessageHandler`'s storage. Cheap to clone.
31- #[ derive( Clone ) ]
32- pub struct ResourceReadHandle {
33- storage : Arc < RwLock < Box < dyn ResourceStorage > > > ,
34- }
35-
36- impl Resources for ResourceReadHandle {
37- fn load < ' a > ( & ' a self , hash : & ' a ResourceHash ) -> ResourceFuture < ' a > {
38- self . storage . load ( hash)
39- }
40- }
41-
4259#[ derive( ExtractField ) ]
4360pub struct ResourceMessageContext { }
4461
4562#[ message_handler_data]
4663impl MessageHandler < ResourceMessage , ResourceMessageContext > for ResourceMessageHandler {
4764 fn process_message ( & mut self , message : ResourceMessage , responses : & mut VecDeque < Message > , _context : ResourceMessageContext ) {
65+ let mut guard = resources_mut ( ) ;
66+ let Some ( storage) = guard. as_deref_mut ( ) else {
67+ log:: error!( "Resource storage not initialized; dropping {message:?}" ) ;
68+ return ;
69+ } ;
70+
4871 match message {
4972 ResourceMessage :: Write { data } => {
50- let _hash = self . storage . write ( data. as_ref ( ) ) ;
73+ let _hash = storage. write ( data. as_ref ( ) ) ;
5174 }
5275 ResourceMessage :: GarbageCollect { used } => {
53- self . storage . garbage_collect ( & used) ;
76+ storage. garbage_collect ( & used) ;
5477 }
5578 ResourceMessage :: Export { document_id, resources } => {
5679 let mut exported = Vec :: with_capacity ( resources. len ( ) ) ;
5780 for hash in resources. iter ( ) {
58- if let Some ( resource) = self . storage . read ( hash) {
81+ if let Some ( resource) = storage. read ( hash) {
5982 exported. push ( ( * hash, resource) ) ;
6083 } else {
6184 log:: error!( "Resource not found for hash {hash} during export" ) ;
6285 }
6386 }
87+ drop ( guard) ;
6488
6589 responses. add ( PortfolioMessage :: DocumentPassMessage {
6690 document_id,
0 commit comments