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,
io::stdout,
panic,
sync::{
atomic::{AtomicBool, Ordering},
mpsc, Arc, Condvar, Mutex,
},
sync::{mpsc, Arc, Condvar, Mutex},
thread,
time::Duration,
};
@ -31,7 +28,6 @@ use tui::{backend::CrosstermBackend, Terminal};
use bottom::{
canvas::{self, canvas_styling::CanvasStyling},
constants::*,
data_conversion::*,
options::*,
*,
@ -87,33 +83,33 @@ fn main() -> Result<()> {
// Check if the current environment is in a terminal.
check_if_terminal();
// Create termination mutex and cvar
#[allow(clippy::mutex_atomic)]
let thread_termination_lock = Arc::new(Mutex::new(false));
let thread_termination_cvar = Arc::new(Condvar::new());
// Create termination mutex and cvar. We use this setup because we need to sleep at some points in the update
// thread, but we want to be able to interrupt the "sleep" if a termination occurs.
let termination_lock = Arc::new(Mutex::new(false));
let termination_cvar = Arc::new(Condvar::new());
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 = create_collection_thread(
sender.clone(),
collection_thread_ctrl_receiver,
thread_termination_lock.clone(),
thread_termination_cvar.clone(),
termination_lock.clone(),
termination_cvar.clone(),
&app.app_config_fields,
app.filters.clone(),
app.used_widgets,
);
// Set up input handling loop thread.
let _input_thread = create_input_thread(sender.clone(), thread_termination_lock.clone());
// Set up the input handling loop thread.
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 lock = thread_termination_lock.clone();
let cvar = thread_termination_cvar.clone();
let cleaning_sender = sender;
let lock = termination_lock.clone();
let cvar = termination_cvar.clone();
let cleaning_sender = sender.clone();
let offset_wait_time = app.app_config_fields.retention_ms + 60000;
thread::spawn(move || {
loop {
@ -163,20 +159,21 @@ fn main() -> Result<()> {
panic::set_hook(Box::new(panic_hook));
// Set termination hook
let is_terminated = Arc::new(AtomicBool::new(false));
let ist_clone = is_terminated.clone();
ctrlc::set_handler(move || {
ist_clone.store(true, Ordering::SeqCst);
let _ = sender.send(BottomEvent::Terminate);
})?;
let mut first_run = true;
// Draw once first to initialize the canvas, so it doesn't feel like it's frozen.
try_drawing(&mut terminal, &mut app, &mut painter)?;
while !is_terminated.load(Ordering::SeqCst) {
// 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_timeout(Duration::from_millis(TICK_RATE_IN_MILLISECONDS)) {
loop {
if let Ok(recv) = receiver.recv() {
match recv {
BottomEvent::Terminate => {
break;
}
BottomEvent::Resize => {
// FIXME: This is bugged with frozen?
try_drawing(&mut terminal, &mut app, &mut painter)?;
@ -328,8 +325,8 @@ fn main() -> Result<()> {
}
// I think doing it in this order is safe...
*thread_termination_lock.lock().unwrap() = true;
thread_termination_cvar.notify_all();
*termination_lock.lock().unwrap() = true;
termination_cvar.notify_all();
cleanup_terminal(&mut terminal)?;
Ok(())

View File

@ -79,6 +79,7 @@ pub enum BottomEvent {
PasteEvent(String),
Update(Box<data_harvester::Data>),
Clean,
Terminate,
}
#[derive(Debug)]
@ -482,7 +483,7 @@ pub fn create_input_thread(
pub fn create_collection_thread(
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,
) -> JoinHandle<()> {
let temp_type = app_config_fields.temperature_type;
@ -504,7 +505,7 @@ pub fn create_collection_thread(
loop {
// 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 {
drop(is_terminated);
break;
@ -533,7 +534,7 @@ pub fn create_collection_thread(
data_state.update_data();
// 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 {
drop(is_terminated);
break;
@ -547,8 +548,8 @@ pub fn create_collection_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(
termination_ctrl_lock.lock().unwrap(),
if let Ok((is_terminated, _)) = termination_cvar.wait_timeout(
termination_lock.lock().unwrap(),
Duration::from_millis(update_time),
) {
if *is_terminated {