Skip to content

Commit

Permalink
fix: High CPU usage
Browse files Browse the repository at this point in the history
  • Loading branch information
akarsh1995 committed Aug 9, 2023
1 parent ebd747d commit cb1c862
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 60 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ All notable changes to this project will be documented in this file.
- Some questions did not appear in "All" question list because they were not attached to any topic.
- To resolve Unknown topic tag is added to the questions which do not have any topic tag.
- App now successfully restores the terminal state. No residual prints on closing the app.
- High CPU usage due to 100ms tick interval. Now tick interval changed to 5 seconds.

## [0.2.0] - 2023-07-30

Expand Down
38 changes: 15 additions & 23 deletions src/app_ui/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::rc::Rc;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;

use super::async_task_channel::{ChannelRequestSender, ChannelResponseReceiver};
use super::async_task_channel::{ChannelRequestSender, TaskResponse};
use super::event::VimPingSender;
use super::widgets::help_bar::HelpBar;
use super::widgets::notification::{Notification, WidgetName, WidgetVariant};
Expand All @@ -30,8 +30,6 @@ pub struct App {

pub task_request_sender: ChannelRequestSender,

pub task_response_recv: ChannelResponseReceiver,

pub pending_notifications: VecDeque<Option<Notification>>,

pub(crate) popup_stack: Vec<Popup>,
Expand All @@ -47,7 +45,6 @@ impl App {
/// Constructs a new instance of [`App`].
pub fn new(
task_request_sender: ChannelRequestSender,
task_response_recv: ChannelResponseReceiver,
vim_tx: VimPingSender,
vim_running: Arc<AtomicBool>,
config: Rc<Config>,
Expand Down Expand Up @@ -84,7 +81,6 @@ impl App {
widget_map: IndexMap::from(order),
selected_wid_idx: 0,
task_request_sender,
task_response_recv,
pending_notifications: vec![].into(),
popup_stack: vec![],
vim_running,
Expand Down Expand Up @@ -160,7 +156,11 @@ impl App {
}

/// Handles the tick event of the terminal.
pub fn tick(&mut self) -> AppResult<()> {
pub fn tick(&mut self, task: Option<TaskResponse>) -> AppResult<()> {
if let Some(task) = task {
self.process_task(task)?;
}

if let Some(popup) = self.get_current_popup_mut() {
if !popup.is_active() {
self.popup_stack.pop();
Expand All @@ -179,19 +179,15 @@ impl App {
self.pending_notifications.push_back(Some(notif));
}
}

self.check_for_task()?;
self.process_pending_notification()?;
Ok(())
}

fn check_for_task(&mut self) -> AppResult<()> {
if let Ok(task_result) = self.task_response_recv.try_recv() {
self.widget_map
.get_mut(&task_result.get_widget_name())
.unwrap()
.process_task_response(task_result)?;
}
pub fn process_task(&mut self, task: TaskResponse) -> AppResult<()> {
self.widget_map
.get_mut(&task.get_widget_name())
.unwrap()
.process_task_response(task)?;
Ok(())
}

Expand Down Expand Up @@ -223,15 +219,11 @@ impl App {

pub fn handle_key_events(&mut self, key_event: KeyEvent) -> AppResult<()> {
let mut p_notif = None;

// if ui has active popups then send only events registered with popup
if let Some(popup) = self.get_current_popup_mut() {
p_notif = popup.handler(key_event)?;
self.pending_notifications.push_back(p_notif);
self.process_pending_notification()?;
return Ok(());
}

if self.get_current_widget().parent_can_handle_events() {
} else if self.get_current_widget().parent_can_handle_events() {
match key_event.code {
KeyCode::Left => p_notif = self.next_widget()?,
KeyCode::Right => p_notif = self.prev_widget()?,
Expand All @@ -246,13 +238,13 @@ impl App {
_ => {
p_notif = self.get_current_widget_mut().handler(key_event)?;
}
};
}
} else {
p_notif = self.get_current_widget_mut().handler(key_event)?;
}

self.pending_notifications.push_back(p_notif);
self.process_pending_notification()?;
self.tick(None)?;

Ok(())
}
Expand Down
5 changes: 0 additions & 5 deletions src/app_ui/async_task_channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,3 @@ use super::widgets::notification::WidgetName;

pub type RequestSendError = tokio::sync::mpsc::error::SendError<TaskRequest>;
pub type RequestRecvError = tokio::sync::mpsc::error::TryRecvError;

pub type ResponseSendError = crossbeam::channel::SendError<TaskResponse>;
pub type ResponseReceiveError = crossbeam::channel::RecvError;

// pub type
25 changes: 16 additions & 9 deletions src/app_ui/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@ use std::{
time::Duration,
};

use futures::{future::FutureExt, StreamExt};
use futures_timer::Delay;
use futures::StreamExt;

use crossterm::event::EventStream;

use crate::errors::AppResult;

/// Terminal events.
#[derive(Clone, Copy, Debug)]
#[derive(Debug)]
pub enum Event {
/// Terminal tick.
Tick,
Expand All @@ -23,6 +22,7 @@ pub enum Event {
/// Terminal resize.
Resize(u16, u16),

TaskResponse(Box<TaskResponse>),
/// redraws the terminal
Redraw,
}
Expand All @@ -38,6 +38,8 @@ pub struct EventHandler {
}

pub use tokio::sync::mpsc::channel as vim_ping_channel;

use super::async_task_channel::TaskResponse;
pub type VimPingSender = tokio::sync::mpsc::Sender<i32>;
pub type VimPingReceiver = tokio::sync::mpsc::Receiver<i32>;

Expand All @@ -47,20 +49,25 @@ pub async fn look_for_events(
sender: std::sync::mpsc::Sender<Event>,
vim_running_loop_ref: Arc<AtomicBool>,
mut vim_rx: VimPingReceiver,
mut should_stop_looking_events: tokio::sync::oneshot::Receiver<bool>,
) -> AppResult<()> {
let tick_rate = Duration::from_millis(tick_rate);
let mut tick_rate = tokio::time::interval(Duration::from_millis(tick_rate));

let mut reader = EventStream::new();

loop {
let delay = Delay::new(tick_rate).fuse();
let event = reader.next().fuse();

tokio::select! {
_ = delay => {
_ = tick_rate.tick() => {
sender.send(Event::Tick)?
},
maybe_event = event => {
maybe_stop = &mut should_stop_looking_events => {
if let Ok(stop) = maybe_stop {
if stop {
break;
}
}
}
maybe_event = reader.next() => {
match maybe_event {
Some(event) => {
match event? {
Expand Down
7 changes: 2 additions & 5 deletions src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,8 @@ pub enum LcAppError {
#[error("Task request receive error sync to async context: {0}")]
RequestRecvError(#[from] RequestRecvError),

#[error("Task response send error async to sync context: {0}")]
ResponseSendError(#[from] Box<ResponseSendError>),

#[error("Task response receive error async to sync context: {0}")]
ResponseReceiveError(#[from] ResponseReceiveError),
#[error("Cannot send one shot signal to stop looking for events. {0}")]
StopEventsSignalSendError(String),

#[error("Deserialization/serialization failed: {0}")]
DeserializeError(#[from] serde_json::Error),
Expand Down
53 changes: 38 additions & 15 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use leetcode_tui_rs::app_ui::async_task_channel::{request_channel, response_channel};
use leetcode_tui_rs::app_ui::async_task_channel::{ChannelRequestSender, ChannelResponseReceiver};
use leetcode_tui_rs::app_ui::async_task_channel::request_channel;
use leetcode_tui_rs::app_ui::async_task_channel::ChannelRequestSender;
use leetcode_tui_rs::app_ui::tui::Tui;
use leetcode_tui_rs::config::Config;
use leetcode_tui_rs::entities::QuestionEntity;
use leetcode_tui_rs::errors::AppResult;
use leetcode_tui_rs::errors::LcAppError;
use sea_orm::Database;
use tokio::task::JoinHandle;

Expand Down Expand Up @@ -48,18 +49,18 @@ async fn main() -> AppResult<()> {
let client = client_clone;

let (tx_request, rx_request) = request_channel();
let (tx_response, rx_response) = response_channel();
let client = client.clone();

let task_receiver_from_app: JoinHandle<AppResult<()>> = tokio::spawn(async move {
async_tasks_executor(rx_request, tx_response, &client, &database_client).await?;
Ok(())
});

let backend = CrosstermBackend::new(io::stderr());
let terminal = Terminal::new(backend)?;

let (ev_sender, ev_receiver) = std::sync::mpsc::channel();
let ev_sender_clone = ev_sender.clone();

let task_receiver_from_app: JoinHandle<AppResult<()>> = tokio::spawn(async move {
async_tasks_executor(rx_request, ev_sender_clone, &client, &database_client).await?;
Ok(())
});

let tui = Tui::new(
terminal,
Expand All @@ -72,13 +73,31 @@ async fn main() -> AppResult<()> {
let vim_running = Arc::new(AtomicBool::new(false));
let vim_running_loop_ref = vim_running.clone();
let (vim_tx, vim_rx) = vim_ping_channel(10);
let (should_stop_looking_for_events, should_stop_looking_events_rx) =
tokio::sync::oneshot::channel();

tokio::task::spawn_blocking(move || {
run_app(tx_request, rx_response, tui, vim_tx, vim_running, config).unwrap()
run_app(
tx_request,
tui,
vim_tx,
vim_running,
config,
should_stop_looking_for_events,
)
.unwrap();
});

// blog post does not work in separate thread
match look_for_events(100, ev_sender, vim_running_loop_ref, vim_rx).await {
match look_for_events(
5000,
ev_sender,
vim_running_loop_ref,
vim_rx,
should_stop_looking_events_rx,
)
.await
{
Ok(_) => Ok(()),
Err(e) => match e {
leetcode_tui_rs::errors::LcAppError::SyncSendError(_) => Ok(()),
Expand All @@ -93,31 +112,35 @@ async fn main() -> AppResult<()> {

fn run_app(
tx_request: ChannelRequestSender,
rx_response: ChannelResponseReceiver,
mut tui: Tui,
vim_tx: VimPingSender,
vim_running: Arc<AtomicBool>,
config: Config,
stop_events_tx: tokio::sync::oneshot::Sender<bool>,
) -> AppResult<()> {
let config = Rc::new(config);
tui.init()?;
let mut app = App::new(tx_request, rx_response, vim_tx, vim_running, config)?;
let mut app = App::new(tx_request, vim_tx, vim_running, config)?;

// Start the main loop.
while app.running {
// Render the user interface.
tui.draw(&mut app)?;

// Handle events.
match tui.events.next()? {
Event::Tick => app.tick()?,
Event::Tick => app.tick(None)?,
Event::Key(key_event) => app.handle_key_events(key_event)?,
Event::Mouse(_) => {}
Event::Resize(_, _) => {}
Event::Redraw => tui.reinit()?,
Event::Resize(_, _) | Event::Redraw => tui.reinit()?,
Event::TaskResponse(response) => app.tick(Some(*response))?,
}
}

// Exit the user interface.
tui.exit()?;
stop_events_tx
.send(true)
.map_err(|e| LcAppError::StopEventsSignalSendError(e.to_string()))?;
Ok(())
}
7 changes: 4 additions & 3 deletions src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::app_ui::event::Event;
use crate::deserializers::problemset_question_list::Question;
use crate::errors::AppResult;
use crate::graphql::problemset_question_list::Query as QuestionDbQuery;
Expand Down Expand Up @@ -88,17 +89,17 @@ pub async fn get_config() -> AppResult<Option<Config>> {
}
}

use crate::app_ui::async_task_channel::{ChannelRequestReceiver, ChannelResponseSender};
use crate::app_ui::async_task_channel::ChannelRequestReceiver;

pub async fn async_tasks_executor(
mut rx_request: ChannelRequestReceiver,
tx_response: ChannelResponseSender,
tx_response: std::sync::mpsc::Sender<Event>,
client: &reqwest::Client,
conn: &DatabaseConnection,
) -> AppResult<()> {
while let Some(task) = rx_request.recv().await {
let response = task.execute(client, conn).await;
tx_response.send(response).map_err(Box::new)?;
tx_response.send(Event::TaskResponse(Box::new(response)))?;
}
Ok(())
}
Expand Down

0 comments on commit cb1c862

Please sign in to comment.