@@ -8,14 +8,15 @@ use crate::{
88 blocking_io,
99 graph:: {
1010 filtering:: { GraphAccessFilter , GraphRowFilter , HiddenKeys } ,
11+ meta_graph:: MetaGraph ,
1112 namespace:: Namespace ,
1213 namespaced_item:: NamespacedItem ,
1314 vectorised_graph:: GqlVectorisedGraph ,
1415 } ,
1516 } ,
1617 paths:: {
1718 mark_dirty, ExistingGraphFolder , InternalPathValidationError , PathValidationError ,
18- ValidGraphPaths , ValidWriteableGraphFolder ,
19+ UnlockedGraphFolder , ValidGraphPaths , ValidWriteableGraphFolder ,
1920 } ,
2021 rayon:: blocking_compute,
2122 GQLError ,
@@ -39,12 +40,14 @@ use raphtory::{
3940 } ,
4041} ;
4142use std:: {
43+ cmp:: Ordering ,
4244 fs, io,
4345 io:: { Read , Seek } ,
4446 ops:: Deref ,
4547 path:: { Path , PathBuf } ,
4648 sync:: Arc ,
4749} ;
50+ use tokio:: sync:: { OwnedRwLockReadGuard , OwnedRwLockWriteGuard , RwLock , RwLockReadGuard } ;
4851use tracing:: { error, warn} ;
4952use walkdir:: WalkDir ;
5053
@@ -145,13 +148,107 @@ pub(crate) fn get_relative_path(
145148
146149/// Inner struct with a drop implementation that cleans up the graphs
147150pub struct DataInner {
148- pub ( crate ) work_dir : PathBuf ,
151+ work_dir : Arc < RwLock < PathBuf > > ,
149152 pub ( crate ) cache : GraphCache ,
150153 pub ( crate ) vector_cache : LazyDiskVectorCache ,
151154 pub ( crate ) graph_conf : Config ,
152155 pub ( crate ) auth_policy : Option < Arc < dyn AuthorizationPolicy > > ,
153156}
154157
158+ #[ derive( Debug , Clone ) ]
159+ pub struct WorkDirWriteGuard {
160+ guard : Arc < OwnedRwLockWriteGuard < PathBuf > > ,
161+ }
162+
163+ impl WorkDirWriteGuard {
164+ pub fn path ( & self ) -> & Path {
165+ & self . guard
166+ }
167+
168+ pub fn to_path_buf ( & self ) -> PathBuf {
169+ self . path ( ) . to_path_buf ( )
170+ }
171+
172+ pub fn validate_path_for_insert (
173+ self ,
174+ path : & str ,
175+ overwrite : bool ,
176+ ) -> Result < ValidWriteableGraphFolder , PathValidationError > {
177+ if overwrite {
178+ ValidWriteableGraphFolder :: try_existing_or_new ( self , path)
179+ } else {
180+ ValidWriteableGraphFolder :: try_new ( self , path)
181+ }
182+ }
183+ }
184+
185+ impl PartialEq for WorkDirWriteGuard {
186+ fn eq ( & self , other : & Self ) -> bool {
187+ self . path ( ) == other. path ( )
188+ }
189+ }
190+
191+ impl Eq for WorkDirWriteGuard { }
192+
193+ impl PartialOrd for WorkDirWriteGuard {
194+ fn partial_cmp ( & self , other : & Self ) -> Option < Ordering > {
195+ self . path ( ) . partial_cmp ( other. path ( ) )
196+ }
197+ }
198+
199+ impl Ord for WorkDirWriteGuard {
200+ fn cmp ( & self , other : & Self ) -> Ordering {
201+ self . path ( ) . cmp ( other. path ( ) )
202+ }
203+ }
204+
205+ #[ derive( Debug , Clone ) ]
206+ pub enum WorkDirGuard {
207+ Read {
208+ guard : Arc < OwnedRwLockReadGuard < PathBuf > > ,
209+ } ,
210+ Write ( WorkDirWriteGuard ) ,
211+ }
212+
213+ impl From < WorkDirWriteGuard > for WorkDirGuard {
214+ fn from ( value : WorkDirWriteGuard ) -> Self {
215+ Self :: Write ( value)
216+ }
217+ }
218+
219+ impl PartialEq for WorkDirGuard {
220+ fn eq ( & self , other : & Self ) -> bool {
221+ self . path ( ) == other. path ( )
222+ }
223+ }
224+
225+ impl Eq for WorkDirGuard { }
226+
227+ impl PartialOrd for WorkDirGuard {
228+ fn partial_cmp ( & self , other : & Self ) -> Option < Ordering > {
229+ self . path ( ) . partial_cmp ( other. path ( ) )
230+ }
231+ }
232+
233+ impl Ord for WorkDirGuard {
234+ fn cmp ( & self , other : & Self ) -> Ordering {
235+ self . path ( ) . cmp ( other. path ( ) )
236+ }
237+ }
238+
239+ impl WorkDirGuard {
240+ pub fn path ( & self ) -> & Path {
241+ match self {
242+ WorkDirGuard :: Read { guard } => & guard,
243+ WorkDirGuard :: Write ( guard) => guard. path ( ) ,
244+ }
245+ }
246+
247+ pub fn to_path_buf ( & self ) -> PathBuf {
248+ self . path ( ) . to_path_buf ( )
249+ }
250+ }
251+
155252/// Outer data struct that wraps the inner data to make sure it is only dropped once
156253#[ derive( Clone ) ]
157254pub struct Data {
@@ -198,7 +295,7 @@ impl Data {
198295
199296 Self {
200297 inner : Arc :: new ( DataInner {
201- work_dir : work_dir. to_path_buf ( ) ,
298+ work_dir : Arc :: new ( RwLock :: new ( work_dir. to_path_buf ( ) ) ) ,
202299 cache,
203300 vector_cache : LazyDiskVectorCache :: new ( work_dir. join ( ".vector-cache" ) ) ,
204301 graph_conf,
@@ -208,28 +305,27 @@ impl Data {
208305 }
209306 }
210307
308+ pub async fn work_dir_read ( & self ) -> WorkDirGuard {
309+ let guard = Arc :: new ( self . work_dir . clone ( ) . read_owned ( ) . await ) ;
310+ WorkDirGuard :: Read { guard }
311+ }
312+
313+ pub async fn work_dir_write ( & self ) -> WorkDirWriteGuard {
314+ let guard = Arc :: new ( self . work_dir . clone ( ) . write_owned ( ) . await ) ;
315+ WorkDirWriteGuard { guard }
316+ }
317+
211318 pub ( crate ) fn set_auth_policy ( & mut self , policy : Arc < dyn AuthorizationPolicy > ) {
212319 Arc :: get_mut ( & mut self . inner )
213320 . expect ( "Data is not uniquely owned when setting auth_policy" )
214321 . auth_policy = Some ( policy) ;
215322 }
216323
217- pub fn validate_path_for_insert (
218- & self ,
219- path : & str ,
220- overwrite : bool ,
221- ) -> Result < ValidWriteableGraphFolder , PathValidationError > {
222- if overwrite {
223- ValidWriteableGraphFolder :: try_existing_or_new ( self . work_dir . clone ( ) , path)
224- } else {
225- ValidWriteableGraphFolder :: try_new ( self . work_dir . clone ( ) , path)
226- }
227- }
228-
229324 /// Validates that `ns_path` exists and is a namespace, returning the `Namespace`
230325 /// so callers can enumerate descendants via `get_all_children()`.
231- pub fn get_namespace ( & self , ns_path : & str ) -> Result < Namespace , PathValidationError > {
232- Namespace :: try_new ( self . work_dir . clone ( ) , ns_path. to_string ( ) )
326+ pub async fn get_namespace ( & self , ns_path : & str ) -> Result < Namespace , PathValidationError > {
327+ let work_dir = self . work_dir_read ( ) . await ;
328+ Namespace :: try_new ( work_dir, ns_path. to_string ( ) )
233329 }
234330
235331 /// # ⚠ Bypasses all permission checks — do not call from resolvers directly.
@@ -318,7 +414,8 @@ impl Data {
318414 }
319415
320416 pub async fn delete_graph ( & self , path : & str ) -> Result < ( ) , DeletionError > {
321- let graph_folder = ExistingGraphFolder :: try_from ( self . work_dir . clone ( ) , path) ?;
417+ let work_dir = self . work_dir_write ( ) . await ;
418+ let graph_folder = ExistingGraphFolder :: try_from ( work_dir. into ( ) , path) ?;
322419 self . delete_graph_inner ( graph_folder)
323420 . await
324421 . map_err ( |err| DeletionError :: from_inner ( path, err) ) ?;
@@ -327,15 +424,15 @@ impl Data {
327424
328425 pub async fn delete_namespace (
329426 & self ,
330- path : & str ,
427+ namespace : Namespace ,
331428 descendants : & Vec < NamespacedItem > ,
332429 ) -> Result < ( ) , DeletionError > {
430+ let path = namespace. local_path ( ) ;
333431 if path. is_empty ( ) {
334432 return Err ( DeletionError :: PathValidation (
335433 PathValidationError :: EmptyPath ,
336434 ) ) ;
337435 }
338- let namespace = Namespace :: try_new ( self . work_dir . clone ( ) , path. to_string ( ) ) ?;
339436 let root = namespace. current_dir ( ) . to_path_buf ( ) ;
340437 let dirty_file = mark_dirty ( & root) . map_err ( |err| {
341438 DeletionError :: from_inner ( path, MutationErrorInner :: InvalidInternal ( err) )
@@ -360,7 +457,9 @@ impl Data {
360457 }
361458
362459 pub async fn create_namespace ( & self , path : & str ) -> Result < ( ) , InsertionError > {
363- let target = crate :: paths:: validate_path_for_namespace_create ( self . work_dir . clone ( ) , path) ?;
460+ let work_dir = self . work_dir_write ( ) . await ;
461+ let target =
462+ crate :: paths:: validate_path_for_namespace_create ( work_dir. to_path_buf ( ) , path) ?;
364463 let mut cleanup_root = target. as_path ( ) ;
365464 while let Some ( parent) = cleanup_root. parent ( ) {
366465 if parent. is_dir ( ) {
@@ -435,15 +534,15 @@ impl Data {
435534 Ok ( ( ) )
436535 }
437536
438- pub fn get_all_graph_folders ( & self ) -> impl Iterator < Item = ExistingGraphFolder > {
439- let base_path = self . work_dir . clone ( ) ;
440- WalkDir :: new ( & self . work_dir )
537+ pub async fn get_all_graph_folders ( & self ) -> impl Iterator < Item = ExistingGraphFolder > {
538+ let work_dir = self . work_dir_read ( ) . await ;
539+ WalkDir :: new ( work_dir. path ( ) )
441540 . into_iter ( )
442541 . filter_map ( move |e| {
443542 let entry = e. ok ( ) ?;
444543 let path = entry. path ( ) ;
445- let relative = get_relative_path ( & base_path , path) . ok ( ) ?;
446- let folder = ExistingGraphFolder :: try_from ( base_path . clone ( ) , & relative) . ok ( ) ?;
544+ let relative = get_relative_path ( work_dir . path ( ) , path) . ok ( ) ?;
545+ let folder = ExistingGraphFolder :: try_from ( work_dir . clone ( ) , & relative) . ok ( ) ?;
447546 Some ( folder)
448547 } )
449548 }
@@ -459,7 +558,8 @@ impl Data {
459558 }
460559
461560 async fn read_graph_from_disk ( & self , path : & str ) -> Result < GraphWithVectors , GQLError > {
462- let folder = ExistingGraphFolder :: try_from ( self . work_dir . clone ( ) , path) ?;
561+ let work_dir = self . work_dir_read ( ) . await ;
562+ let folder = ExistingGraphFolder :: try_from ( work_dir, path) ?;
463563 Ok ( self . read_graph_from_disk_inner ( folder) . await ?)
464564 }
465565}
@@ -653,7 +753,7 @@ impl Data {
653753 path : & str ,
654754 perm : GraphPermission ,
655755 graph_type : Option < GqlGraphType > ,
656- ) -> async_graphql:: Result < ( ExistingGraphFolder , DynamicGraph ) > {
756+ ) -> async_graphql:: Result < ( UnlockedGraphFolder , DynamicGraph ) > {
657757 let gwv = self . get_graph ( path) . await ?;
658758 let typed_graph = match graph_type {
659759 Some ( GqlGraphType :: Event ) => match gwv. graph ( ) {
@@ -691,7 +791,7 @@ impl Data {
691791 ctx : & Context < ' _ > ,
692792 path : & str ,
693793 graph_type : Option < GqlGraphType > ,
694- ) -> async_graphql:: Result < Option < ( ExistingGraphFolder , DynamicGraph ) > > {
794+ ) -> async_graphql:: Result < Option < ( UnlockedGraphFolder , DynamicGraph ) > > {
695795 match require_at_least_read ( ctx, & self . auth_policy , path) {
696796 Ok ( perm) => self . load_and_filter ( path, perm, graph_type) . await . map ( Some ) ,
697797 Err ( _) => Ok ( None ) ,
@@ -707,7 +807,7 @@ impl Data {
707807 ctx : & Context < ' _ > ,
708808 path : & str ,
709809 graph_type : Option < GqlGraphType > ,
710- ) -> async_graphql:: Result < ( ExistingGraphFolder , DynamicGraph ) > {
810+ ) -> async_graphql:: Result < ( UnlockedGraphFolder , DynamicGraph ) > {
711811 let perm = require_at_least_read ( ctx, & self . auth_policy , path) ?;
712812 self . load_and_filter ( path, perm, graph_type) . await
713813 }
@@ -789,8 +889,9 @@ pub(crate) mod data_tests {
789889 data : & Data ,
790890 graphs : & HashMap < String , MaterializedGraph > ,
791891 ) -> Result < ( ) , InsertionError > {
892+ let work_dir = data. work_dir_write ( ) . await ;
792893 for ( name, graph) in graphs. into_iter ( ) {
793- let folder = data . validate_path_for_insert ( name, true ) ?;
894+ let folder = work_dir . clone ( ) . validate_path_for_insert ( name, true ) ?;
794895 data. insert_graph ( folder, graph. clone ( ) ) . await ?;
795896 }
796897 Ok ( ( ) )
@@ -888,8 +989,9 @@ pub(crate) mod data_tests {
888989
889990 let paths = data
890991 . get_all_graph_folders ( )
992+ . await
891993 . into_iter ( )
892- . map ( |folder| folder. 0 . root ( ) . to_path_buf ( ) )
994+ . map ( |folder| folder. folder . root ( ) . to_path_buf ( ) )
893995 . collect_vec ( ) ;
894996
895997 assert_eq ! ( paths. len( ) , 5 ) ;
0 commit comments