perf(network): Improved performance and reactiveness of the UI by speeding up network requests and clearing the channel whenever a request is cancelled/the UI is routing
This commit is contained in:
@@ -165,7 +165,7 @@ impl<'a> App<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if self.is_routing {
|
if self.is_routing {
|
||||||
if self.is_loading && !self.should_refresh {
|
if !self.should_refresh {
|
||||||
self.cancellation_token.cancel();
|
self.cancellation_token.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -582,7 +582,6 @@ mod tests {
|
|||||||
async fn test_radarr_on_tick_routing_while_long_request_is_running_should_cancel_request() {
|
async fn test_radarr_on_tick_routing_while_long_request_is_running_should_cancel_request() {
|
||||||
let (mut app, mut sync_network_rx) = construct_app_unit();
|
let (mut app, mut sync_network_rx) = construct_app_unit();
|
||||||
app.is_routing = true;
|
app.is_routing = true;
|
||||||
app.is_loading = true;
|
|
||||||
app.should_refresh = false;
|
app.should_refresh = false;
|
||||||
|
|
||||||
app
|
app
|
||||||
|
|||||||
+25
-5
@@ -6,6 +6,7 @@ use std::panic::PanicHookInfo;
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
use std::{io, panic, process};
|
use std::{io, panic, process};
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
@@ -20,11 +21,12 @@ use crossterm::execute;
|
|||||||
use crossterm::terminal::{
|
use crossterm::terminal::{
|
||||||
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
|
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
|
||||||
};
|
};
|
||||||
use log::error;
|
use log::{error, warn};
|
||||||
use network::NetworkTrait;
|
use network::NetworkTrait;
|
||||||
use ratatui::backend::CrosstermBackend;
|
use ratatui::backend::CrosstermBackend;
|
||||||
use ratatui::Terminal;
|
use ratatui::Terminal;
|
||||||
use reqwest::{Certificate, Client};
|
use reqwest::{Certificate, Client};
|
||||||
|
use tokio::select;
|
||||||
use tokio::sync::mpsc::Receiver;
|
use tokio::sync::mpsc::Receiver;
|
||||||
use tokio::sync::{mpsc, Mutex};
|
use tokio::sync::{mpsc, Mutex};
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
@@ -144,9 +146,24 @@ async fn start_networking(
|
|||||||
) {
|
) {
|
||||||
let mut network = Network::new(app, cancellation_token, client);
|
let mut network = Network::new(app, cancellation_token, client);
|
||||||
|
|
||||||
while let Some(network_event) = network_rx.recv().await {
|
loop {
|
||||||
if let Err(e) = network.handle_network_event(network_event).await {
|
select! {
|
||||||
error!("Encountered an error handling network event: {e:?}");
|
Some(network_event) = network_rx.recv() => {
|
||||||
|
if let Err(e) = network.handle_network_event(network_event).await {
|
||||||
|
error!("Encountered an error handling network event: {e:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = network.cancellation_token.cancelled() => {
|
||||||
|
warn!("Clearing network channel");
|
||||||
|
while network_rx.try_recv().is_ok() {
|
||||||
|
// Discard the message
|
||||||
|
}
|
||||||
|
{
|
||||||
|
/* Wrapped in its own block so we don't lock the app arc early,
|
||||||
|
so UI is still processed */
|
||||||
|
network.reset_cancellation_token().await;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -228,7 +245,10 @@ fn load_config(path: &str) -> Result<AppConfig> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn build_network_client(config: &AppConfig) -> Client {
|
fn build_network_client(config: &AppConfig) -> Client {
|
||||||
let mut client_builder = Client::builder();
|
let mut client_builder = Client::builder()
|
||||||
|
.pool_max_idle_per_host(10)
|
||||||
|
.http2_keep_alive_interval(Duration::from_secs(5))
|
||||||
|
.tcp_keepalive(Duration::from_secs(5));
|
||||||
|
|
||||||
if let Some(ref cert_path) = config.radarr.ssl_cert_path {
|
if let Some(ref cert_path) = config.radarr.ssl_cert_path {
|
||||||
let cert = create_cert(cert_path, "Radarr");
|
let cert = create_cert(cert_path, "Radarr");
|
||||||
|
|||||||
+8
-4
@@ -40,7 +40,7 @@ pub trait NetworkTrait {
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Network<'a, 'b> {
|
pub struct Network<'a, 'b> {
|
||||||
client: Client,
|
client: Client,
|
||||||
cancellation_token: CancellationToken,
|
pub cancellation_token: CancellationToken,
|
||||||
pub app: &'a Arc<Mutex<App<'b>>>,
|
pub app: &'a Arc<Mutex<App<'b>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,6 +74,13 @@ impl<'a, 'b> Network<'a, 'b> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) async fn reset_cancellation_token(&mut self) {
|
||||||
|
let mut app = self.app.lock().await;
|
||||||
|
self.cancellation_token = app.reset_cancellation_token();
|
||||||
|
app.should_refresh = true;
|
||||||
|
app.is_loading = false;
|
||||||
|
}
|
||||||
|
|
||||||
async fn handle_request<B, R>(
|
async fn handle_request<B, R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
request_props: RequestProps<B>,
|
request_props: RequestProps<B>,
|
||||||
@@ -89,9 +96,6 @@ impl<'a, 'b> Network<'a, 'b> {
|
|||||||
select! {
|
select! {
|
||||||
_ = self.cancellation_token.cancelled() => {
|
_ = self.cancellation_token.cancelled() => {
|
||||||
warn!("Received Cancel request. Cancelling request to: {request_uri}");
|
warn!("Received Cancel request. Cancelling request to: {request_uri}");
|
||||||
let mut app = self.app.lock().await;
|
|
||||||
self.cancellation_token = app.reset_cancellation_token();
|
|
||||||
app.is_loading = false;
|
|
||||||
Ok(R::default())
|
Ok(R::default())
|
||||||
}
|
}
|
||||||
resp = self.call_api(request_props).await.send() => {
|
resp = self.call_api(request_props).await.send() => {
|
||||||
|
|||||||
@@ -181,11 +181,31 @@ mod tests {
|
|||||||
|
|
||||||
assert!(!async_server.matched_async().await);
|
assert!(!async_server.matched_async().await);
|
||||||
assert!(app_arc.lock().await.error.text.is_empty());
|
assert!(app_arc.lock().await.error.text.is_empty());
|
||||||
assert!(!network.cancellation_token.is_cancelled());
|
|
||||||
assert!(resp.is_ok());
|
assert!(resp.is_ok());
|
||||||
assert_eq!(resp.unwrap(), Test::default());
|
assert_eq!(resp.unwrap(), Test::default());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_reset_cancellation_token() {
|
||||||
|
let cancellation_token = CancellationToken::new();
|
||||||
|
let (tx, _) = mpsc::channel::<NetworkEvent>(500);
|
||||||
|
let app_arc = Arc::new(Mutex::new(App::new(
|
||||||
|
tx,
|
||||||
|
AppConfig::default(),
|
||||||
|
cancellation_token.clone(),
|
||||||
|
)));
|
||||||
|
app_arc.lock().await.should_refresh = false;
|
||||||
|
app_arc.lock().await.is_loading = true;
|
||||||
|
let mut network = Network::new(&app_arc, cancellation_token, Client::new());
|
||||||
|
network.cancellation_token.cancel();
|
||||||
|
|
||||||
|
network.reset_cancellation_token().await;
|
||||||
|
|
||||||
|
assert!(!network.cancellation_token.is_cancelled());
|
||||||
|
assert!(app_arc.lock().await.should_refresh);
|
||||||
|
assert!(!app_arc.lock().await.is_loading);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_handle_request_get_invalid_body() {
|
async fn test_handle_request_get_invalid_body() {
|
||||||
let mut server = Server::new_async().await;
|
let mut server = Server::new_async().await;
|
||||||
|
|||||||
@@ -286,83 +286,88 @@ fn draw_movie_history(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn draw_movie_cast(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
fn draw_movie_cast(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||||
let is_loading = app.is_loading && app.data.radarr_data.movie_details_modal.is_none();
|
match app.data.radarr_data.movie_details_modal.as_mut() {
|
||||||
let cast_row_mapping = |cast_member: &Credit| {
|
Some(movie_details_modal) if !app.is_loading => {
|
||||||
let Credit {
|
let cast_row_mapping = |cast_member: &Credit| {
|
||||||
person_name,
|
let Credit {
|
||||||
character,
|
person_name,
|
||||||
..
|
character,
|
||||||
} = cast_member;
|
..
|
||||||
|
} = cast_member;
|
||||||
|
|
||||||
Row::new(vec![
|
Row::new(vec![
|
||||||
Cell::from(person_name.to_owned()),
|
Cell::from(person_name.to_owned()),
|
||||||
Cell::from(character.clone().unwrap_or_default()),
|
Cell::from(character.clone().unwrap_or_default()),
|
||||||
])
|
])
|
||||||
.success()
|
.success()
|
||||||
};
|
};
|
||||||
let content = Some(
|
let content = Some(&mut movie_details_modal.movie_cast);
|
||||||
&mut app
|
let help_footer = app
|
||||||
.data
|
.data
|
||||||
.radarr_data
|
.radarr_data
|
||||||
.movie_details_modal
|
.movie_info_tabs
|
||||||
.as_mut()
|
.get_active_tab_contextual_help();
|
||||||
.unwrap()
|
let cast_table = ManagarrTable::new(content, cast_row_mapping)
|
||||||
.movie_cast,
|
.block(layout_block_top_border())
|
||||||
);
|
.footer(help_footer)
|
||||||
let help_footer = app
|
.loading(app.is_loading)
|
||||||
.data
|
.headers(["Cast Member", "Character"])
|
||||||
.radarr_data
|
.constraints([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]);
|
||||||
.movie_info_tabs
|
|
||||||
.get_active_tab_contextual_help();
|
|
||||||
let cast_table = ManagarrTable::new(content, cast_row_mapping)
|
|
||||||
.block(layout_block_top_border())
|
|
||||||
.footer(help_footer)
|
|
||||||
.loading(is_loading)
|
|
||||||
.headers(["Cast Member", "Character"])
|
|
||||||
.constraints([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]);
|
|
||||||
|
|
||||||
f.render_widget(cast_table, area);
|
f.render_widget(cast_table, area);
|
||||||
|
}
|
||||||
|
_ => f.render_widget(
|
||||||
|
LoadingBlock::new(
|
||||||
|
app.is_loading || app.data.radarr_data.movie_details_modal.is_none(),
|
||||||
|
layout_block_top_border(),
|
||||||
|
),
|
||||||
|
area,
|
||||||
|
),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_movie_crew(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
fn draw_movie_crew(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||||
let is_loading = app.is_loading && app.data.radarr_data.movie_details_modal.is_none();
|
match app.data.radarr_data.movie_details_modal.as_mut() {
|
||||||
let crew_row_mapping = |crew_member: &Credit| {
|
Some(movie_details_modal) if !app.is_loading => {
|
||||||
let Credit {
|
let crew_row_mapping = |crew_member: &Credit| {
|
||||||
person_name,
|
let Credit {
|
||||||
job,
|
person_name,
|
||||||
department,
|
job,
|
||||||
..
|
department,
|
||||||
} = crew_member;
|
..
|
||||||
|
} = crew_member;
|
||||||
|
|
||||||
Row::new(vec![
|
Row::new(vec![
|
||||||
Cell::from(person_name.to_owned()),
|
Cell::from(person_name.to_owned()),
|
||||||
Cell::from(job.clone().unwrap_or_default()),
|
Cell::from(job.clone().unwrap_or_default()),
|
||||||
Cell::from(department.clone().unwrap_or_default()),
|
Cell::from(department.clone().unwrap_or_default()),
|
||||||
])
|
])
|
||||||
.success()
|
.success()
|
||||||
};
|
};
|
||||||
let content = Some(
|
let content = Some(&mut movie_details_modal.movie_crew);
|
||||||
&mut app
|
let help_footer = app
|
||||||
.data
|
.data
|
||||||
.radarr_data
|
.radarr_data
|
||||||
.movie_details_modal
|
.movie_info_tabs
|
||||||
.as_mut()
|
.get_active_tab_contextual_help();
|
||||||
.unwrap()
|
let crew_table = ManagarrTable::new(content, crew_row_mapping)
|
||||||
.movie_crew,
|
.block(layout_block_top_border())
|
||||||
);
|
.loading(app.is_loading)
|
||||||
let help_footer = app
|
.headers(["Crew Member", "Job", "Department"])
|
||||||
.data
|
.constraints(iter::repeat(Constraint::Ratio(1, 3)).take(3))
|
||||||
.radarr_data
|
.footer(help_footer);
|
||||||
.movie_info_tabs
|
|
||||||
.get_active_tab_contextual_help();
|
|
||||||
let crew_table = ManagarrTable::new(content, crew_row_mapping)
|
|
||||||
.block(layout_block_top_border())
|
|
||||||
.loading(is_loading)
|
|
||||||
.headers(["Crew Member", "Job", "Department"])
|
|
||||||
.constraints(iter::repeat(Constraint::Ratio(1, 3)).take(3))
|
|
||||||
.footer(help_footer);
|
|
||||||
|
|
||||||
f.render_widget(crew_table, area);
|
f.render_widget(crew_table, area);
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => f.render_widget(
|
||||||
|
LoadingBlock::new(
|
||||||
|
app.is_loading || app.data.radarr_data.movie_details_modal.is_none(),
|
||||||
|
layout_block_top_border(),
|
||||||
|
),
|
||||||
|
area,
|
||||||
|
),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_movie_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
fn draw_movie_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||||
|
|||||||
Reference in New Issue
Block a user