From 8651e933518ae30b2fb71b40d76c68418cdad6a5 Mon Sep 17 00:00:00 2001 From: Nils Andreas Svee Date: Tue, 11 Aug 2026 00:12:37 +0700 Subject: [PATCH] fix(tui): bound input event draining handle_input() previously drained terminal events in an unbounded while-poll loop, so a continuously-ready input stream could keep the frontend inside handle_input() indefinitely, starving managed-event processing, completion checks, peer-close stop policy, and rendering in the outer run_tui() loop. Bound each call to at most INPUT_EVENT_WORK_BUDGET (128) polled terminal events, mirroring the MANAGED_EVENT_WORK_BUDGET pattern already used for managed event draining. All polled events count against the budget, including ignored/unknown/release events, so a stream of non-actionable input can't bypass the bound. Remaining events are left queued for the next outer-loop iteration rather than dropped. Quit/Ctrl-C now also breaks out of the drain immediately after being handled, since there is no benefit to consuming further buffered input before the outer loop can request managed shutdown. --- crates/irtt-cli/src/cmd/tui/run.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/irtt-cli/src/cmd/tui/run.rs b/crates/irtt-cli/src/cmd/tui/run.rs index 3b8cf35..c229fd4 100644 --- a/crates/irtt-cli/src/cmd/tui/run.rs +++ b/crates/irtt-cli/src/cmd/tui/run.rs @@ -26,6 +26,7 @@ use irtt_client::managed::{ const RENDER_INTERVAL: Duration = Duration::from_millis(250); const TUI_WAIT_SLICE: Duration = Duration::from_millis(20); const MANAGED_EVENT_CAPACITY: usize = 16_384; +const INPUT_EVENT_WORK_BUDGET: usize = 128; pub fn run_tui( args: TuiArgs, @@ -216,7 +217,10 @@ fn drain_final_tui_events( } fn handle_input(state: &mut TuiState, shutdown_requested: &AtomicBool) -> io::Result { let mut force_render = false; - while event::poll(Duration::ZERO)? { + for _ in 0..INPUT_EVENT_WORK_BUDGET { + if !event::poll(Duration::ZERO)? { + break; + } let Event::Key(key) = event::read()? else { continue; }; @@ -231,6 +235,7 @@ fn handle_input(state: &mut TuiState, shutdown_requested: &AtomicBool) -> io::Re state.quit_requested = true; shutdown_requested.store(true, Ordering::Relaxed); force_render = true; + break; } KeyCode::Char('r') => { state.clear_visible_history(); -- 2.54.0