helper: add stdout/stderr pipe threads to model

This commit is contained in:
hinto-janaiyo 2022-11-29 22:38:01 -05:00
parent db60bc2c09
commit eb4a70c483
No known key found for this signature in database
GPG key ID: B1C5A64B80691E45
5 changed files with 74 additions and 67 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 272 KiB

After

Width:  |  Height:  |  Size: 273 KiB

View file

@ -1,7 +1,7 @@
# Gupax source files
# Gupax source
* [Structure](#Structure)
* [Bootstrap](#Bootstrap)
* [Thread Model](#Thread-Model)
* [Bootstrap](#Bootstrap)
* [Disk](#Disk)
* [Scale](#Scale)
* [Naming Scheme](#naming-scheme)
@ -10,27 +10,29 @@
| File/Folder | Purpose |
|--------------|---------|
| constants.rs | General constants needed in Gupax
| disk.rs | Code for writing to disk: `state.toml/node.toml/pool.toml`; This holds the structs for the [State]
| disk.rs | Code for writing to disk: `state.toml/node.toml/pool.toml`; This holds the structs for the [State] struct
| ferris.rs | Cute crab bytes
| gupax.rs | `Gupax` tab
| main.rs | `App/Tab/State` + misc data/functions
| helper.rs | The "helper" thread that runs for the entire duration Gupax is alive. All the processing that needs to be done without blocking the main GUI thread runs here, including everything related to handling P2Pool/XMRig
| main.rs | The main `App` struct that holds all data + misc data/functions
| node.rs | Community node ping code for the `P2Pool` simple tab
| process.rs | Code for executing/handling P2Pool/XMRig
| p2pool.rs | `P2Pool` tab
| status.rs | `Status` tab
| update.rs | Update code for the `Gupax` tab
| xmrig.rs | `XMRig` tab
## Thread Model
![thread_model.png](https://github.com/hinto-janaiyo/gupax/blob/main/images/thread_model.png)
## Bootstrap
This is how Gupax works internally when starting up:
1. **INIT**
- Initialize custom console logging with `log`, `env_logger`
- Initialize misc data (structs, text styles, thread count, images, etc)
- Check for admin privilege (for XMRig)
- Start initializing main `App` struct
- Parse command arguments
- Attempt to read disk files `state.toml`, `node.toml`
- Attempt to read disk files
- If errors were found, pop-up window
2. **AUTO**
@ -44,9 +46,6 @@ This is how Gupax works internally when starting up:
- If `ask_before_quit` == `true`, ask before quitting
- Kill processes, kill connections, exit
## Thread Model
![thread_model.png](https://github.com/hinto-janaiyo/gupax/blob/main/images/thread_model.png)
## Disk
Long-term state is saved onto the disk in the "OS data folder", using the [TOML](https://github.com/toml-lang/toml) format. If not found, default files will be created. Given a slightly corrupted state file, Gupax will attempt to merge it with a new default one. This will most likely happen if the internal data structure of `state.toml` is changed in the future (e.g removing an outdated setting). Merging silently in the background is a good non-interactive way to handle this. The node/pool database cannot be merged, and if given a corrupted file, Gupax will show an un-recoverable error screen. If Gupax can't read/write to disk at all, or if there are any other big issues, it will show an un-recoverable error screen.

View file

@ -15,9 +15,24 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// This file handles all things related to child processes (P2Pool/XMRig).
// The main GUI thread will interface with the [Arc<Mutex<...>>] data found
// here, e.g: User clicks [Start P2Pool] -> Init p2pool thread here.
// This file represents the "helper" thread, which is the full separate thread
// that runs alongside the main [App] GUI thread. It exists for the entire duration
// of Gupax so that things can be handled without locking up the GUI thread.
//
// This thread is a continual 1 second loop, collecting available jobs on the
// way down and (if possible) asynchronously executing them at the very end.
//
// The main GUI thread will interface with this thread by mutating the Arc<Mutex>'s
// found here, e.g: User clicks [Start P2Pool] -> Arc<Mutex<ProcessSignal> is set
// indicating to this thread during its loop: "I should start P2Pool!", e.g:
//
// match p2pool.lock().unwrap().signal {
// ProcessSignal::Start => start_p2pool(),
// ...
// }
//
// This also includes all things related to handling the child processes (P2Pool/XMRig):
// piping their stdout/stderr/stdin, accessing their APIs (HTTP + disk files), etc.
//---------------------------------------------------------------------------------------------------- Import
use std::{
@ -31,14 +46,11 @@ use log::*;
//---------------------------------------------------------------------------------------------------- [Process] Struct
// This holds all the state of a (child) process.
// The actual process thread runs in a 1 second loop, reading/writing to this struct.
// The main GUI thread will use this to display console text, online state, etc.
pub struct Process {
name: ProcessName, // P2Pool or XMRig?
online: bool, // Is the process alive?
args: String, // A single [String] containing the arguments
path: PathBuf, // The absolute path to the process binary
signal: ProcessSignal, // Did the user click [Stop/Restart]?
state: ProcessState, // The state of the process (alive, dead, etc)
signal: ProcessSignal, // Did the user click [Start/Stop/Restart]?
output: String, // This is the process's stdout + stderr
// STDIN Problem:
// - User can input many many commands in 1 second
@ -58,9 +70,7 @@ impl Process {
pub fn new(name: ProcessName, args: String, path: PathBuf) -> Self {
Self {
name,
online: false,
args,
path,
state: ProcessState::Dead,
signal: ProcessSignal::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
@ -74,57 +84,51 @@ impl Process {
pub fn parse_args(args: &str) -> Vec<String> {
args.split_whitespace().map(|s| s.to_owned()).collect()
}
pub fn spawn(process: &Arc<Mutex<Self>>, name: ProcessName) {
// Setup
let process = Arc::clone(process);
let args = Self::parse_args(&process.lock().unwrap().args);
info!("{} | Spawning initial thread", name);
info!("{} | Arguments: {:?}", name, args);
// Spawn thread
thread::spawn(move || {
// Create & spawn child
let mut child = Command::new(&process.lock().unwrap().path)
.args(args)
.stdout(std::process::Stdio::piped())
.spawn().unwrap();
// 1-second loop, reading and writing data to relevent struct
loop {
let process = process.lock().unwrap(); // Get lock
// If user sent a signal, handle it
match process.signal {
ProcessSignal::None => {},
_ => { child.kill(); break; },
};
// println!("{:?}", String::from_utf8(child.wait_with_output().unwrap().stdout).unwrap());
thread::sleep(SECOND);
}
// End of thread, must mean process is offline
process.lock().unwrap().online = false;
});
}
}
//---------------------------------------------------------------------------------------------------- [ProcessSignal] Enum
//---------------------------------------------------------------------------------------------------- [Process*] Enum
#[derive(Copy,Clone,Eq,PartialEq,Debug)]
pub enum ProcessState {
Alive, // Process is online, GREEN!
Dead, // Process is dead, BLACK!
Failed, // Process is dead AND exited with a bad code, RED!
// Process is starting up, YELLOW!
// Really, processes start instantly, this just accounts for the delay
// between the main thread and this threads 1 second event loop.
Starting,
}
#[derive(Copy,Clone,Eq,PartialEq,Debug)]
pub enum ProcessSignal {
None,
Start,
Stop,
Restart,
}
//---------------------------------------------------------------------------------------------------- [ProcessName] Enum
#[derive(Copy,Clone,Eq,PartialEq,Debug)]
pub enum ProcessName {
P2Pool,
XMRig,
}
impl std::fmt::Display for ProcessName {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:#?}", self)
}
impl std::fmt::Display for ProcessState { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{:#?}", self) } }
impl std::fmt::Display for ProcessSignal { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{:#?}", self) } }
impl std::fmt::Display for ProcessName { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{:#?}", self) } }
//---------------------------------------------------------------------------------------------------- The "helper" loop
#[tokio::main]
pub async fn helper() {
thread::spawn(|| { loop {
// 1. Check process signals, match, do action (start, kill, read)
// 2. Spawn P2Pool API task
// 3. Spawn XMRig HTTP API task
// 4.
// 5.
// 6.
// 7.
// 8.
// 9.
// 10.
}});
}

View file

@ -60,7 +60,7 @@ mod gupax;
mod p2pool;
mod xmrig;
mod update;
mod process;
mod helper;
use {ferris::*,constants::*,node::*,disk::*,status::*,update::*,gupax::*};
//---------------------------------------------------------------------------------------------------- Struct + Impl
@ -93,16 +93,18 @@ pub struct App {
// If Gupax updated itself, this represents that the
// user should (but isn't required to) restart Gupax.
restart: Arc<Mutex<Restart>>,
// Error State
// Error State:
// These values are essentially global variables that
// indicate if an error message needs to be displayed
// (it takes up the whole screen with [error_msg] and buttons for ok/quit/etc)
error_state: ErrorState,
// Process/update state:
// Doesn't make sense to save this on disk
// so it's represented as a bool here.
p2pool: bool, // Is p2pool online?
xmrig: bool, // Is xmrig online?
// Process State:
// This holds everything related to the
// child processes when starting P2Pool/XMRig.
p2pool: bool,
xmrig: bool,
// p2pool: Arc<Mutex<Process>>,
// xmrig: Arc<Mutex<Process>>,
// State from [--flags]
no_startup: bool,
// Static stuff
@ -153,6 +155,8 @@ impl App {
error_state: ErrorState::new(),
p2pool: false,
xmrig: false,
// p2pool: Arc::new(Mutex::new(Process::new())),
// xmrig: Arc::new(Mutex::new(Process::new())),
no_startup: false,
now: Instant::now(),
exe: String::new(),

View file

@ -20,7 +20,7 @@ use crate::{
constants::*,
disk::*,
node::*,
process::*,
helper::*,
};
use egui::{
TextEdit,SelectableLabel,ComboBox,Label,Button,