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:
Clement Tsang 2023-05-25 00:13:04 -04:00 committed by GitHub
parent b6dc17cfb3
commit 69318465ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 29 additions and 31 deletions

View File

@ -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(())

View File

@ -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 {