@@ -94,12 +94,14 @@ fn validate_timeouts(min: Duration, max: Duration, activity: Duration) -> Result
9494/// Spawns the command, sets up pipes, and initializes the execution state.
9595/// Note: The command passed in should already have pre_exec configured if needed.
9696fn spawn_command_and_setup_state (
97- command : StdCommand , // Own command to avoid borrowing issues
97+ command : & mut StdCommand ,
9898 initial_deadline : Instant ,
9999) -> Result < CommandExecutionState < impl AsyncRead + Unpin , impl AsyncRead + Unpin > , CommandError > {
100+ command. stdout ( Stdio :: piped ( ) ) ;
101+ command. stderr ( Stdio :: piped ( ) ) ;
100102
101- // pass ownership of command
102- let mut tokio_cmd = TokioCommand :: from ( command) ;
103+ // Command already has pre_exec set by the caller function
104+ let mut tokio_cmd = TokioCommand :: from ( std :: mem :: replace ( command, StdCommand :: new ( "" ) ) ) ;
103105
104106 let mut child = tokio_cmd
105107 . kill_on_drop ( true )
@@ -487,8 +489,11 @@ pub async fn run_command_with_timeout(
487489 } ;
488490
489491 // Configure the command to run in its own process group
492+ // This MUST be done before spawning the command.
493+ // Take ownership to modify, then pass the modified command to spawn_command_and_setup_state
494+ let mut std_cmd = std:: mem:: replace ( & mut command, StdCommand :: new ( "" ) ) ; // Take ownership temporarily
490495 unsafe {
491- command . pre_exec ( || {
496+ std_cmd . pre_exec ( || {
492497 // libc::setpgid(0, 0) makes the new process its own group leader.
493498 // Pass 0 for both pid and pgid to achieve this for the calling process.
494499 if libc:: setpgid ( 0 , 0 ) == 0 {
@@ -499,11 +504,11 @@ pub async fn run_command_with_timeout(
499504 }
500505 } ) ;
501506 }
502-
503-
507+ // Put the modified command back for spawning
508+ command = std_cmd ;
504509
505510 // Setup state (spawns command with pre_exec hook)
506- let mut state = spawn_command_and_setup_state ( command, initial_deadline) ?;
511+ let mut state = spawn_command_and_setup_state ( & mut command, initial_deadline) ?;
507512
508513 // Main execution loop
509514 run_command_loop ( & mut state, & timeout_config) . await ?;
@@ -516,22 +521,22 @@ pub async fn run_command_with_timeout(
516521 & mut state. stdout_read_buffer ,
517522 "stdout" ,
518523 )
519- . await ?;
524+ . await ?;
520525 drain_reader (
521526 & mut state. stderr_reader ,
522527 & mut state. stderr_buffer ,
523528 & mut state. stderr_read_buffer ,
524529 "stderr" ,
525530 )
526- . await ?;
531+ . await ?;
527532
528533 // Post-loop processing: Final wait if killed and status not yet obtained
529534 let final_exit_status = finalize_exit_status (
530535 & mut state. child ,
531536 state. exit_status , // Use status potentially set in loop
532537 state. timed_out ,
533538 )
534- . await ?;
539+ . await ?;
535540
536541 let end_time = Instant :: now ( ) ;
537542 let duration = end_time. duration_since ( start_time) ;
@@ -849,7 +854,7 @@ mod tests {
849854 fn test_min_timeout_greater_than_max_timeout ( ) {
850855 run_async_test ( || async {
851856 let cmd = StdCommand :: new ( "echo" ) ; // removed mut
852- // cmd.arg("test"); // Don't need args
857+ // cmd.arg("test"); // Don't need args
853858
854859 let min_timeout = Duration :: from_secs ( 2 ) ;
855860 let max_timeout = Duration :: from_secs ( 1 ) ; // Invalid config
@@ -870,7 +875,7 @@ mod tests {
870875 fn test_zero_activity_timeout ( ) {
871876 run_async_test ( || async {
872877 let cmd = StdCommand :: new ( "echo" ) ; // removed mut
873- // cmd.arg("test"); // Don't need args
878+ // cmd.arg("test"); // Don't need args
874879
875880 let min_timeout = Duration :: from_millis ( 100 ) ;
876881 let max_timeout = Duration :: from_secs ( 1 ) ;
@@ -988,4 +993,4 @@ mod tests {
988993 ) ;
989994 } ) ;
990995 }
991- } // end tests mod
996+ } // end tests mod
0 commit comments