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.
This commit is contained in:
Nils Andreas Svee committed 2026-08-11 00:12:37 +07:00
1 parent 17bbf72c6c
commit 8651e93351
1 file changed
+6 -1
+6 -1
View File
@@ -26,6 +26,7 @@ use irtt_client::managed::{
const RENDER_INTERVAL: Duration = Duration::from_millis(250); const RENDER_INTERVAL: Duration = Duration::from_millis(250);
const TUI_WAIT_SLICE: Duration = Duration::from_millis(20); const TUI_WAIT_SLICE: Duration = Duration::from_millis(20);
const MANAGED_EVENT_CAPACITY: usize = 16_384; const MANAGED_EVENT_CAPACITY: usize = 16_384;
const INPUT_EVENT_WORK_BUDGET: usize = 128;
pub fn run_tui( pub fn run_tui(
args: TuiArgs, args: TuiArgs,
@@ -216,7 +217,10 @@ fn drain_final_tui_events(
} }
fn handle_input(state: &mut TuiState, shutdown_requested: &AtomicBool) -> io::Result<bool> { fn handle_input(state: &mut TuiState, shutdown_requested: &AtomicBool) -> io::Result<bool> {
let mut force_render = false; 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 { let Event::Key(key) = event::read()? else {
continue; continue;
}; };
@@ -231,6 +235,7 @@ fn handle_input(state: &mut TuiState, shutdown_requested: &AtomicBool) -> io::Re
state.quit_requested = true; state.quit_requested = true;
shutdown_requested.store(true, Ordering::Relaxed); shutdown_requested.store(true, Ordering::Relaxed);
force_render = true; force_render = true;
break;
} }
KeyCode::Char('r') => { KeyCode::Char('r') => {
state.clear_visible_history(); state.clear_visible_history();