From e0b0e58e93f2744cdadee7bb058f1eb0ff33b7a4 Mon Sep 17 00:00:00 2001 From: Hansie Odendaal Date: Mon, 20 Jan 2025 16:18:34 +0200 Subject: [PATCH] Investigate crossterm Windows 10 Investigated crossterm failure when running Console Wallet in Windows 10 and Windows Console Host. The backend crossterm process dies with maximize -> reset ``` 2025-01-20 16:20:11.499961300 [wallet::app::crossterm_events] ERROR Internal error in crossterm events, tick rate: 249.9999ms, err: No process is on the other end of the pipe. (os error 233) 2025-01-20 16:20:12.415411300 [wallet::console_wallet::wallet_event_monitor] TRACE Wallet Event Monitor received wallet connectivity event PeerConnectFailed(NodeId(85d605836f02951c65651f99d0)) 2025-01-20 16:20:12.500616800 [wallet::ui::app] TRACE Key event: RestartCrosstermLoop 2025-01-20 16:20:12.500709300 [wallet::ui::app] ERROR Error clearing interface. No process is on the other end of the pipe. (os error 233) ``` --- .../minotari_console_wallet/src/ui/mod.rs | 26 ++++++++--- .../src/utils/crossterm_events.rs | 45 ++++++++++++++----- .../src/utils/events.rs | 2 + .../src/wallet_modes.rs | 42 ++++++++++------- 4 files changed, 81 insertions(+), 34 deletions(-) diff --git a/applications/minotari_console_wallet/src/ui/mod.rs b/applications/minotari_console_wallet/src/ui/mod.rs index a1b343e6d6..ecf1690acb 100644 --- a/applications/minotari_console_wallet/src/ui/mod.rs +++ b/applications/minotari_console_wallet/src/ui/mod.rs @@ -51,7 +51,7 @@ use crate::utils::events::{Event, EventStream}; pub const MAX_WIDTH: u16 = 167; -pub fn run(app: App>) -> Result<(), ExitError> { +pub fn run(app: App>) -> Result { let mut app = app; Handle::current() .block_on(async { @@ -75,7 +75,7 @@ pub fn run(app: App>) -> Result<(), ExitError> { crossterm_loop(app) } /// This is the main loop of the application UI using Crossterm based events -fn crossterm_loop(mut app: App>) -> Result<(), ExitError> { +fn crossterm_loop(mut app: App>) -> Result { let events = CrosstermEvents::new(); enable_raw_mode().map_err(|e| { error!(target: LOG_TARGET, "Error enabling Raw Mode {}", e); @@ -103,16 +103,19 @@ fn crossterm_loop(mut app: App>) -> Result<(), ExitErro ExitCode::InterfaceError })?; + let mut all_ok = true; loop { terminal.draw(|f| app.draw(f)).map_err(|e| { error!(target: LOG_TARGET, "Error drawing interface. {}", e); ExitCode::InterfaceError })?; - #[allow(clippy::blocks_in_conditions)] - match events.next().map_err(|e| { + let key_event = events.next().map_err(|e| { error!(target: LOG_TARGET, "Error reading input event: {}", e); ExitCode::InterfaceError - })? { + })?; + trace!(target: LOG_TARGET, "Key event: {:?}", key_event); + #[allow(clippy::blocks_in_conditions)] + match key_event { Event::Input(event) => match (event.code, event.modifiers) { (KeyCode::Char(c), KeyModifiers::CONTROL) => app.on_control_key(c), (KeyCode::Char(c), _) => app.on_key(c), @@ -131,12 +134,21 @@ fn crossterm_loop(mut app: App>) -> Result<(), ExitErro Event::Tick => { app.on_tick(); }, + Event::RestartCrosstermLoop => { + if let Err(e) = terminal.clear() { + error!(target: LOG_TARGET, "Error clearing interface. {}", e); + } else if let Err(e) = disable_raw_mode() { + error!(target: LOG_TARGET, "Error disabling Raw Mode {}", e); + } + all_ok = false; + return Ok(all_ok); + }, } if app.should_quit { + trace!(target: LOG_TARGET, "CrosstermBackend should quit"); break; } } - terminal.clear().map_err(|e| { error!(target: LOG_TARGET, "Error clearing interface. {}", e); ExitCode::InterfaceError @@ -155,5 +167,5 @@ fn crossterm_loop(mut app: App>) -> Result<(), ExitErro ExitCode::InterfaceError })?; - Ok(()) + Ok(all_ok) } diff --git a/applications/minotari_console_wallet/src/utils/crossterm_events.rs b/applications/minotari_console_wallet/src/utils/crossterm_events.rs index 954a9319f8..f72489d716 100644 --- a/applications/minotari_console_wallet/src/utils/crossterm_events.rs +++ b/applications/minotari_console_wallet/src/utils/crossterm_events.rs @@ -64,16 +64,19 @@ impl CrosstermEvents { let mut last_tick = Instant::now(); loop { // poll for tick rate duration, if no events, sent tick event. - match event::poll( - config - .tick_rate - .checked_sub(last_tick.elapsed()) - .unwrap_or_else(|| Duration::from_millis(1)), - ) { + let effective_tick_rate = config + .tick_rate + .checked_sub(last_tick.elapsed()) + .unwrap_or_else(|| Duration::from_millis(1)); + match event::poll(effective_tick_rate.clone()) { Ok(true) => { if let Ok(CEvent::Key(key)) = event::read() { - if tx.send(Event::Input(key)).is_err() { - info!(target: LOG_TARGET, "Tick event channel shutting down"); + if let Err(e) = tx.send(Event::Input(key)) { + info!( + target: LOG_TARGET, + "Tick event channel shutting down, key: {:?}, tick rate: {:?}, err: {}", + key, effective_tick_rate, e + ); // A send operation can only fail if the receiving end of a channel is disconnected. break; } @@ -81,12 +84,32 @@ impl CrosstermEvents { }, Ok(false) => {}, Err(e) => { - error!(target: LOG_TARGET, "Internal error in crossterm events: {}", e); + error!( + target: LOG_TARGET, + "Internal error in crossterm events, tick rate: {:?}, err: {}", + effective_tick_rate, e + ); + // // Wait a bit before trying again + thread::sleep(Duration::from_millis(1000)); + if e.to_string().contains("No process is on the other end of the pipe") { + if let Err(e) = tx.send(Event::RestartCrosstermLoop) { + info!( + target: LOG_TARGET, + "Tick event channel error 'Event::RestartCrosstermLoop', tick rate: {:?}, err: {})", + effective_tick_rate, e + ); + } + break; + } }, } if last_tick.elapsed() >= config.tick_rate { - if tx.send(Event::Tick).is_err() { - info!(target: LOG_TARGET, "Tick event channel shutting down"); + if let Err(e) = tx.send(Event::Tick) { + info!( + target: LOG_TARGET, + "Tick event channel shutting down 'Event::Tick', tick rate: {:?}, err: {})", + effective_tick_rate, e + ); // A send operation can only fail if the receiving end of a channel is disconnected. break; } diff --git a/applications/minotari_console_wallet/src/utils/events.rs b/applications/minotari_console_wallet/src/utils/events.rs index 16968e855f..85ed4feaec 100644 --- a/applications/minotari_console_wallet/src/utils/events.rs +++ b/applications/minotari_console_wallet/src/utils/events.rs @@ -22,9 +22,11 @@ use std::sync::mpsc; +#[derive(Debug)] pub enum Event { Input(I), Tick, + RestartCrosstermLoop, } pub trait EventStream { diff --git a/applications/minotari_console_wallet/src/wallet_modes.rs b/applications/minotari_console_wallet/src/wallet_modes.rs index 79ca2df5dc..46b2a7d6e7 100644 --- a/applications/minotari_console_wallet/src/wallet_modes.rs +++ b/applications/minotari_console_wallet/src/wallet_modes.rs @@ -22,7 +22,11 @@ #![allow(dead_code, unused)] -use std::{fs, io::Stdout, path::PathBuf}; +use std::{ + fs, + io::{stdout, Stdout}, + path::PathBuf, +}; use clap::Parser; use log::*; @@ -374,24 +378,30 @@ pub fn tui_mode( return Err(ExitError::new(ExitCode::WalletError, "Could not select a base node")); } - let app = handle.block_on(App::>::new( - "Minotari Wallet".into(), - wallet, - config.clone(), - base_node_selected, - base_node_config.clone(), - notifier, - ))?; + loop { + info!(target: LOG_TARGET, "Starting app"); - info!(target: LOG_TARGET, "Starting app"); + let app = handle.block_on(App::>::new( + "Minotari Wallet".into(), + wallet.clone(), + config.clone(), + base_node_selected.clone(), + base_node_config.clone(), + notifier.clone(), + ))?; - // Do not remove this println! - const CUCUMBER_TEST_MARKER: &str = "Minotari Console Wallet running... (TUI mode started)"; - println!("{}", CUCUMBER_TEST_MARKER); + // Do not remove this println! + const CUCUMBER_TEST_MARKER: &str = "Minotari Console Wallet running... (TUI mode started)"; + println!("{}", CUCUMBER_TEST_MARKER); - { - let _enter = handle.enter(); - ui::run(app)?; + { + let _enter = handle.enter(); + if ui::run(app)? { + break; + } else { + trace!(target: LOG_TARGET, "Re-starting app required"); + } + } } info!(