oconnor663 / shared_child.rs

a wrapper around std::process::Child that lets multiple threads wait or kill at once
MIT License
39 stars 7 forks source link

Real time stdout streams #24

Closed niraj-khatiwada closed 5 months ago

niraj-khatiwada commented 5 months ago

First of all, great library. I was finally able to kill the child process after waiting for the process to settle. For context: I'm developing a Tauri app and using ffmpeg to compress a video. The ffmpeg binary spawns a new command process and from the child .take_stdout(), I'm trying to read the process's stdout. But this only runs at the end of process. So I'm not sure how do I log the outputs in real time.

let mut command = self
            .ffmpeg
            .args(preset)
            .stdout(Stdio::piped())
            .stderr(Stdio::piped());

        match SharedChild::spawn(&mut command) {
            Ok(child) => {
                let cp = Arc::new(child);
                let cp_clone = cp.clone();

                if let Some(window) = self.app.get_webview_window("main") {
                    window.on_window_event(move |event| match event {
                        WindowEvent::Destroyed => match cp_clone.kill() {
                            Ok(_) => {
                                log::error!("[ffmpeg] child process killed.");
                            }
                            Err(err) => {
                                log::error!(
                                    "[ffmpeg] child process could not be killed {}",
                                    err.to_string()
                                );
                            }
                        },
                        _ => {}
                    });
                }

                let thread: tokio::task::JoinHandle<u8> = tokio::spawn(async move {
                    #[cfg(debug_assertions)]
                    if let Some(stdout) = cp.take_stdout() {
                        let lines = BufReader::new(stdout).lines();
                        for line in lines {
                            if let Ok(out) = line {
                                log::debug!("[ffmpeg] stdout: {:?}", out);
                            }
                        }
                    }

                    #[cfg(debug_assertions)]
                    if let Some(stderr) = cp.take_stderr() {
                        let lines = BufReader::new(stderr).lines();
                        for line in lines {
                            if let Ok(out) = line {
                                log::debug!("[ffmpeg] stderr: {:?}", out);
                            }
                        }
                    }
                    if let Ok(_) = cp.wait() {
                        return 0;
                    }
                    return 1;
                });

                match thread.await {
                    Ok(exit_status) => {
                        if exit_status == 1 {
                            return Err(String::from("Video corrupted."));
                        }
                    }
                    Err(_) => {
                        return Err(String::from("Thread panicked."));
                    }
                }
            }
            Err(err) => {
                return Err(err.to_string());
            }
        };

Thanks!

niraj-khatiwada commented 5 months ago

EDIT: I figured it out.

I was looping through stdout and stderr in the same thread. The streams were buffered via stderr which is in the later loop. That caused the streams to only output at the end of the thread. The solution would be to read each streams using their own thread.

let mut command = self
            .ffmpeg
            .args(preset)
            .stdout(Stdio::piped())
            .stderr(Stdio::piped());

        match SharedChild::spawn(&mut command) {
            Ok(child) => {
                let cp = Arc::new(child);
                let cp_clone = cp.clone();

                if let Some(window) = self.app.get_webview_window("main") {
                    window.on_window_event(move |event| match event {
                        WindowEvent::Destroyed => match cp_clone.kill() {
                            Ok(_) => {
                                log::error!("[ffmpeg] child process killed.");
                            }
                            Err(err) => {
                                log::error!(
                                    "[ffmpeg] child process could not be killed {}",
                                    err.to_string()
                                );
                            }
                        },
                        _ => {}
                    });
                }

                let thread: tokio::task::JoinHandle<u8> = tokio::spawn(async move {
                    #[cfg(debug_assertions)]
                    if let Some(stdout) = cp.take_stdout() {
                        let lines = BufReader::new(stdout).lines();
                        for line in lines {
                            if let Ok(out) = line {
                                log::debug!("[ffmpeg] stdout: {:?}", out);
                            }
                        }
                    }

        // Move this to their own thread
                   #[cfg(debug_assertions)]
            // if let Some(stderr) = cp.take_stderr() {
            //     let lines = BufReader::new(stderr).lines();
            //     for line in lines {
            //         if let Ok(out) = line {
            //             log::debug!("[ffmpeg] stderr: {:?}", out);
            //         }
            //     }
            // }    
                    if let Ok(_) = cp.wait() {
                        return 0;
                    }
                    return 1;
                });

                match thread.await {
                    Ok(exit_status) => {
                        if exit_status == 1 {
                            return Err(String::from("Video corrupted."));
                        }
                    }
                    Err(_) => {
                        return Err(String::from("Thread panicked."));
                    }
                }
            }
            Err(err) => {
                return Err(err.to_string());
            }
        };