mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
fix(terminal): recover attach publication race
Co-authored-by: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@buzz.block.builderlab.xyz> Signed-off-by: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@buzz.block.builderlab.xyz>
This commit is contained in:
parent
ba4ff04fea
commit
4bd3b03a3c
@@ -5,14 +5,14 @@ use std::sync::{Arc, Mutex};
|
||||
use std::thread::JoinHandle;
|
||||
|
||||
use buzz_terminal::context::{context_vars, GuiContext};
|
||||
use buzz_terminal::damage::Style;
|
||||
use buzz_terminal::damage::{Frame, Style};
|
||||
use buzz_terminal::{Fences, SharedTerminal, Size, Terminal, Viewport};
|
||||
use portable_pty::{native_pty_system, CommandBuilder, MasterPty, PtySize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tauri::ipc::Channel;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::terminal_transport::{FramePublisher, Publication, SubscriptionId};
|
||||
use crate::terminal_transport::{FramePublisher, OfferError, Publication, SubscriptionId};
|
||||
|
||||
const MAX_LIVE_SESSIONS: usize = 20;
|
||||
const MAX_INPUT_BYTES: usize = 1024 * 1024;
|
||||
@@ -316,6 +316,20 @@ fn pty_size(columns: u16, rows: u16, pixel_width: u16, pixel_height: u16) -> Pty
|
||||
}
|
||||
}
|
||||
|
||||
fn offer_capture(
|
||||
publisher: &Mutex<FramePublisher>,
|
||||
frame: Frame,
|
||||
snapshot: impl FnOnce() -> Frame,
|
||||
) -> Option<Publication> {
|
||||
let offered = publisher.lock().ok()?.offer(frame);
|
||||
match offered {
|
||||
Ok(publication) => publication,
|
||||
Err(OfferError::PendingFrameMustBeSnapshot) => {
|
||||
publisher.lock().ok()?.offer(snapshot()).ok().flatten()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) fn terminal_attach(
|
||||
request: AttachRequest,
|
||||
@@ -473,10 +487,9 @@ pub(crate) fn terminal_attach(
|
||||
if frame.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let publication = reader_publisher
|
||||
.lock()
|
||||
.ok()
|
||||
.and_then(|mut publisher| publisher.offer(frame).ok().flatten());
|
||||
let publication = offer_capture(&reader_publisher, frame, || {
|
||||
reader_terminal.snapshot(&mut snapshot_encoder)
|
||||
});
|
||||
if let Some(publication) = publication {
|
||||
let subscription = publication.subscription_id;
|
||||
let result = wire_publication(publication).and_then(|mut message| {
|
||||
@@ -720,6 +733,67 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn marker_frame(marker: usize, full: bool) -> Frame {
|
||||
Frame {
|
||||
rows: vec![RowFrame {
|
||||
line: marker,
|
||||
spans: Vec::new(),
|
||||
}],
|
||||
cursor: CursorFrame {
|
||||
line: 0,
|
||||
column: 0,
|
||||
visible: true,
|
||||
},
|
||||
full,
|
||||
viewport: Viewport {
|
||||
generation: 0,
|
||||
columns: 80,
|
||||
screen_lines: 24,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_post_snapshot_capture_survives_attach(mut publisher: FramePublisher) {
|
||||
assert!(!publisher.requires_snapshot());
|
||||
let bootstrap = marker_frame(1, true);
|
||||
let post_snapshot_incremental = marker_frame(42, false);
|
||||
let subscription = SubscriptionId::new();
|
||||
publisher.attach(subscription, bootstrap).unwrap();
|
||||
let publisher = Mutex::new(publisher);
|
||||
|
||||
assert_eq!(
|
||||
offer_capture(&publisher, post_snapshot_incremental, || marker_frame(
|
||||
42, true
|
||||
)),
|
||||
None
|
||||
);
|
||||
|
||||
let successor = publisher
|
||||
.lock()
|
||||
.unwrap()
|
||||
.acknowledge(subscription, 1)
|
||||
.expect("post-snapshot PTY output was lost");
|
||||
assert_eq!(successor.frame.rows[0].line, 42);
|
||||
assert!(successor.frame.full);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn initial_attach_retains_output_captured_after_its_bootstrap_snapshot() {
|
||||
let viewport = marker_frame(0, true).viewport;
|
||||
let publisher = FramePublisher::new(viewport);
|
||||
assert_post_snapshot_capture_survives_attach(publisher);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reattach_retains_output_captured_after_its_bootstrap_snapshot() {
|
||||
let viewport = marker_frame(0, true).viewport;
|
||||
let mut publisher = FramePublisher::new(viewport);
|
||||
let old = SubscriptionId::new();
|
||||
publisher.attach(old, marker_frame(0, true)).unwrap();
|
||||
assert!(publisher.acknowledge(old, 1).is_none());
|
||||
assert_post_snapshot_capture_survives_attach(publisher);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mapper_expands_ascii_runs_without_unicode_classification() {
|
||||
let message = wire_publication(publication(vec![Span {
|
||||
|
||||
Reference in New Issue
Block a user