This commit is contained in:
nora 2021-12-18 14:34:55 +01:00
parent 817a14c3bc
commit f1e1cbd0a1
3 changed files with 76 additions and 25 deletions

View file

@ -8,7 +8,7 @@ use std::io;
use std::io::{ErrorKind, Read, Write}; use std::io::{ErrorKind, Read, Write};
use std::process::{Child, Command, Stdio}; use std::process::{Child, Command, Stdio};
use std::sync::mpsc::TryRecvError; use std::sync::mpsc::TryRecvError;
use std::sync::{mpsc, Mutex}; use std::sync::{mpsc, Arc, Mutex};
use std::time::Duration; use std::time::Duration;
use tui::backend::Backend; use tui::backend::Backend;
use tui::widgets::TableState; use tui::widgets::TableState;
@ -32,6 +32,7 @@ pub fn run_app<B: Backend>(terminal: &mut Terminal<B>, mut app: App) -> SmResult
None => break, None => break,
}, },
KeyCode::Char('r') => app.run_service()?, KeyCode::Char('r') => app.run_service()?,
KeyCode::Char('k') => app.kill_service()?,
KeyCode::Down => app.next(), KeyCode::Down => app.next(),
KeyCode::Up => app.previous(), KeyCode::Up => app.previous(),
KeyCode::Enter => app.select_service(), KeyCode::Enter => app.select_service(),
@ -43,7 +44,7 @@ pub fn run_app<B: Backend>(terminal: &mut Terminal<B>, mut app: App) -> SmResult
} }
// terminate the child processes // terminate the child processes
for sender in app.thread_terminates { for sender in app.thread_terminates.values() {
let _ = sender.send(()); let _ = sender.send(());
} }
@ -68,7 +69,7 @@ impl App {
.ok_or_else(|| io::Error::from(ErrorKind::Other)) .ok_or_else(|| io::Error::from(ErrorKind::Other))
.or_else(|_| std::env::current_dir())?, .or_else(|_| std::env::current_dir())?,
env: service.env.unwrap_or_else(HashMap::new), env: service.env.unwrap_or_else(HashMap::new),
status: Mutex::new(ServiceStatus::NotStarted), status: Arc::new(Mutex::new(ServiceStatus::NotStarted)),
std_io_buf: Vec::new(), std_io_buf: Vec::new(),
stdout: StdIoStream { stdout: StdIoStream {
recv: stdout_recv, recv: stdout_recv,
@ -79,7 +80,7 @@ impl App {
.collect::<io::Result<_>>()?, .collect::<io::Result<_>>()?,
}, },
selected: None, selected: None,
thread_terminates: Vec::new(), thread_terminates: HashMap::new(),
}) })
} }
@ -156,8 +157,31 @@ impl App {
Ok(()) Ok(())
} }
fn start_service(&mut self, service: usize) -> SmResult { fn kill_service(&mut self) -> SmResult {
let service = &mut self.table.services[service]; let index = self.selected.or_else(|| self.table.table_state.selected());
if let Some(index) = index {
let status = {
let service = &mut self.table.services[index];
*service.status.lock()?
};
if status == ServiceStatus::Running {
let terminate_sender = &mut self
.thread_terminates
.get(&index)
.ok_or(SmError::Bug("Child termination channel not found"))?;
terminate_sender.send(()).map_err(|_| {
SmError::Bug("Failed to send termination signal to child process")
})?;
}
}
Ok(())
}
fn start_service(&mut self, index: usize) -> SmResult {
let service = &mut self.table.services[index];
*service.status.lock()? = ServiceStatus::Running; *service.status.lock()? = ServiceStatus::Running;
@ -191,29 +215,38 @@ impl App {
let (terminate_send, terminate_recv) = mpsc::channel(); let (terminate_send, terminate_recv) = mpsc::channel();
self.thread_terminates.push(terminate_send); self.thread_terminates.insert(index, terminate_send);
let service_status = service.status.clone();
std::thread::spawn(move || { std::thread::spawn(move || {
match child_process_thread(child, stdout_send, terminate_recv) { match child_process_thread(child, stdout_send, service_status, terminate_recv) {
Ok(_) => {} Ok(_) => {}
Err(e) => std::fs::write("error.txt", e.to_string()).unwrap(), Err(e) => {
let _ = std::fs::write("error.txt", e.to_string());
}
} }
}); });
Ok(()) Ok(())
} }
} }
fn child_process_thread( fn child_process_thread(
child: Child, child: Child,
stdout_send: mpsc::Sender<StdioSendBuf>, stdout_send: mpsc::Sender<StdioSendBuf>,
service_status: Arc<Mutex<ServiceStatus>>,
terminate_channel: mpsc::Receiver<()>, terminate_channel: mpsc::Receiver<()>,
) -> io::Result<()> { ) -> SmResult {
let mut child = child; let mut child = child;
let mut stdout = child.stdout.take().unwrap(); let mut stdout = child
.stdout
.take()
.ok_or(SmError::Bug("Stdout of child could not be taken"))?;
loop { let result = loop {
match terminate_channel.try_recv() { match terminate_channel.try_recv() {
Ok(_) | Err(TryRecvError::Disconnected) => break, Ok(_) | Err(TryRecvError::Disconnected) => break Ok(()),
Err(TryRecvError::Empty) => {} Err(TryRecvError::Empty) => {}
} }
@ -224,18 +257,29 @@ fn child_process_thread(
Ok(n) => { Ok(n) => {
stdout_send stdout_send
.send((stdout_buf, n)) .send((stdout_buf, n))
.map_err(|_| io::Error::from(io::ErrorKind::Other))?; .map_err(|_| SmError::Bug("Failed to send stdout to main thread"))?;
} }
Err(e) if e.kind() == io::ErrorKind::Interrupted => {} Err(e) if e.kind() == io::ErrorKind::Interrupted => {}
Err(e) => return Err(e), Err(e) => break Err(e.into()),
}; };
} };
match child.kill() { match child.kill() {
Ok(()) => {} Ok(()) => {
*service_status.lock().map_err(|_| SmError::MutexPoisoned)? = ServiceStatus::Killed
}
Err(e) if e.kind() == io::ErrorKind::InvalidInput => {} Err(e) if e.kind() == io::ErrorKind::InvalidInput => {}
Err(e) => return Err(e), Err(e) => return Err(e.into()),
} }
Ok(()) let mut send_message_buf = [0; STDIO_SEND_BUF_SIZE];
let kill_msg = "\n\n<Process was killed>\n";
send_message_buf
.as_mut_slice()
.write_all(kill_msg.as_bytes())?;
stdout_send
.send((send_message_buf, kill_msg.len()))
.map_err(|_| SmError::Bug("Failed to send stdout to main thread"))?;
result
} }

