@@ -8,7 +8,9 @@ use std::fs;
88use std:: os:: unix:: fs:: { MetadataExt , PermissionsExt } ;
99use std:: path:: { Path , PathBuf } ;
1010use std:: sync:: atomic:: { AtomicBool , Ordering } ;
11+ use std:: sync:: mpsc;
1112use std:: sync:: { Arc , LazyLock , RwLock } ;
13+ use std:: time:: Duration ;
1214use uuid:: Uuid ;
1315use uzers:: { get_group_by_gid, get_user_by_uid} ;
1416
@@ -165,6 +167,10 @@ pub struct StreamingListingState {
165167 pub cancelled : AtomicBool ,
166168}
167169
170+ /// Interval for checking cancellation while waiting for directory listing results.
171+ /// This ensures we can respond to ESC within ~100ms even if I/O is blocked.
172+ const CANCELLATION_POLL_INTERVAL : Duration = Duration :: from_millis ( 100 ) ;
173+
168174/// Cache for streaming state (separate from completed listings cache)
169175pub ( crate ) static STREAMING_STATE : LazyLock < RwLock < HashMap < String , Arc < StreamingListingState > > > > =
170176 LazyLock :: new ( || RwLock :: new ( HashMap :: new ( ) ) ) ;
@@ -361,9 +367,9 @@ pub fn list_directory(path: &Path) -> Result<Vec<FileEntry>, std::io::Error> {
361367 let overall_start = std:: time:: Instant :: now ( ) ;
362368 let mut entries = Vec :: new ( ) ;
363369
364- let mut metadata_time = std :: time :: Duration :: ZERO ;
365- let mut owner_lookup_time = std :: time :: Duration :: ZERO ;
366- let mut entry_creation_time = std :: time :: Duration :: ZERO ;
370+ let mut metadata_time = Duration :: ZERO ;
371+ let mut owner_lookup_time = Duration :: ZERO ;
372+ let mut entry_creation_time = Duration :: ZERO ;
367373
368374 let read_start = std:: time:: Instant :: now ( ) ;
369375 let dir_entries: Vec < _ > = fs:: read_dir ( path) ?. collect ( ) ;
@@ -1022,8 +1028,8 @@ pub fn list_directory_core(path: &Path) -> Result<Vec<FileEntry>, std::io::Error
10221028 benchmark:: log_event_value ( "readdir END, count" , dir_entries. len ( ) ) ;
10231029
10241030 benchmark:: log_event ( "stat_loop START" ) ;
1025- let mut metadata_time = std :: time :: Duration :: ZERO ;
1026- let mut owner_lookup_time = std :: time :: Duration :: ZERO ;
1031+ let mut metadata_time = Duration :: ZERO ;
1032+ let mut owner_lookup_time = Duration :: ZERO ;
10271033
10281034 for entry in dir_entries {
10291035 let entry = entry?;
@@ -1426,10 +1432,40 @@ fn read_directory_with_progress(
14261432 . ok_or_else ( || std:: io:: Error :: new ( std:: io:: ErrorKind :: NotFound , format ! ( "Volume not found: {}" , volume_id) ) ) ?;
14271433
14281434 // Read directory entries via Volume abstraction
1435+ // Use polling-based cancellation to remain responsive even when filesystem I/O blocks
1436+ // (e.g., on slow/stuck network drives like SMB mounts)
14291437 let read_start = std:: time:: Instant :: now ( ) ;
1430- let mut entries = volume
1431- . list_directory ( path)
1432- . map_err ( |e| std:: io:: Error :: other ( e. to_string ( ) ) ) ?;
1438+ let path_for_thread = path. to_path_buf ( ) ;
1439+ let ( tx, rx) = mpsc:: channel ( ) ;
1440+
1441+ std:: thread:: spawn ( move || {
1442+ let result = volume. list_directory ( & path_for_thread) ;
1443+ let _ = tx. send ( result) ;
1444+ } ) ;
1445+
1446+ // Poll for results, checking cancellation between polls
1447+ let entries_result = loop {
1448+ if state. cancelled . load ( Ordering :: Relaxed ) {
1449+ benchmark:: log_event ( "read_directory_with_progress CANCELLED (during read_dir polling)" ) ;
1450+ let _ = app. emit (
1451+ "listing-cancelled" ,
1452+ ListingCancelledEvent {
1453+ listing_id : listing_id. to_string ( ) ,
1454+ } ,
1455+ ) ;
1456+ return Ok ( ( ) ) ;
1457+ }
1458+
1459+ match rx. recv_timeout ( CANCELLATION_POLL_INTERVAL ) {
1460+ Ok ( result) => break result,
1461+ Err ( mpsc:: RecvTimeoutError :: Timeout ) => continue ,
1462+ Err ( mpsc:: RecvTimeoutError :: Disconnected ) => {
1463+ return Err ( std:: io:: Error :: other ( "Directory listing thread terminated unexpectedly" ) ) ;
1464+ }
1465+ }
1466+ } ;
1467+
1468+ let mut entries = entries_result. map_err ( |e| std:: io:: Error :: other ( e. to_string ( ) ) ) ?;
14331469 let read_dir_time = read_start. elapsed ( ) ;
14341470 benchmark:: log_event_value ( "read_dir COMPLETE, entries" , entries. len ( ) ) ;
14351471
0 commit comments