From 5f416f7d25b079fe264c4a473a9bb13619237d27 Mon Sep 17 00:00:00 2001 From: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@buzz.block.builderlab.xyz> Date: Sun, 2 Aug 2026 01:53:12 -0400 Subject: [PATCH] test(terminal): require the runtime to pump deferred work Co-authored-by: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@buzz.block.builderlab.xyz> Signed-off-by: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@buzz.block.builderlab.xyz> --- .../crates/buzz-terminal/src/fences.rs | 4 +-- .../src-tauri/crates/buzz-terminal/src/lib.rs | 5 ++++ .../crates/buzz-terminal/src/reader.rs | 5 ++-- desktop/src-tauri/src/terminal_runtime.rs | 30 ++++++++++++++++--- 4 files changed, 36 insertions(+), 8 deletions(-) diff --git a/desktop/src-tauri/crates/buzz-terminal/src/fences.rs b/desktop/src-tauri/crates/buzz-terminal/src/fences.rs index ab5d3234d2..2797fa401f 100644 --- a/desktop/src-tauri/crates/buzz-terminal/src/fences.rs +++ b/desktop/src-tauri/crates/buzz-terminal/src/fences.rs @@ -130,8 +130,8 @@ pub fn max_atom_work(columns: usize, lines: usize, scrollback: usize) -> u64 { /// fixtures caught it: /// /// * The budget is checked *between* slices, so a drain overshoots by up to -/// one whole slice -- not one atom. [`slice_bytes`] keeps that under one -/// budget wherever its derivation is unclamped. +/// one whole slice -- not one atom. [`slice_bytes_remaining`] keeps that +/// under one budget wherever its derivation is unclamped. /// * A callback already running cannot be preempted. RIS at the default 10k /// scrollback is worth 16x the whole budget on its own, so on such a grid /// the floor binds and the overshoot is a few of those atoms. No scheduler diff --git a/desktop/src-tauri/crates/buzz-terminal/src/lib.rs b/desktop/src-tauri/crates/buzz-terminal/src/lib.rs index ff87968703..72edac1af6 100644 --- a/desktop/src-tauri/crates/buzz-terminal/src/lib.rs +++ b/desktop/src-tauri/crates/buzz-terminal/src/lib.rs @@ -161,6 +161,11 @@ impl Terminal { } /// Whether the tail is at its cap and the reader must stop reading. + /// + /// There is no production consumer today, deliberately: the desktop + /// runtime pumps `drain()` to completion after every read, so the tail is + /// empty between iterations. A future reader that defers pumping must + /// consult this signal before accepting more PTY bytes. pub fn tail_full(&self) -> bool { self.feeder.tail_full() } diff --git a/desktop/src-tauri/crates/buzz-terminal/src/reader.rs b/desktop/src-tauri/crates/buzz-terminal/src/reader.rs index 82324f4601..0a09bc160f 100644 --- a/desktop/src-tauri/crates/buzz-terminal/src/reader.rs +++ b/desktop/src-tauri/crates/buzz-terminal/src/reader.rs @@ -172,8 +172,9 @@ impl Feeder { /// per count when it finds no stop. /// /// What *is* bounded is the number of atoms per slice, and that bound - /// holds from the first byte of a cold feeder: [`slice_bytes`] is derived - /// from the densest work-per-byte upstream can produce on this grid, so + /// holds from the first byte of a cold feeder: + /// [`slice_bytes_remaining`] is derived from the densest work-per-byte + /// upstream can produce on this grid, so /// no slice can contain more than one budget's worth of callbacks no /// matter what the payload is or what the feeder has seen before. /// diff --git a/desktop/src-tauri/src/terminal_runtime.rs b/desktop/src-tauri/src/terminal_runtime.rs index 30d8df6d1e..9be896e9bc 100644 --- a/desktop/src-tauri/src/terminal_runtime.rs +++ b/desktop/src-tauri/src/terminal_runtime.rs @@ -203,6 +203,15 @@ fn wire_publication(publication: Publication) -> Result { }) } +fn feed_and_drain(terminal: &SharedTerminal, bytes: &[u8]) -> bool { + let mut more = terminal.feed(bytes); + let deferred = more; + while more && !terminal.is_closing() { + more = terminal.drain(); + } + deferred +} + struct ReaderThread { handle: Option>, terminal: Arc, @@ -502,10 +511,7 @@ pub(crate) fn terminal_attach( if reader_terminal.is_closing() { continue; } - let mut more = reader_terminal.feed(&buffer[..count]); - while more && !reader_terminal.is_closing() { - more = reader_terminal.drain(); - } + let _ = feed_and_drain(&reader_terminal, &buffer[..count]); if reader_terminal.is_closing() { continue; } @@ -815,6 +821,22 @@ mod tests { assert!(successor.frame.full); } + #[test] + fn reader_pumps_a_deferred_tail_without_an_external_event() { + let (terminal, _actions) = Terminal::new(Size::default(), Fences::ALL); + let terminal = SharedTerminal::new(terminal); + let payload = "\u{1b}c".repeat(2_102_714); + + assert!( + feed_and_drain(&terminal, payload.as_bytes()), + "fixture must defer parser work before the runtime pumps it" + ); + + let terminal = terminal.lock(); + assert_eq!(terminal.pending_bytes(), 0); + assert_eq!(terminal.stats().completed_units, 2_102_714); + } + #[test] fn initial_attach_retains_output_captured_after_its_bootstrap_snapshot() { let viewport = marker_frame(0, true).viewport;