Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/apps/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ pub const PTY_DEFAULT_ROWS: u16 = 24;
/// Height of the footer row in the full-screen output view.
pub const OUTPUT_FOOTER_HEIGHT: u16 = 1;

/// Stream channel capacity (1000 messages).
pub const STREAM_CHANNEL_SIZE: usize = 1000;
/// Capacity of the raw PTY stream channel (`0` for rendezvous backpressure).
pub const STREAM_CHANNEL_SIZE: usize = 0;

// Preview layout constants

Expand Down
20 changes: 11 additions & 9 deletions src/apps/exec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@
//! Owns the per-code-block output state and provides shared functions
//! for running code, processing PTY output streams, and reloading documents.

use std::path::PathBuf;
use std::{collections::HashMap, path::PathBuf};

use crossbeam_channel::Receiver;

use crate::apps::config::Envs;
use crate::apps::task::Task;
use crate::{pty::process::Size as PtySize, pty::stream::Stream, runner};
use std::collections::HashMap;
use upmd_parser::{nodes, CodeId};
use upmd_runtime::Cmd;

Expand Down Expand Up @@ -104,19 +103,22 @@ pub fn merge_envs(dest: &mut Envs, captured: &Envs) {

/// Creates a stream command that forwards process output and control separately.
///
/// PTY output can be effectively infinite (`yes` is the canonical case), so
/// `Out` is best-effort on the low-priority queue. Lifecycle/state messages go
/// to the high-priority queue so `Exit`/`End` cannot sit behind stale output.
/// PTY output uses the bounded low-priority queue, which applies backpressure
/// instead of dropping chunks. Lifecycle/state messages use the high-priority
/// queue so the runtime handles them before already-queued output.
pub fn stream_rx<M: Send + 'static>(
id: CodeId,
rx: Receiver<Stream>,
mk_msg: impl Fn(CodeId, Stream) -> M + Send + 'static,
) -> Cmd<M> {
Cmd::priority_stream(move |output_tx, control_tx| {
while let Ok(msg) = rx.recv() {
if matches!(msg, Stream::Out(_)) {
let _ = output_tx.try_send(mk_msg(id, msg));
} else if control_tx.send(mk_msg(id, msg)).is_err() {
while let Ok(stream) = rx.recv() {
let sender = if matches!(stream, Stream::Out(_)) {
&output_tx
} else {
&control_tx
};
if sender.send(mk_msg(id, stream)).is_err() {
break;
}
}
Expand Down
17 changes: 15 additions & 2 deletions tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,21 @@ use std::process::Command;

#[cfg(unix)]
#[test]
fn cli_block_yes_prints_pty_output_before_exit() {
fn cli_block_prints_pty_output_tail_before_exit() {
let tmp = tempfile::tempdir().expect("create temp dir");
std::fs::write(tmp.path().join("listed-file.txt"), "").expect("create listed file");

let markdown = tmp.path().join("case.md");
std::fs::write(&markdown, "```shell\nls\n```\n").expect("write markdown");
std::fs::write(
&markdown,
r#"```shell
for i in $(seq 1 5000); do printf 'line-%04d\n' "$i"; done
ls
printf 'FINAL-PTY-TAIL\n'
```
"#,
)
.expect("write markdown");

let output = Command::new(env!("CARGO_BIN_EXE_upmd"))
.arg(&markdown)
Expand All @@ -33,6 +42,10 @@ fn cli_block_yes_prints_pty_output_before_exit() {
stdout.contains("listed-file.txt"),
"CLI output should include PTY stdout\nstdout:\n{stdout}\nstderr:\n{stderr}"
);
assert!(
stdout.contains("FINAL-PTY-TAIL"),
"CLI output should include the final PTY output\nstdout:\n{stdout}\nstderr:\n{stderr}"
);
}

#[cfg(unix)]
Expand Down
Loading