@@ -10,23 +10,26 @@ mod tokio_runtime {
1010 use std:: ffi:: CStr ;
1111 use std:: fs:: File ;
1212 use std:: io;
13+ use std:: os:: unix:: fs:: MetadataExt ;
1314 use std:: os:: unix:: io:: AsRawFd ;
1415 use std:: path:: Path ;
1516 use std:: sync:: atomic:: { AtomicBool , AtomicUsize , Ordering } ;
16- use std:: sync:: mpsc:: channel;
17- use std:: sync:: Arc ;
17+ use std:: sync:: mpsc:: { channel, Receiver } ;
18+ use std:: sync:: { Arc , Condvar , Mutex } ;
1819 use std:: thread;
1920 use std:: time:: Duration ;
2021
2122 use async_trait:: async_trait;
2223 use fuse_backend_core:: abi:: fuse_abi:: { stat64, CreateIn , FsOptions , OpenOptions , ROOT_ID } ;
2324 use fuse_backend_core:: api:: filesystem:: {
2425 AsyncFileSystem , AsyncZeroCopyReader , AsyncZeroCopyWriter , Context , Entry , FileSystem ,
25- SetattrValid ,
26+ SetattrValid , ZeroCopyWriter ,
2627 } ;
2728 use fuse_backend_core:: api:: server:: Server ;
2829 use fuse_backend_core:: async_runtime:: { self , RuntimeType } ;
29- use fuse_backend_fusedev:: { FuseDevTask , FuseDevWriter , FuseSession } ;
30+ use fuse_backend_fusedev:: {
31+ FuseDevTask , FuseDevWriter , FuseSession , SyncFuseServing , SyncServingConfig ,
32+ } ;
3033 use fuse_backend_passthrough:: { Config , PassthroughFs } ;
3134 use tokio:: sync:: { oneshot, Semaphore } ;
3235 use vmm_sys_util:: tempdir:: TempDir ;
@@ -95,15 +98,10 @@ mod tokio_runtime {
9598 let mut task = FuseDevTask :: new ( buf_size, file, server, state) ;
9699 block_on_without_tokio ( task. poll_handler ( ) ) ;
97100 } ) ;
98- let ( tx, rx) = channel ( ) ;
99101 let path = mnt. as_path ( ) . join ( "hello" ) ;
100- let reader = thread:: spawn ( move || {
101- let _ = tx. send ( std:: fs:: read ( path) ) ;
102- } ) ;
103- let read = rx. recv_timeout ( Duration :: from_secs ( 5 ) ) ;
102+ let read = in_thread ( move || std:: fs:: read ( path) ) . recv_timeout ( Duration :: from_secs ( 5 ) ) ;
104103 se. umount ( ) . unwrap ( ) ;
105104 let served = handle. join ( ) ;
106- reader. join ( ) . unwrap ( ) ;
107105 assert ! (
108106 served. is_ok( ) ,
109107 "poll_handler() panicked without a tokio runtime"
@@ -218,12 +216,11 @@ mod tokio_runtime {
218216 }
219217 }
220218
221- /// Stat `path ` in a thread of its own and send the inode number .
222- fn stat_in_thread ( path : std :: path :: PathBuf ) -> std :: sync :: mpsc :: Receiver < u64 > {
219+ /// Run `f ` in a thread of its own and send its result .
220+ fn in_thread < T : Send + ' static > ( f : impl FnOnce ( ) -> T + Send + ' static ) -> Receiver < T > {
223221 let ( tx, rx) = channel ( ) ;
224222 thread:: spawn ( move || {
225- use std:: os:: unix:: fs:: MetadataExt ;
226- let _ = tx. send ( std:: fs:: metadata ( path) . unwrap ( ) . ino ( ) ) ;
223+ let _ = tx. send ( f ( ) ) ;
227224 } ) ;
228225 rx
229226 }
@@ -407,10 +404,10 @@ mod tokio_runtime {
407404
408405 /// Serve `file` with `server` on a tokio runtime of its own until the task returns or `stop`
409406 /// completes. Stopping drops the requests in flight.
410- fn serve (
407+ fn serve < F : AsyncFileSystem + Send + Sync + ' static > (
411408 file : File ,
412409 buf_size : usize ,
413- server : Arc < Server < Arc < TestFs > > > ,
410+ server : Arc < Server < F > > ,
414411 stop : oneshot:: Receiver < ( ) > ,
415412 ) -> thread:: JoinHandle < ( ) > {
416413 thread:: spawn ( move || {
@@ -451,11 +448,11 @@ mod tokio_runtime {
451448 stop,
452449 ) ;
453450
454- let slow = stat_in_thread ( mnt. as_path ( ) . join ( "slow" ) ) ;
451+ let path = mnt. as_path ( ) . join ( "slow" ) ;
452+ let slow = in_thread ( move || std:: fs:: metadata ( path) . unwrap ( ) . ino ( ) ) ;
455453 thread:: sleep ( Duration :: from_millis ( 100 ) ) ;
456454
457455 // A lookup waiting in the file system doesn't hold up the others.
458- use std:: os:: unix:: fs:: MetadataExt ;
459456 let fast = std:: fs:: metadata ( mnt. as_path ( ) . join ( "fast" ) ) . unwrap ( ) ;
460457 assert_eq ! ( fast. ino( ) , FAST ) ;
461458 assert ! ( slow. try_recv( ) . is_err( ) ) ;
@@ -511,7 +508,8 @@ mod tokio_runtime {
511508 old_handle. join ( ) . unwrap ( ) ;
512509 return ;
513510 }
514- let slow = stat_in_thread ( mnt. as_path ( ) . join ( "slow" ) ) ;
511+ let path = mnt. as_path ( ) . join ( "slow" ) ;
512+ let slow = in_thread ( move || std:: fs:: metadata ( path) . unwrap ( ) . ino ( ) ) ;
515513 thread:: sleep ( Duration :: from_millis ( 100 ) ) ;
516514 stop_tx. send ( ( ) ) . unwrap ( ) ;
517515 old_handle. join ( ) . unwrap ( ) ;
@@ -533,4 +531,280 @@ mod tokio_runtime {
533531 se. umount ( ) . unwrap ( ) ;
534532 new_handle. join ( ) . unwrap ( ) ;
535533 }
534+
535+ /// Size of the files of `SlowFs`.
536+ const SLOW_FS_FILE_SIZE : usize = 64 << 10 ;
537+
538+ /// The content of the files of `SlowFs`.
539+ fn slow_fs_data ( ) -> Vec < u8 > {
540+ ( 0 ..SLOW_FS_FILE_SIZE ) . map ( |i| ( i % 251 ) as u8 ) . collect ( )
541+ }
542+
543+ /// A root directory with the files `fast` and `slow`, which `SyncFuseServing` serves. Reading
544+ /// `slow` waits until the gate opens. The files are direct IO, so every read reaches the file
545+ /// system.
546+ #[ derive( Default ) ]
547+ struct SlowFs {
548+ gate : Mutex < bool > ,
549+ opened : Condvar ,
550+ slow_reads : AtomicUsize ,
551+ has_resend : AtomicBool ,
552+ }
553+
554+ impl SlowFs {
555+ fn open_gate ( & self ) {
556+ * self . gate . lock ( ) . unwrap ( ) = true ;
557+ self . opened . notify_all ( ) ;
558+ }
559+
560+ /// Wait until `n` reads of `slow` have started.
561+ fn wait_for_slow_reads ( & self , n : usize ) {
562+ for _ in 0 ..500 {
563+ if self . slow_reads . load ( Ordering :: Acquire ) >= n {
564+ return ;
565+ }
566+ thread:: sleep ( Duration :: from_millis ( 10 ) ) ;
567+ }
568+ panic ! ( "the file system got no read of slow" ) ;
569+ }
570+
571+ fn stat ( inode : u64 ) -> stat64 {
572+ let mut st = stat ( inode) ;
573+ if inode != ROOT_ID {
574+ st. st_size = SLOW_FS_FILE_SIZE as i64 ;
575+ }
576+ st
577+ }
578+ }
579+
580+ #[ allow( unused_variables) ]
581+ impl FileSystem for SlowFs {
582+ type Inode = u64 ;
583+ type Handle = u64 ;
584+
585+ fn init ( & self , capable : FsOptions ) -> io:: Result < FsOptions > {
586+ self . has_resend
587+ . store ( capable. contains ( FsOptions :: HAS_RESEND ) , Ordering :: Relaxed ) ;
588+ Ok ( FsOptions :: empty ( ) )
589+ }
590+
591+ fn lookup ( & self , ctx : & Context , parent : u64 , name : & CStr ) -> io:: Result < Entry > {
592+ let inode = match name. to_bytes ( ) {
593+ b"fast" => FAST ,
594+ b"slow" => SLOW ,
595+ _ => return Err ( io:: Error :: from_raw_os_error ( libc:: ENOENT ) ) ,
596+ } ;
597+ Ok ( Entry {
598+ attr : Self :: stat ( inode) ,
599+ ..entry ( inode)
600+ } )
601+ }
602+
603+ fn forget ( & self , ctx : & Context , inode : u64 , count : u64 ) { }
604+
605+ fn getattr (
606+ & self ,
607+ ctx : & Context ,
608+ inode : u64 ,
609+ handle : Option < u64 > ,
610+ ) -> io:: Result < ( stat64 , Duration ) > {
611+ Ok ( ( Self :: stat ( inode) , Duration :: from_secs ( 60 ) ) )
612+ }
613+
614+ fn open (
615+ & self ,
616+ ctx : & Context ,
617+ inode : u64 ,
618+ flags : u32 ,
619+ fuse_flags : u32 ,
620+ ) -> io:: Result < ( Option < u64 > , OpenOptions , Option < u32 > ) > {
621+ Ok ( ( Some ( inode) , OpenOptions :: DIRECT_IO , None ) )
622+ }
623+
624+ fn read (
625+ & self ,
626+ ctx : & Context ,
627+ inode : u64 ,
628+ handle : u64 ,
629+ w : & mut dyn ZeroCopyWriter ,
630+ size : u32 ,
631+ offset : u64 ,
632+ lock_owner : Option < u64 > ,
633+ flags : u32 ,
634+ ) -> io:: Result < usize > {
635+ if inode == SLOW {
636+ self . slow_reads . fetch_add ( 1 , Ordering :: AcqRel ) ;
637+ let mut open = self . gate . lock ( ) . unwrap ( ) ;
638+ while !* open {
639+ open = self . opened . wait ( open) . unwrap ( ) ;
640+ }
641+ }
642+ let data = slow_fs_data ( ) ;
643+ let start = std:: cmp:: min ( offset as usize , data. len ( ) ) ;
644+ let end = std:: cmp:: min ( start + size as usize , data. len ( ) ) ;
645+ w. write_all ( & data[ start..end] ) ?;
646+ Ok ( end - start)
647+ }
648+
649+ fn release (
650+ & self ,
651+ ctx : & Context ,
652+ inode : u64 ,
653+ flags : u32 ,
654+ handle : u64 ,
655+ flush : bool ,
656+ flock_release : bool ,
657+ lock_owner : Option < u64 > ,
658+ ) -> io:: Result < ( ) > {
659+ Ok ( ( ) )
660+ }
661+ }
662+
663+ /// A mounted `SlowFs`, and an unserved mount for a `PassthroughFs` of it. Dropping it opens
664+ /// the gate first, so a failing test doesn't leave reads waiting. Then the fields drop in
665+ /// order, so the upper mount, and the files that `fs` holds open in the lower mount, go away
666+ /// before the lower mount.
667+ struct SlowFsStack {
668+ upper_se : FuseSession ,
669+ fs : Arc < PassthroughFs < ( ) > > ,
670+ _lower : SyncFuseServing < Arc < SlowFs > > ,
671+ slow_fs : Arc < SlowFs > ,
672+ }
673+
674+ impl Drop for SlowFsStack {
675+ fn drop ( & mut self ) {
676+ self . slow_fs . open_gate ( ) ;
677+ }
678+ }
679+
680+ /// Mount a `SlowFs` at `lower`, served by threads of its own, and create a `PassthroughFs`
681+ /// of it with the blocking thread pool enabled, for a mount at `upper`.
682+ fn mount_passthrough_of_slow_fs ( lower : & Path , upper : & Path ) -> Option < SlowFsStack > {
683+ let slow_fs = Arc :: new ( SlowFs :: default ( ) ) ;
684+ let lower_se = mount ( lower) ?;
685+ let serving = SyncFuseServing :: new (
686+ lower_se,
687+ Arc :: new ( Server :: new ( slow_fs. clone ( ) ) ) ,
688+ SyncServingConfig {
689+ workers : 4 ,
690+ blocking : false ,
691+ } ,
692+ )
693+ . unwrap ( ) ;
694+ let fs = PassthroughFs :: < ( ) > :: new_shared ( Config {
695+ root_dir : lower. to_string_lossy ( ) . to_string ( ) ,
696+ do_import : false ,
697+ ..Default :: default ( )
698+ } )
699+ . unwrap ( ) ;
700+ fs. import ( ) . unwrap ( ) ;
701+ fs. enable_async_thread_pool ( true ) ;
702+ let upper_se = mount ( upper) ?;
703+ Some ( SlowFsStack {
704+ upper_se,
705+ fs,
706+ _lower : serving,
707+ slow_fs,
708+ } )
709+ }
710+
711+ /// With the thread pool, a READ that waits for the backing file doesn't hold up the other
712+ /// requests on the current-thread runtime.
713+ #[ test]
714+ fn test_passthrough_pool_serves_while_read_waits ( ) {
715+ async_runtime:: set_runtime_type ( RuntimeType :: Tokio ) . unwrap ( ) ;
716+
717+ let lower = TempDir :: new ( ) . unwrap ( ) ;
718+ let upper = TempDir :: new ( ) . unwrap ( ) ;
719+ let Some ( mut stack) = mount_passthrough_of_slow_fs ( lower. as_path ( ) , upper. as_path ( ) ) else {
720+ return ;
721+ } ;
722+ let ( _stop_tx, stop) = oneshot:: channel ( ) ;
723+ let handle = serve (
724+ stack. upper_se . clone_fuse_file ( ) . unwrap ( ) ,
725+ stack. upper_se . bufsize ( ) ,
726+ Arc :: new ( Server :: new ( stack. fs . clone ( ) ) ) ,
727+ stop,
728+ ) ;
729+
730+ let path = upper. as_path ( ) . join ( "slow" ) ;
731+ let slow = in_thread ( move || std:: fs:: read ( path) ) ;
732+ stack. slow_fs . wait_for_slow_reads ( 1 ) ;
733+ // Without the pool, the runtime thread waits for the gate, which opens 2s from now.
734+ let backstop = stack. slow_fs . clone ( ) ;
735+ thread:: spawn ( move || {
736+ thread:: sleep ( Duration :: from_secs ( 2 ) ) ;
737+ backstop. open_gate ( ) ;
738+ } ) ;
739+ let start = std:: time:: Instant :: now ( ) ;
740+ let fast = std:: fs:: read ( upper. as_path ( ) . join ( "fast" ) ) . unwrap ( ) ;
741+ let elapsed = start. elapsed ( ) ;
742+ eprintln ! ( "reading fast while slow waits took {:?}" , elapsed) ;
743+ assert_eq ! ( fast, slow_fs_data( ) ) ;
744+ assert ! (
745+ elapsed < Duration :: from_secs( 1 ) ,
746+ "reading fast took {:?}" ,
747+ elapsed
748+ ) ;
749+ assert ! ( slow. try_recv( ) . is_err( ) ) ;
750+
751+ stack. slow_fs . open_gate ( ) ;
752+ let slow = slow. recv_timeout ( Duration :: from_secs ( 5 ) ) . unwrap ( ) . unwrap ( ) ;
753+ assert_eq ! ( slow, slow_fs_data( ) ) ;
754+
755+ stack. upper_se . umount ( ) . unwrap ( ) ;
756+ handle. join ( ) . unwrap ( ) ;
757+ }
758+
759+ /// A server that stops with a READ waiting on a pool thread frees the request's buffer while
760+ /// the pool thread still reads. The pool thread reads into a buffer of its own, and a new
761+ /// server answers the READ that the kernel sends again.
762+ #[ test]
763+ fn test_passthrough_pool_read_survives_takeover ( ) {
764+ async_runtime:: set_runtime_type ( RuntimeType :: Tokio ) . unwrap ( ) ;
765+
766+ let lower = TempDir :: new ( ) . unwrap ( ) ;
767+ let upper = TempDir :: new ( ) . unwrap ( ) ;
768+ let Some ( mut stack) = mount_passthrough_of_slow_fs ( lower. as_path ( ) , upper. as_path ( ) ) else {
769+ return ;
770+ } ;
771+ // `import()` stat'ed the root of the lower mount, which waited for its INIT.
772+ if !stack. slow_fs . has_resend . load ( Ordering :: Relaxed ) {
773+ eprintln ! ( "kernel without FUSE_NOTIFY_RESEND, skipping" ) ;
774+ return ;
775+ }
776+ let buf_size = stack. upper_se . bufsize ( ) ;
777+ // The kernel ends the requests read from a file description when it is closed, so the
778+ // test keeps the one the old server reads from, as a takeover passes it to the new one.
779+ let file = stack. upper_se . clone_fuse_file ( ) . unwrap ( ) ;
780+ let old = Arc :: new ( Server :: new ( stack. fs . clone ( ) ) ) ;
781+ let ( stop_tx, stop) = oneshot:: channel ( ) ;
782+ let old_handle = serve ( file. try_clone ( ) . unwrap ( ) , buf_size, old. clone ( ) , stop) ;
783+
784+ let path = upper. as_path ( ) . join ( "slow" ) ;
785+ let slow = in_thread ( move || std:: fs:: read ( path) ) ;
786+ stack. slow_fs . wait_for_slow_reads ( 1 ) ;
787+ // Dropping poll_handler() frees the request buffers. The runtime then waits for its pool
788+ // threads when it shuts down, so the old serving thread ends only after the gate opens.
789+ stop_tx. send ( ( ) ) . unwrap ( ) ;
790+ thread:: sleep ( Duration :: from_millis ( 100 ) ) ;
791+
792+ let new = Arc :: new ( Server :: new ( stack. fs . clone ( ) ) ) ;
793+ let ( version, options) = old. init_state ( ) ;
794+ new. restore_init ( version, options) ;
795+ let mut buf = vec ! [ 0u8 ; 0x1000 ] ;
796+ let writer = FuseDevWriter :: < ( ) > :: new ( file. as_raw_fd ( ) , & mut buf) . unwrap ( ) ;
797+ new. notify_resend ( writer) . unwrap ( ) ;
798+ let ( _stop_tx, stop) = oneshot:: channel ( ) ;
799+ let new_handle = serve ( file. try_clone ( ) . unwrap ( ) , buf_size, new, stop) ;
800+ stack. slow_fs . wait_for_slow_reads ( 2 ) ;
801+
802+ stack. slow_fs . open_gate ( ) ;
803+ let slow = slow. recv_timeout ( Duration :: from_secs ( 5 ) ) . unwrap ( ) . unwrap ( ) ;
804+ assert_eq ! ( slow, slow_fs_data( ) ) ;
805+ old_handle. join ( ) . unwrap ( ) ;
806+
807+ stack. upper_se . umount ( ) . unwrap ( ) ;
808+ new_handle. join ( ) . unwrap ( ) ;
809+ }
536810}
0 commit comments