View file

@ -1,7 +1,7 @@
use crate::controller::StdioSendBuf; use crate::controller::StdioSendBuf;
use std::collections::HashMap; use std::collections::HashMap;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::{mpsc, Mutex}; use std::sync::{mpsc, Arc, Mutex};
use tui::widgets::TableState; use tui::widgets::TableState;
pub use error::{SmError, SmResult}; pub use error::{SmError, SmResult};
@ -10,7 +10,7 @@ pub use error::{SmError, SmResult};
pub struct App { pub struct App {
pub table: AppState, pub table: AppState,
pub selected: Option<usize>, pub selected: Option<usize>,
pub thread_terminates: Vec<mpsc::Sender<()>>, pub thread_terminates: HashMap<usize, mpsc::Sender<()>>,
} }
#[derive(Debug)] #[derive(Debug)]
@ -25,7 +25,7 @@ pub struct Service {
pub name: String, pub name: String,
pub workdir: PathBuf, pub workdir: PathBuf,
pub env: HashMap<String, String>, pub env: HashMap<String, String>,
pub status: Mutex<ServiceStatus>, pub status: Arc<Mutex<ServiceStatus>>,
pub std_io_buf: Vec<u8>, pub std_io_buf: Vec<u8>,
pub stdout: StdIoStream, pub stdout: StdIoStream,
} }
@ -37,12 +37,12 @@ pub struct StdIoStream {
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[allow(dead_code)]
pub enum ServiceStatus { pub enum ServiceStatus {
NotStarted, NotStarted,
Running, Running,
Exited, Exited,
Failed(u8), Failed(u8),
Killed,
} }
pub mod config { pub mod config {
@ -71,6 +71,9 @@ mod error {
FailedToStartChild(io::Error), FailedToStartChild(io::Error),
MutexPoisoned, MutexPoisoned,
FailedToSendStdio, FailedToSendStdio,
/// This should never happen and would be a panic in most programs, but panicking here
/// might fuck things up badly, so we don't want to
Bug(&'static str),
} }
impl From<io::Error> for SmError { impl From<io::Error> for SmError {
@ -94,6 +97,7 @@ mod error {
SmError::FailedToSendStdio => { SmError::FailedToSendStdio => {
f.write_str("Failed to send stdio to display thread. This is a bug.") f.write_str("Failed to send stdio to display thread. This is a bug.")
} }
SmError::Bug(str) => write!(f, "{}. This is a bug.", str),
} }
} }
} }

View file

@ -95,10 +95,12 @@ fn render_help_footer<B: Backend>(f: &mut Frame<B>, app: &App, area: Rect) {
let paragraph = Paragraph::new(if app.is_table() { let paragraph = Paragraph::new(if app.is_table() {
vec![Spans::from( vec![Spans::from(
"q-quit down-down up-up enter-select r-run service", "q-quit down-down up-up enter-select r-run service k-kill service",
)] )]
} else { } else {
vec![Spans::from("q-back esc-back r-run service")] vec![Spans::from(
"q-back esc-back r-run service k-kill service",
)]
}) })
.block(block); .block(block);
@ -112,6 +114,7 @@ impl Display for ServiceStatus {
ServiceStatus::Exited => f.write_str("exited (0)"), ServiceStatus::Exited => f.write_str("exited (0)"),
ServiceStatus::Failed(code) => write!(f, "failed ({})", code), ServiceStatus::Failed(code) => write!(f, "failed ({})", code),
ServiceStatus::NotStarted => f.write_str("not started"), ServiceStatus::NotStarted => f.write_str("not started"),
ServiceStatus::Killed => f.write_str("killed"),
} }
} }
} }