mirror of
https://github.com/ClementTsang/bottom.git
synced 2025-07-13 00:34:46 +02:00
other: simplify termination/event loop logic (#1169)
This just simplifies the logic around ctrl-c and termination logic and event loop logic to something simpler and without the need for timeouts and/or atomics. Instead, we just make termination an event sent by ctrl-c and use the same receiver for event handling to react to it and break the loop.
This commit is contained in:
parent
b6dc17cfb3
commit
69318465ae
@ -13,10 +13,7 @@ use std::{
|
|||||||
boxed::Box,
|
boxed::Box,
|
||||||
io::stdout,
|
io::stdout,
|
||||||
panic,
|
panic,
|
||||||
sync::{
|
sync::{mpsc, Arc, Condvar, Mutex},
|
||||||
atomic::{AtomicBool, Ordering},
|
|
||||||
mpsc, Arc, Condvar, Mutex,
|
|
||||||
},
|
|
||||||
thread,
|
thread,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
@ -31,7 +28,6 @@ use tui::{backend::CrosstermBackend, Terminal};
|
|||||||
|
|
||||||
use bottom::{
|
use bottom::{
|
||||||
canvas::{self, canvas_styling::CanvasStyling},
|
canvas::{self, canvas_styling::CanvasStyling},
|
||||||
constants::*,
|
|
||||||
data_conversion::*,
|
data_conversion::*,
|
||||||
options::*,
|
options::*,
|
||||||
*,
|
*,
|
||||||
@ -87,33 +83,33 @@ fn main() -> Result<()> {
|
|||||||
// Check if the current environment is in a terminal.
|
// Check if the current environment is in a terminal.
|
||||||
check_if_terminal();
|
check_if_terminal();
|
||||||
|
|
||||||
// Create termination mutex and cvar
|
// Create termination mutex and cvar. We use this setup because we need to sleep at some points in the update
|
||||||
#[allow(clippy::mutex_atomic)]
|
// thread, but we want to be able to interrupt the "sleep" if a termination occurs.
|
||||||
let thread_termination_lock = Arc::new(Mutex::new(false));
|
let termination_lock = Arc::new(Mutex::new(false));
|
||||||
let thread_termination_cvar = Arc::new(Condvar::new());
|
let termination_cvar = Arc::new(Condvar::new());
|
||||||
|
|
||||||
let (sender, receiver) = mpsc::channel();
|
let (sender, receiver) = mpsc::channel();
|
||||||
|
|
||||||
// Set up event loop thread; we set this up early to speed up first-time-to-data.
|
// Set up the event loop thread; we set this up early to speed up first-time-to-data.
|
||||||
let (collection_thread_ctrl_sender, collection_thread_ctrl_receiver) = mpsc::channel();
|
let (collection_thread_ctrl_sender, collection_thread_ctrl_receiver) = mpsc::channel();
|
||||||
let _collection_thread = create_collection_thread(
|
let _collection_thread = create_collection_thread(
|
||||||
sender.clone(),
|
sender.clone(),
|
||||||
collection_thread_ctrl_receiver,
|
collection_thread_ctrl_receiver,
|
||||||
thread_termination_lock.clone(),
|
termination_lock.clone(),
|
||||||
thread_termination_cvar.clone(),
|
termination_cvar.clone(),
|
||||||
&app.app_config_fields,
|
&app.app_config_fields,
|
||||||
app.filters.clone(),
|
app.filters.clone(),
|
||||||
app.used_widgets,
|
app.used_widgets,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Set up input handling loop thread.
|
// Set up the input handling loop thread.
|
||||||
let _input_thread = create_input_thread(sender.clone(), thread_termination_lock.clone());
|
let _input_thread = create_input_thread(sender.clone(), termination_lock.clone());
|
||||||
|
|
||||||
// Set up cleaning loop thread.
|
// Set up the cleaning loop thread.
|
||||||
let _cleaning_thread = {
|
let _cleaning_thread = {
|
||||||
let lock = thread_termination_lock.clone();
|
let lock = termination_lock.clone();
|
||||||
let cvar = thread_termination_cvar.clone();
|
let cvar = termination_cvar.clone();
|
||||||
let cleaning_sender = sender;
|
let cleaning_sender = sender.clone();
|
||||||
let offset_wait_time = app.app_config_fields.retention_ms + 60000;
|
let offset_wait_time = app.app_config_fields.retention_ms + 60000;
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
loop {
|
loop {
|
||||||
@ -163,20 +159,21 @@ fn main() -> Result<()> {
|
|||||||
panic::set_hook(Box::new(panic_hook));
|
panic::set_hook(Box::new(panic_hook));
|
||||||
|
|
||||||
// Set termination hook
|
// Set termination hook
|
||||||
let is_terminated = Arc::new(AtomicBool::new(false));
|
|
||||||
let ist_clone = is_terminated.clone();
|
|
||||||
ctrlc::set_handler(move || {
|
ctrlc::set_handler(move || {
|
||||||
ist_clone.store(true, Ordering::SeqCst);
|
let _ = sender.send(BottomEvent::Terminate);
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let mut first_run = true;
|
let mut first_run = true;
|
||||||
|
|
||||||
// Draw once first to initialize the canvas, so it doesn't feel like it's frozen.
|
// Draw once first to initialize the canvas, so it doesn't feel like it's frozen.
|
||||||
try_drawing(&mut terminal, &mut app, &mut painter)?;
|
try_drawing(&mut terminal, &mut app, &mut painter)?;
|
||||||
|
|
||||||
while !is_terminated.load(Ordering::SeqCst) {
|
loop {
|
||||||
// TODO: Would be good to instead use a mix of is_terminated check + recv. Probably use a termination event instead.
|
if let Ok(recv) = receiver.recv() {
|
||||||
if let Ok(recv) = receiver.recv_timeout(Duration::from_millis(TICK_RATE_IN_MILLISECONDS)) {
|
|
||||||
match recv {
|
match recv {
|
||||||
|
BottomEvent::Terminate => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
BottomEvent::Resize => {
|
BottomEvent::Resize => {
|
||||||
// FIXME: This is bugged with frozen?
|
// FIXME: This is bugged with frozen?
|
||||||
try_drawing(&mut terminal, &mut app, &mut painter)?;
|
try_drawing(&mut terminal, &mut app, &mut painter)?;
|
||||||
@ -328,8 +325,8 @@ fn main() -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// I think doing it in this order is safe...
|
// I think doing it in this order is safe...
|
||||||
*thread_termination_lock.lock().unwrap() = true;
|
*termination_lock.lock().unwrap() = true;
|
||||||
thread_termination_cvar.notify_all();
|
termination_cvar.notify_all();
|
||||||
cleanup_terminal(&mut terminal)?;
|
cleanup_terminal(&mut terminal)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
11
src/lib.rs
11
src/lib.rs
@ -79,6 +79,7 @@ pub enum BottomEvent {
|
|||||||
PasteEvent(String),
|
PasteEvent(String),
|
||||||
Update(Box<data_harvester::Data>),
|
Update(Box<data_harvester::Data>),
|
||||||
Clean,
|
Clean,
|
||||||
|
Terminate,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -482,7 +483,7 @@ pub fn create_input_thread(
|
|||||||
|
|
||||||
pub fn create_collection_thread(
|
pub fn create_collection_thread(
|
||||||
sender: Sender<BottomEvent>, control_receiver: Receiver<ThreadEvent>,
|
sender: Sender<BottomEvent>, control_receiver: Receiver<ThreadEvent>,
|
||||||
termination_ctrl_lock: Arc<Mutex<bool>>, termination_ctrl_cvar: Arc<Condvar>,
|
termination_lock: Arc<Mutex<bool>>, termination_cvar: Arc<Condvar>,
|
||||||
app_config_fields: &AppConfigFields, filters: DataFilters, used_widget_set: UsedWidgets,
|
app_config_fields: &AppConfigFields, filters: DataFilters, used_widget_set: UsedWidgets,
|
||||||
) -> JoinHandle<()> {
|
) -> JoinHandle<()> {
|
||||||
let temp_type = app_config_fields.temperature_type;
|
let temp_type = app_config_fields.temperature_type;
|
||||||
@ -504,7 +505,7 @@ pub fn create_collection_thread(
|
|||||||
|
|
||||||
loop {
|
loop {
|
||||||
// Check once at the very top... don't block though.
|
// Check once at the very top... don't block though.
|
||||||
if let Ok(is_terminated) = termination_ctrl_lock.try_lock() {
|
if let Ok(is_terminated) = termination_lock.try_lock() {
|
||||||
if *is_terminated {
|
if *is_terminated {
|
||||||
drop(is_terminated);
|
drop(is_terminated);
|
||||||
break;
|
break;
|
||||||
@ -533,7 +534,7 @@ pub fn create_collection_thread(
|
|||||||
data_state.update_data();
|
data_state.update_data();
|
||||||
|
|
||||||
// Yet another check to bail if needed... do not block!
|
// Yet another check to bail if needed... do not block!
|
||||||
if let Ok(is_terminated) = termination_ctrl_lock.try_lock() {
|
if let Ok(is_terminated) = termination_lock.try_lock() {
|
||||||
if *is_terminated {
|
if *is_terminated {
|
||||||
drop(is_terminated);
|
drop(is_terminated);
|
||||||
break;
|
break;
|
||||||
@ -547,8 +548,8 @@ pub fn create_collection_thread(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This is actually used as a "sleep" that can be interrupted by another thread.
|
// This is actually used as a "sleep" that can be interrupted by another thread.
|
||||||
if let Ok((is_terminated, _wait_timeout_result)) = termination_ctrl_cvar.wait_timeout(
|
if let Ok((is_terminated, _)) = termination_cvar.wait_timeout(
|
||||||
termination_ctrl_lock.lock().unwrap(),
|
termination_lock.lock().unwrap(),
|
||||||
Duration::from_millis(update_time),
|
Duration::from_millis(update_time),
|
||||||
) {
|
) {
|
||||||
if *is_terminated {
|
if *is_terminated {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user