aboutsummaryrefslogtreecommitdiff
path: root/src/server/headless
diff options
context:
space:
mode:
Diffstat (limited to 'src/server/headless')
-rw-r--r--src/server/headless/render.rs10
-rw-r--r--src/server/headless/tests/mod.rs47
2 files changed, 57 insertions, 0 deletions
diff --git a/src/server/headless/render.rs b/src/server/headless/render.rs
index fc55417f..882a1615 100644
--- a/src/server/headless/render.rs
+++ b/src/server/headless/render.rs
@@ -462,6 +462,16 @@ impl HeadlessServer {
let mut broken_clients: Vec<u64> = Vec::new();
for (client_id, (cols, rows), cell_size, _is_foreground, mode) in render_targets {
+ #[cfg(unix)]
+ if matches!(mode, ClientConnectionMode::TerminalObserve { .. })
+ && self
+ .clients
+ .get(&client_id)
+ .is_some_and(|client| client.deferred_render() != DeferredRender::None)
+ {
+ // The writer-drained event schedules recovery, even if pane output stops.
+ continue;
+ }
let area = Rect::new(0, 0, cols, rows);
let shell_target = self.shell_target_for_client(client_id);
let shell_tab_id = self.shell_tab_id_for_client(client_id);
diff --git a/src/server/headless/tests/mod.rs b/src/server/headless/tests/mod.rs
index 2c13dc8b..bb196be5 100644
--- a/src/server/headless/tests/mod.rs
+++ b/src/server/headless/tests/mod.rs
@@ -3509,6 +3509,53 @@ fn terminal_attach_disconnect_restores_client_shell_pane_size() {
rt.shutdown_timeout(Duration::from_millis(100));
}
+#[cfg(unix)]
+#[test]
+fn backpressured_observer_skips_runtime_access_and_recovers_without_new_output() {
+ with_terminal_session_test_server(|server, terminal_id, target, _| {
+ let (writer, control, frames) = test_client_writer();
+ server.handle_server_event(ServerEvent::ClientConnected {
+ client_id: 7,
+ cols: 80,
+ rows: 24,
+ cell_width_px: 0,
+ cell_height_px: 0,
+ pixel_mouse: false,
+ writer,
+ });
+ server.handle_server_event(ServerEvent::ClientObserveTerminal {
+ client_id: 7,
+ target,
+ });
+ server.render_and_stream();
+ server.app.terminal_runtimes.insert(
+ terminal_id.clone(),
+ crate::terminal::TerminalRuntime::test_with_screen_bytes(80, 24, b"LATEST"),
+ );
+ server.render_and_stream();
+ assert_eq!(server.clients[&7].deferred_render(), DeferredRender::Full);
+
+ // An absent runtime makes any attempted rendering observable without timing a lock.
+ let runtime = server.app.terminal_runtimes.remove(&terminal_id).unwrap();
+ server.render_and_stream();
+ server.app.terminal_runtimes.insert(terminal_id, runtime);
+ assert!(
+ server.clients.contains_key(&7),
+ "backpressure must skip runtime access"
+ );
+ assert!(control.try_recv().is_err());
+
+ let _ = frames.recv().expect("previously accepted frame");
+ assert!(server.handle_server_event(ServerEvent::ClientWriterDrained { client_id: 7 }));
+ server.render_and_stream();
+ let ServerMessage::Terminal(frame) = read_server_message(frames.recv().unwrap()) else {
+ panic!("terminal update");
+ };
+ assert!(String::from_utf8_lossy(&frame.bytes).contains("LATEST"));
+ assert_eq!(server.clients[&7].deferred_render(), DeferredRender::None);
+ });
+}
+
#[test]
fn terminal_observe_allows_multiple_clients_without_attach_ownership() {
with_terminal_session_test_server(|server, terminal_id, terminal_id_string, _| {