@@ -4,10 +4,10 @@ use crate::{
44 error:: { CommandError , FileIo } ,
55 file_util, TmcError ,
66} ;
7- use std:: ffi:: OsStr ;
87use std:: fs:: File ;
9- use std:: io:: { Read , Seek , SeekFrom } ;
8+ use std:: io:: Read ;
109use std:: time:: Duration ;
10+ use std:: { ffi:: OsStr , thread:: JoinHandle } ;
1111use subprocess:: { Exec , ExitStatus , PopenError } ;
1212
1313/// Wrapper around subprocess::Exec
@@ -18,21 +18,6 @@ pub struct TmcCommand {
1818 stderr : Option < File > ,
1919}
2020
21- fn read_output (
22- file_handle : Option < File > ,
23- exec_output : Option < & mut File > ,
24- ) -> Result < Vec < u8 > , TmcError > {
25- let mut v = vec ! [ ] ;
26- if let Some ( mut file) = file_handle {
27- let _ = file. seek ( SeekFrom :: Start ( 0 ) ) ; // ignore errors
28- file. read_to_end ( & mut v) . map_err ( FileIo :: Generic ) ?;
29- } else if let Some ( file) = exec_output {
30- let _ = file. seek ( SeekFrom :: Start ( 0 ) ) ; // ignore errors
31- file. read_to_end ( & mut v) . map_err ( FileIo :: Generic ) ?;
32- }
33- Ok ( v)
34- }
35-
3621impl TmcCommand {
3722 /// Creates a new command
3823 pub fn new ( cmd : impl AsRef < OsStr > ) -> Self {
@@ -44,6 +29,7 @@ impl TmcCommand {
4429 }
4530
4631 /// Creates a new command with stdout and stderr redirected to files
32+ // todo: remember why this is useful
4733 pub fn new_with_file_io ( cmd : impl AsRef < OsStr > ) -> Result < Self , TmcError > {
4834 let stdout = file_util:: temp_file ( ) ?;
4935 let stderr = file_util:: temp_file ( ) ?;
@@ -78,6 +64,8 @@ impl TmcCommand {
7864
7965 // starts executing the command
8066 let mut popen = exec. popen ( ) . map_err ( |e| popen_to_tmc_err ( cmd. clone ( ) , e) ) ?;
67+ let stdout_handle = spawn_reader ( stdout, popen. stdout . take ( ) ) ;
68+ let stderr_handle = spawn_reader ( stderr, popen. stderr . take ( ) ) ;
8169
8270 let exit_status = if let Some ( timeout) = timeout {
8371 // timeout set
@@ -92,8 +80,12 @@ impl TmcCommand {
9280 popen
9381 . terminate ( )
9482 . map_err ( |e| CommandError :: Terminate ( cmd. clone ( ) , e) ) ?;
95- let stdout = read_output ( stdout, popen. stdout . as_mut ( ) ) ?;
96- let stderr = read_output ( stderr, popen. stderr . as_mut ( ) ) ?;
83+ let stdout = stdout_handle
84+ . join ( )
85+ . expect ( "the thread should not be able to panic" ) ;
86+ let stderr = stderr_handle
87+ . join ( )
88+ . expect ( "the thread should not be able to panic" ) ;
9789 return Err ( TmcError :: Command ( CommandError :: TimeOut {
9890 command : cmd,
9991 timeout,
@@ -108,17 +100,21 @@ impl TmcCommand {
108100 } ;
109101
110102 log:: info!( "finished executing {}" , cmd) ;
111- let stdout = read_output ( stdout, popen. stdout . as_mut ( ) ) ?;
112- let stderr = read_output ( stderr, popen. stderr . as_mut ( ) ) ?;
103+ let stdout = stdout_handle
104+ . join ( )
105+ . expect ( "the thread should not be able to panic" ) ;
106+ let stderr = stderr_handle
107+ . join ( )
108+ . expect ( "the thread should not be able to panic" ) ;
113109
114110 // on success, log stdout trace and stderr debug
115111 // on failure if checked, log warn
116112 // on failure if not checked, log debug
117113 if !exit_status. success ( ) {
118114 // if checked is set, error with failed exit status
119115 if checked {
120- log:: warn!( "stdout: {}" , String :: from_utf8_lossy( & stdout) ) ;
121- log:: warn!( "stderr: {}" , String :: from_utf8_lossy( & stderr) ) ;
116+ log:: warn!( "stdout: {}" , String :: from_utf8_lossy( & stdout) . into_owned ( ) ) ;
117+ log:: warn!( "stderr: {}" , String :: from_utf8_lossy( & stderr) . into_owned ( ) ) ;
122118 return Err ( CommandError :: Failed {
123119 command : cmd,
124120 status : exit_status,
@@ -127,12 +123,12 @@ impl TmcCommand {
127123 }
128124 . into ( ) ) ;
129125 } else {
130- log:: debug!( "stdout: {}" , String :: from_utf8_lossy( & stdout) ) ;
131- log:: debug!( "stderr: {}" , String :: from_utf8_lossy( & stderr) ) ;
126+ log:: debug!( "stdout: {}" , String :: from_utf8_lossy( & stdout) . into_owned ( ) ) ;
127+ log:: debug!( "stderr: {}" , String :: from_utf8_lossy( & stderr) . into_owned ( ) ) ;
132128 }
133129 } else {
134- log:: trace!( "stdout: {}" , String :: from_utf8_lossy( & stdout) ) ;
135- log:: debug!( "stderr: {}" , String :: from_utf8_lossy( & stderr) ) ;
130+ log:: trace!( "stdout: {}" , String :: from_utf8_lossy( & stdout) . into_owned ( ) ) ;
131+ log:: debug!( "stderr: {}" , String :: from_utf8_lossy( & stderr) . into_owned ( ) ) ;
136132 }
137133 Ok ( Output {
138134 status : exit_status,
@@ -167,6 +163,23 @@ impl TmcCommand {
167163 }
168164}
169165
166+ // it's assumed the thread will never panic
167+ fn spawn_reader ( temp_file : Option < File > , file : Option < File > ) -> JoinHandle < Vec < u8 > > {
168+ std:: thread:: spawn ( move || {
169+ if let Some ( mut temp_file) = temp_file {
170+ let mut buf = vec ! [ ] ;
171+ let _ = temp_file. read_to_end ( & mut buf) ;
172+ buf
173+ } else if let Some ( mut file) = file {
174+ let mut buf = vec ! [ ] ;
175+ let _ = file. read_to_end ( & mut buf) ;
176+ buf
177+ } else {
178+ vec ! [ ]
179+ }
180+ } )
181+ }
182+
170183// convenience function to convert an error while checking for command not found error
171184fn popen_to_tmc_err ( cmd : String , err : PopenError ) -> TmcError {
172185 if let PopenError :: IoError ( io) = & err {
@@ -198,7 +211,7 @@ mod test {
198211 . with ( |e| e. arg ( "1" ) ) ;
199212 assert ! ( matches!(
200213 cmd. output_with_timeout( Duration :: from_nanos( 1 ) ) ,
201- Err ( TmcError :: Command ( CommandError :: TimeOut { .. } ) )
214+ Err ( TmcError :: Command ( CommandError :: TimeOut { .. } ) )
202215 ) ) ;
203216 }
204217
@@ -207,7 +220,7 @@ mod test {
207220 let cmd = TmcCommand :: new_with_file_io ( "nonexistent command" ) . unwrap ( ) ;
208221 assert ! ( matches!(
209222 cmd. output( ) ,
210- Err ( TmcError :: Command ( CommandError :: NotFound { .. } ) )
223+ Err ( TmcError :: Command ( CommandError :: NotFound { .. } ) )
211224 ) ) ;
212225 }
213226}
0 commit comments