From 5d293054cfa55d704b244ba45cf90a0ed1ed7b5f Mon Sep 17 00:00:00 2001 From: hinto-janaiyo Date: Sun, 4 Dec 2022 11:24:38 -0500 Subject: [PATCH] helper: p2pool - fix args, basic watchdog loop, add STDOUT/STDERR handle to [Process] struct --- Cargo.lock | 6 ++--- Cargo.toml | 2 +- src/constants.rs | 1 + src/helper.rs | 69 +++++++++++++++++++++++++++++++++--------------- src/main.rs | 8 ++++-- 5 files changed, 58 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 34d69b6..d81ceda 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = 3 [[package]] name = "Gupax" -version = "0.5.0" +version = "0.7.0" dependencies = [ "anyhow", "arti-client", @@ -2500,9 +2500,9 @@ dependencies = [ [[package]] name = "num-format" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54b862ff8df690cf089058c98b183676a7ed0f974cc08b426800093227cbff3b" +checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" dependencies = [ "arrayvec 0.7.2", "itoa", diff --git a/Cargo.toml b/Cargo.toml index 0a61b52..458eece 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "Gupax" -version = "0.5.0" +version = "0.7.0" authors = ["hinto-janaiyo "] description = "GUI for P2Pool+XMRig" documentation = "https://github.com/hinto-janaiyo/gupax" diff --git a/src/constants.rs b/src/constants.rs index 01cf749..43d81c5 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -54,6 +54,7 @@ pub const BLACK: egui::Color32 = egui::Color32::BLACK; // [Duration] constants pub const SECOND: std::time::Duration = std::time::Duration::from_secs(1); +pub const MILLI_900: std::time::Duration = std::time::Duration::from_millis(900); pub const TOKIO_SECOND: tokio::time::Duration = std::time::Duration::from_secs(1); // OS specific diff --git a/src/helper.rs b/src/helper.rs index 1300923..c2dc8f9 100644 --- a/src/helper.rs +++ b/src/helper.rs @@ -75,7 +75,7 @@ pub struct Process { pub signal: ProcessSignal, // Did the user click [Start/Stop/Restart]? start: Instant, // Start time of process pub uptime: HumanTime, // Human readable process uptime - pub output: String, // This is the process's stdout + stderr + pub output: String, // This is the process's PUBLIC stdout + stderr // STDIN Problem: // - User can input many many commands in 1 second // - The process loop only processes every 1 second @@ -86,7 +86,10 @@ pub struct Process { // - When the user inputs something, push it to a [Vec] // - In the process loop, loop over every [Vec] element and // send each one individually to the process stdin - stdin: Option, // A handle to the process's STDIN + pub child: Option>>, // A handle to the actual child process + stdout: Option, // A handle to the process's STDOUT + stderr: Option, // A handle to the process's STDERR + stdin: Option, // A handle to the process's STDIN pub input: Vec, } @@ -100,7 +103,10 @@ impl Process { signal: ProcessSignal::None, start: now, uptime: HumanTime::into_human(now.elapsed()), + stdout: Option::None, + stderr: Option::None, stdin: Option::None, + child: Option::None, // P2Pool log level 1 produces a bit less than 100,000 lines a day. // Assuming each line averages 80 UTF-8 scalars (80 bytes), then this // initial buffer should last around a week (56MB) before resetting. @@ -540,13 +546,17 @@ impl Helper { // The tokio runtime that blocks while async reading both STDOUT/STDERR // Cheaper than spawning 2 OS threads just to read 2 pipes (...right? :D) #[tokio::main] - async fn read_stdout_stderr(process: &Arc>, stdout: tokio::process::ChildStdout, stderr: tokio::process::ChildStderr) { - let process_stdout = Arc::clone(process); - let process_stderr = Arc::clone(process); + async fn read_stdout_stderr(process: Arc>) { + let process_stdout = Arc::clone(&process); + let process_stderr = Arc::clone(&process); + let stdout = process.lock().unwrap().stdout.take().unwrap(); + let stderr = process.lock().unwrap().stderr.take().unwrap(); + // Create STDOUT pipe job let stdout_job = tokio::spawn(async move { let mut stdout_reader = BufReader::new(stdout).lines(); while let Ok(Some(line)) = stdout_reader.next_line().await { +// println!("{}", line); // For debugging. writeln!(process_stdout.lock().unwrap().output, "{}", line); } }); @@ -554,6 +564,7 @@ impl Helper { let stderr_job = tokio::spawn(async move { let mut stderr_reader = BufReader::new(stderr).lines(); while let Ok(Some(line)) = stderr_reader.next_line().await { +// println!("{}", line); // For debugging. writeln!(process_stderr.lock().unwrap().output, "{}", line); } }); @@ -573,15 +584,15 @@ impl Helper { // [Simple] if state.simple { // Build the p2pool argument - let (ip, rpc, zmq) = crate::node::enum_to_ip_rpc_zmq_tuple(state.node); // Get: (IP, RPC, ZMQ) - args.push(format!("--wallet {}", state.address)); // Wallet Address - args.push(format!("--host {}", ip)); // IP Address - args.push(format!("--rpc-port {}", rpc)); // RPC Port - args.push(format!("--zmq-port {}", zmq)); // ZMQ Port - args.push(format!("--data-api {}", api_path.display())); // API Path - args.push("--local-api".to_string()); // Enable API - args.push("--no-color".to_string()); // Remove color escape sequences, Gupax terminal can't parse it :( - args.push("--mini".to_string()); // P2Pool Mini + let (ip, rpc, zmq) = crate::node::enum_to_ip_rpc_zmq_tuple(state.node); // Get: (IP, RPC, ZMQ) + args.push("--wallet".to_string()); args.push(state.address.clone()); // Wallet address + args.push("--host".to_string()); args.push(ip.to_string()); // IP Address + args.push("--rpc-port".to_string()); args.push(rpc.to_string()); // RPC Port + args.push("--zmq-port".to_string()); args.push(zmq.to_string()); // ZMQ Port + args.push("--data-api".to_string()); args.push(api_path.display().to_string()); // API Path + args.push("--local-api".to_string()); // Enable API + args.push("--no-color".to_string()); // Remove color escape sequences, Gupax terminal can't parse it :( + args.push("--mini".to_string()); // P2Pool Mini // [Advanced] } else { @@ -622,30 +633,44 @@ impl Helper { #[tokio::main] async fn spawn_p2pool_watchdog(process: Arc>, pub_api: Arc>, priv_api: Arc>, args: Vec, path: std::path::PathBuf) { // 1. Create command - let mut child = tokio::process::Command::new(path) + let child = Arc::new(Mutex::new(tokio::process::Command::new(path) .args(args) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .stdin(Stdio::piped()) - .spawn().unwrap(); + .spawn().unwrap())); - // 2. Set start time - let now = Instant::now(); - process.lock().unwrap().start = now; - process.lock().unwrap().uptime = HumanTime::into_human(now.elapsed()); + // 2. Set process state + let mut lock = process.lock().unwrap(); + lock.state = ProcessState::Alive; + lock.signal = ProcessSignal::None; + lock.start = Instant::now(); + lock.child = Some(Arc::clone(&child)); + lock.stdin = Some(child.lock().unwrap().stdin.take().unwrap()); + drop(lock); // 3. Spawn STDOUT/STDERR thread + let process_clone = Arc::clone(&process); thread::spawn(move || { - Self::read_stdout_stderr(&process, child.stdout.take().unwrap(), child.stderr.take().unwrap()); + Self::read_stdout_stderr(process_clone); }); // 4. Loop forever as watchdog until process dies loop { // a. Watch user SIGNAL + match process.lock().unwrap().signal { + ProcessSignal::Stop => {}, + ProcessSignal::Restart => {}, + _ => {}, + } // b. Create STDIN task + if !process.lock().unwrap().input.is_empty() { /* process it */ } // c. Create API task + let async_file_read = { /* tokio async file read job */ }; // d. Execute async tasks - // e. Sleep (900ms) + tokio::join![/* jobs */]; + // f. Sleep (900ms) + std::thread::sleep(MILLI_900); } } diff --git a/src/main.rs b/src/main.rs index 56ede18..ba279f8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1018,14 +1018,18 @@ impl eframe::App for App { if ui.add_sized([width, height], Button::new("⟲")).on_hover_text("Restart P2Pool").clicked() { self.p2pool = false; } if ui.add_sized([width, height], Button::new("⏹")).on_hover_text("Stop P2Pool").clicked() { self.p2pool = false; } ui.add_enabled_ui(false, |ui| { - ui.add_sized([width, height], Button::new("⏺")).on_hover_text("Start P2Pool"); + if ui.add_sized([width, height], Button::new("⏺")).on_hover_text("Start P2Pool").clicked() { + Helper::spawn_p2pool(&self.helper, &self.state.p2pool, self.state.gupax.absolute_p2pool_path.clone()); + } }); } else { ui.add_enabled_ui(false, |ui| { ui.add_sized([width, height], Button::new("⟲")).on_hover_text("Restart P2Pool"); ui.add_sized([width, height], Button::new("⏹")).on_hover_text("Stop P2Pool"); }); - if ui.add_sized([width, height], Button::new("⏺")).on_hover_text("Start P2Pool").clicked() { self.p2pool = true; } + if ui.add_sized([width, height], Button::new("⏺")).on_hover_text("Start P2Pool").clicked() { + Helper::spawn_p2pool(&self.helper, &self.state.p2pool, self.state.gupax.absolute_p2pool_path.clone()); + } } }); },