@@ -3,7 +3,10 @@ use std::process::Command;
33use std:: thread;
44use std:: time:: Duration ;
55use tauri:: { AppHandle , Emitter } ;
6+ use tokio:: time:: timeout;
7+ use crate :: cache;
68use crate :: cli:: execute_command;
9+ use crate :: paths:: { get_socket_path, COMMAND_TIMEOUT_SECS , MOUNT_TIMEOUT_SECS } ;
710
811#[ derive( Debug , Clone , Serialize , Deserialize ) ]
912pub struct Partition {
@@ -36,8 +39,8 @@ pub struct DiskListResult {
3639
3740#[ tauri:: command]
3841pub async fn list_disks ( use_sudo : bool ) -> Result < DiskListResult , String > {
39- // Run in blocking task to avoid freezing UI
40- tokio:: task:: spawn_blocking ( move || {
42+ // Run in blocking task with timeout to avoid freezing UI
43+ let list_future = tokio:: task:: spawn_blocking ( move || {
4144 // Run both list commands and merge results:
4245 // - `list` (native): correctly detects Linux filesystems on Linux-only cards
4346 // - `list -m` (Microsoft fallback): works with broken GUID tables
@@ -68,9 +71,12 @@ pub async fn list_disks(use_sudo: bool) -> Result<DiskListResult, String> {
6871 result. used_admin_mode = use_sudo;
6972
7073 Ok ( result)
71- } )
72- . await
73- . map_err ( |e| format ! ( "Task error: {}" , e) ) ?
74+ } ) ;
75+
76+ timeout ( Duration :: from_secs ( COMMAND_TIMEOUT_SECS ) , list_future)
77+ . await
78+ . map_err ( |_| format ! ( "List disks timed out after {} seconds" , COMMAND_TIMEOUT_SECS ) ) ?
79+ . map_err ( |e| format ! ( "Task error: {}" , e) ) ?
7480}
7581
7682fn merge_disk_results (
@@ -170,7 +176,8 @@ fn update_mount_status(result: &mut DiskListResult) {
170176fn get_system_mounts ( ) -> Vec < ( String , String ) > {
171177 let mut mounts = Vec :: new ( ) ;
172178
173- if let Ok ( output) = Command :: new ( "mount" ) . output ( ) {
179+ // Use cached mount output to avoid redundant process spawning
180+ if let Some ( output) = cache:: get_mount_output ( ) {
174181 let mount_output = String :: from_utf8_lossy ( & output. stdout ) ;
175182
176183 for line in mount_output. lines ( ) {
@@ -505,18 +512,23 @@ fn parse_type_and_name(parts: &[&str]) -> (String, Option<String>) {
505512
506513#[ tauri:: command]
507514pub async fn mount_disk ( app : AppHandle , device : String , passphrase : Option < String > ) -> Result < String , String > {
508- // Run in blocking task to avoid freezing UI during sudo prompt
509- let result = tokio:: task:: spawn_blocking ( move || {
515+ // Run in blocking task with timeout to avoid freezing UI
516+ let mount_future = tokio:: task:: spawn_blocking ( move || {
510517 let pass_ref = passphrase. as_deref ( ) ;
511518 let result = execute_command ( & [ "mount" , & device] , true , pass_ref) ;
512519
513- // Give a moment for mount to complete, then verify with retries
514- // 20 retries × 500ms = 10 seconds total timeout
515- for _ in 0 ..20 {
516- thread:: sleep ( Duration :: from_millis ( 500 ) ) ;
520+ // Check immediately first, then retry with short intervals
521+ // 40 retries × 250ms = 10 seconds total timeout
522+ for i in 0 ..40 {
523+ // Invalidate cache to get fresh mount data
524+ cache:: invalidate_mount_cache ( ) ;
517525 if check_nfs_mount_exists ( ) {
518526 return Ok ( result. unwrap_or_else ( |_| "Mounted successfully" . to_string ( ) ) ) ;
519527 }
528+ // Don't sleep on first iteration - check immediately
529+ if i > 0 {
530+ thread:: sleep ( Duration :: from_millis ( 250 ) ) ;
531+ }
520532 }
521533
522534 // Mount verification failed - return error with details
@@ -531,9 +543,13 @@ pub async fn mount_disk(app: AppHandle, device: String, passphrase: Option<Strin
531543 }
532544 Err ( e) => Err ( e) ,
533545 }
534- } )
535- . await
536- . map_err ( |e| format ! ( "Task error: {}" , e) ) ?;
546+ } ) ;
547+
548+ // Apply overall timeout
549+ let result = timeout ( Duration :: from_secs ( MOUNT_TIMEOUT_SECS ) , mount_future)
550+ . await
551+ . map_err ( |_| format ! ( "Mount operation timed out after {} seconds" , MOUNT_TIMEOUT_SECS ) ) ?
552+ . map_err ( |e| format ! ( "Task error: {}" , e) ) ?;
537553
538554 // Emit status changed event regardless of success/failure
539555 let _ = app. emit ( "status-changed" , ( ) ) ;
@@ -542,7 +558,8 @@ pub async fn mount_disk(app: AppHandle, device: String, passphrase: Option<Strin
542558}
543559
544560fn check_nfs_mount_exists ( ) -> bool {
545- if let Ok ( output) = Command :: new ( "mount" ) . output ( ) {
561+ // Use cached mount output to avoid redundant process spawning
562+ if let Some ( output) = cache:: get_mount_output ( ) {
546563 let mount_output = String :: from_utf8_lossy ( & output. stdout ) ;
547564 // Look for anylinuxfs NFS mount pattern
548565 mount_output. contains ( "localhost:/mnt/" ) && mount_output. contains ( "/Volumes/" )
@@ -553,24 +570,46 @@ fn check_nfs_mount_exists() -> bool {
553570
554571#[ tauri:: command]
555572pub async fn unmount_disk ( app : AppHandle ) -> Result < String , String > {
556- // Run in blocking task - unmount doesn't need sudo
557- let result = tokio:: task:: spawn_blocking ( || {
573+ // Run in blocking task with timeout
574+ let unmount_future = tokio:: task:: spawn_blocking ( || {
558575 execute_command ( & [ "unmount" ] , false , None )
559- } )
560- . await
561- . map_err ( |e| format ! ( "Task error: {}" , e) ) ?;
576+ } ) ;
577+
578+ let result = timeout ( Duration :: from_secs ( COMMAND_TIMEOUT_SECS ) , unmount_future)
579+ . await
580+ . map_err ( |_| format ! ( "Unmount timed out after {} seconds" , COMMAND_TIMEOUT_SECS ) ) ?
581+ . map_err ( |e| format ! ( "Task error: {}" , e) ) ?;
582+
583+ // Wait for VM to fully shut down by polling until krun process is gone
584+ // 40 retries × 250ms = 10 seconds max wait
585+ for _ in 0 ..40 {
586+ if !is_vm_running ( ) {
587+ break ;
588+ }
589+ tokio:: time:: sleep ( Duration :: from_millis ( 250 ) ) . await ;
590+ }
591+
592+ // Invalidate all caches after unmount
593+ cache:: invalidate_all ( ) ;
562594
563595 // Emit status changed event
564596 let _ = app. emit ( "status-changed" , ( ) ) ;
565597
566598 result
567599}
568600
601+ /// Check if the VM (krun) process is running
602+ fn is_vm_running ( ) -> bool {
603+ // Use cached pgrep result, but invalidate first since we're polling for shutdown
604+ cache:: invalidate_process_cache ( ) ;
605+ cache:: is_krun_running ( )
606+ }
607+
569608#[ tauri:: command]
570609pub async fn eject_disk ( device : String ) -> Result < String , String > {
571610 // Eject (power down) a disk using diskutil
572611 // First unmount anylinuxfs if it has anything mounted, then eject
573- tokio:: task:: spawn_blocking ( move || {
612+ let eject_future = tokio:: task:: spawn_blocking ( move || {
574613 // Check if anylinuxfs has anything mounted and unmount first
575614 if check_nfs_mount_exists ( ) {
576615 // Unmount anylinuxfs first - this shuts down the VM properly
@@ -579,10 +618,14 @@ pub async fn eject_disk(device: String) -> Result<String, String> {
579618 // Wait for anylinuxfs to fully stop (up to 5 seconds)
580619 for _ in 0 ..10 {
581620 thread:: sleep ( Duration :: from_millis ( 500 ) ) ;
621+ // Invalidate cache to get fresh mount data
622+ cache:: invalidate_mount_cache ( ) ;
582623 if !check_nfs_mount_exists ( ) {
583624 break ;
584625 }
585626 }
627+ // Invalidate all caches after unmount
628+ cache:: invalidate_all ( ) ;
586629 }
587630
588631 // Now safe to eject the disk
@@ -597,9 +640,12 @@ pub async fn eject_disk(device: String) -> Result<String, String> {
597640 let stderr = String :: from_utf8_lossy ( & output. stderr ) ;
598641 Err ( format ! ( "Failed to eject: {}" , stderr) )
599642 }
600- } )
601- . await
602- . map_err ( |e| format ! ( "Task error: {}" , e) ) ?
643+ } ) ;
644+
645+ timeout ( Duration :: from_secs ( COMMAND_TIMEOUT_SECS ) , eject_future)
646+ . await
647+ . map_err ( |_| format ! ( "Eject timed out after {} seconds" , COMMAND_TIMEOUT_SECS ) ) ?
648+ . map_err ( |e| format ! ( "Task error: {}" , e) ) ?
603649}
604650
605651#[ tauri:: command]
@@ -623,9 +669,9 @@ pub async fn force_cleanup() -> Result<String, String> {
623669 }
624670
625671 // Remove socket file if it exists
626- let socket_path = "/tmp/anylinuxfs.sock" ;
627- if std :: path :: Path :: new ( socket_path) . exists ( ) {
628- if std:: fs:: remove_file ( socket_path) . is_ok ( ) {
672+ let socket_path = get_socket_path ( ) ;
673+ if socket_path. exists ( ) {
674+ if std:: fs:: remove_file ( & socket_path) . is_ok ( ) {
629675 killed. push ( "socket" ) ;
630676 }
631677 }
0 commit comments