Skip to main content

rich_embed/
host.rs

1//! What runs behind a terminal pane: [`PtyHost`], and its two
2//! implementations here, [`LocalPty`] (a program on this machine) and
3//! [`ReplayHost`] (bytes played back, for tests and demos).
4//!
5//! A host starts the program at a size, carries bytes both ways and the
6//! size when it changes, and says when the program has exited. Output is
7//! read from the host's own thread into a buffer, and the host calls the
8//! [`Notify`] it was given whenever there is something new: the pane then
9//! reads it on the app's thread, between frames. An SSH session, a
10//! container's exec stream or a remote shell is another implementation of
11//! the same trait, passed to [`terminal_with`](crate::terminal_with).
12
13use std::collections::VecDeque;
14use std::ffi::OsString;
15use std::fmt;
16use std::io::{self, Read, Write};
17use std::path::PathBuf;
18use std::sync::atomic::{AtomicUsize, Ordering};
19use std::sync::{Arc, Mutex, MutexGuard, PoisonError, mpsc};
20use std::thread;
21use std::time::{Duration, Instant};
22
23use portable_pty::{ChildKiller, CommandBuilder, MasterPty, PtySize, native_pty_system};
24
25/// Called by a host, from any thread, when output arrives or the program
26/// exits. It only schedules work: the pane reads the host on the app's
27/// thread.
28pub type Notify = Arc<dyn Fn() + Send + Sync>;
29
30/// How a program ended.
31#[derive(Clone, Debug, PartialEq, Eq, Hash)]
32pub struct ExitStatus {
33    code: u32,
34    signal: Option<String>,
35}
36
37impl ExitStatus {
38    /// It exited with `code`.
39    pub fn with_code(code: u32) -> ExitStatus {
40        ExitStatus { code, signal: None }
41    }
42
43    /// It was ended by `signal` (its name, such as `"Terminated"`).
44    pub fn with_signal(signal: impl Into<String>) -> ExitStatus {
45        ExitStatus {
46            code: 1,
47            signal: Some(signal.into()),
48        }
49    }
50
51    /// The exit code (1 when a signal ended it).
52    pub fn code(&self) -> u32 {
53        self.code
54    }
55
56    /// The signal that ended it, if one did.
57    pub fn signal(&self) -> Option<&str> {
58        self.signal.as_deref()
59    }
60
61    /// Whether it exited with code 0.
62    pub fn success(&self) -> bool {
63        self.code == 0 && self.signal.is_none()
64    }
65}
66
67impl fmt::Display for ExitStatus {
68    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69        match &self.signal {
70            Some(signal) => write!(f, "ended by signal {signal}"),
71            None => write!(f, "exited with code {}", self.code),
72        }
73    }
74}
75
76impl From<portable_pty::ExitStatus> for ExitStatus {
77    fn from(status: portable_pty::ExitStatus) -> ExitStatus {
78        match status.signal() {
79            Some(signal) => ExitStatus::with_signal(signal),
80            None => ExitStatus::with_code(status.exit_code()),
81        }
82    }
83}
84
85/// Starts a program and carries its bytes and its size: what a terminal
86/// pane talks to. Every method is called on the app's thread.
87pub trait PtyHost {
88    /// Start the program on a screen `columns` x `rows` cells. Called once,
89    /// when the pane is first laid out.
90    fn start(&mut self, columns: u16, rows: u16) -> io::Result<()>;
91
92    /// Send `bytes` to the program, as if typed. A host may refuse with
93    /// [`io::ErrorKind::WouldBlock`] while the program's input is full
94    /// (it is not reading it): nothing was sent, and the same bytes can be
95    /// sent again later.
96    fn write(&mut self, bytes: &[u8]) -> io::Result<()>;
97
98    /// The pane is now `columns` x `rows` cells.
99    fn resize(&mut self, columns: u16, rows: u16) -> io::Result<()>;
100
101    /// The output that arrived since the last call; empty when there is
102    /// none. Never blocks.
103    fn read(&mut self) -> Vec<u8>;
104
105    /// Call `notify` whenever output arrives or the program exits. Given
106    /// once, before or after [`start`](Self::start); a host with something
107    /// already waiting calls it at once.
108    fn set_notify(&mut self, notify: Notify);
109
110    /// How the program ended, once it has, and once the output it wrote
111    /// before that has been [read](Self::read).
112    fn exit_status(&mut self) -> Option<ExitStatus>;
113
114    /// End the program. Also called when the pane goes away.
115    fn kill(&mut self) -> io::Result<()>;
116}
117
118/// A program and its arguments, environment and directory, for
119/// [`LocalPty`]. A string is a program name; an array or a vector is the
120/// program and its arguments.
121#[derive(Clone, Debug, PartialEq, Eq)]
122pub struct Command {
123    program: OsString,
124    args: Vec<OsString>,
125    env: Vec<(OsString, OsString)>,
126    cwd: Option<PathBuf>,
127}
128
129impl Command {
130    pub fn new(program: impl Into<OsString>) -> Command {
131        Command {
132            program: program.into(),
133            args: Vec::new(),
134            env: Vec::new(),
135            cwd: None,
136        }
137    }
138
139    #[must_use]
140    pub fn arg(mut self, arg: impl Into<OsString>) -> Command {
141        self.args.push(arg.into());
142        self
143    }
144
145    #[must_use]
146    pub fn args<I: IntoIterator<Item = S>, S: Into<OsString>>(mut self, args: I) -> Command {
147        self.args.extend(args.into_iter().map(Into::into));
148        self
149    }
150
151    /// Set `key` in the program's environment (it inherits the app's,
152    /// with `TERM=xterm-256color`).
153    #[must_use]
154    pub fn env(mut self, key: impl Into<OsString>, value: impl Into<OsString>) -> Command {
155        self.env.push((key.into(), value.into()));
156        self
157    }
158
159    /// Run in `dir` (default: the app's current directory).
160    #[must_use]
161    pub fn cwd(mut self, dir: impl Into<PathBuf>) -> Command {
162        self.cwd = Some(dir.into());
163        self
164    }
165
166    /// The program's name.
167    pub fn program(&self) -> &OsString {
168        &self.program
169    }
170
171    fn builder(&self) -> CommandBuilder {
172        let mut builder = CommandBuilder::new(&self.program);
173        builder.args(&self.args);
174        builder.env("TERM", "xterm-256color");
175        for (key, value) in &self.env {
176            builder.env(key, value);
177        }
178        match &self.cwd {
179            Some(dir) => builder.cwd(dir),
180            // portable-pty would start in the home directory.
181            None => {
182                if let Ok(dir) = std::env::current_dir() {
183                    builder.cwd(dir);
184                }
185            }
186        }
187        builder
188    }
189}
190
191impl From<&str> for Command {
192    fn from(program: &str) -> Command {
193        Command::new(program)
194    }
195}
196
197impl From<String> for Command {
198    fn from(program: String) -> Command {
199        Command::new(program)
200    }
201}
202
203impl<S: Into<OsString>, const N: usize> From<[S; N]> for Command {
204    fn from(parts: [S; N]) -> Command {
205        Command::from_parts(parts)
206    }
207}
208
209impl<S: Into<OsString>> From<Vec<S>> for Command {
210    fn from(parts: Vec<S>) -> Command {
211        Command::from_parts(parts)
212    }
213}
214
215impl Command {
216    fn from_parts<S: Into<OsString>>(parts: impl IntoIterator<Item = S>) -> Command {
217        let mut parts = parts.into_iter().map(Into::into);
218        let program = parts.next().unwrap_or_default();
219        Command::new(program).args(parts)
220    }
221}
222
223/// Output kept for the pane at most, in bytes: past it the oldest goes,
224/// so a program that floods a pane the app is not reading costs a bounded
225/// amount of memory.
226const MAX_BUFFERED: usize = 16 * 1024 * 1024;
227/// Input held for a program that has not read it yet, in bytes: past it,
228/// [`LocalPty::write`](PtyHost::write) refuses more.
229const MAX_INPUT: usize = 1024 * 1024;
230/// How long a program has to end once hung up on before it is killed.
231const KILL_GRACE: Duration = Duration::from_secs(1);
232/// How often the waiter looks whether the program has ended.
233const WAIT_TURN: Duration = Duration::from_millis(20);
234
235#[derive(Default)]
236struct Output {
237    buffer: VecDeque<u8>,
238    /// The reader reached the end of the output.
239    eof: bool,
240    /// The reader is waiting for room ([`LocalPty::backpressure`]).
241    paused: bool,
242    /// The host is gone: a paused reader stops.
243    closed: bool,
244    /// When the program was hung up on ([`LocalPty::kill`]).
245    hung_up: Option<Instant>,
246    status: Option<ExitStatus>,
247    notify: Option<Notify>,
248}
249
250fn lock<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
251    mutex.lock().unwrap_or_else(PoisonError::into_inner)
252}
253
254/// Notify outside the lock.
255fn notify(shared: &Mutex<Output>) {
256    let notify = lock(shared).notify.clone();
257    if let Some(notify) = notify {
258        notify();
259    }
260}
261
262/// A program on this machine, on a PTY (ConPTY on Windows), through
263/// portable-pty as rs-rich-record's tapes run theirs.
264pub struct LocalPty {
265    command: Command,
266    shared: Arc<Mutex<Output>>,
267    master: Option<Box<dyn MasterPty + Send>>,
268    /// To the writer thread: a program that stops reading its input fills
269    /// the PTY, and a write would then block the app.
270    writer: Option<mpsc::Sender<Vec<u8>>>,
271    /// Bytes sent to the writer thread and not yet written to the PTY.
272    queued: Arc<AtomicUsize>,
273    killer: Option<Box<dyn ChildKiller + Send + Sync>>,
274    /// Hold at most this much unread output, then stop reading.
275    backpressure: Option<usize>,
276}
277
278impl LocalPty {
279    pub fn new(command: impl Into<Command>) -> LocalPty {
280        LocalPty {
281            command: command.into(),
282            shared: Arc::default(),
283            master: None,
284            writer: None,
285            queued: Arc::default(),
286            killer: None,
287            backpressure: None,
288        }
289    }
290
291    /// Hold at most about `bytes` of output that has not been
292    /// [read](PtyHost::read) (one read of the PTY more, at most 64 KiB);
293    /// past it, stop reading the program's output until some is read, so
294    /// its writes wait, as they do on a slow terminal. Nothing is dropped,
295    /// at any limit (16 MiB's or more included): memory is bounded by the
296    /// limit itself. Without this, output is never waited for: past 16 MiB
297    /// unread, the oldest is dropped. For
298    /// a reader that may fall behind and must not lose anything, such as a
299    /// browser at the end of a network (rs-rich-web).
300    #[must_use]
301    pub fn backpressure(mut self, bytes: usize) -> LocalPty {
302        self.backpressure = Some(bytes.max(1));
303        self
304    }
305
306    fn size(columns: u16, rows: u16) -> PtySize {
307        PtySize {
308            rows: rows.max(1),
309            cols: columns.max(1),
310            pixel_width: 0,
311            pixel_height: 0,
312        }
313    }
314}
315
316impl PtyHost for LocalPty {
317    fn start(&mut self, columns: u16, rows: u16) -> io::Result<()> {
318        if self.master.is_some() {
319            return Ok(());
320        }
321        let pty = native_pty_system()
322            .openpty(LocalPty::size(columns, rows))
323            .map_err(io::Error::other)?;
324        let mut child = pty
325            .slave
326            .spawn_command(self.command.builder())
327            .map_err(io::Error::other)?;
328        // The child holds the only other end: its exit ends the output.
329        drop(pty.slave);
330        let mut reader = pty.master.try_clone_reader().map_err(io::Error::other)?;
331        let mut writer = pty.master.take_writer().map_err(io::Error::other)?;
332        let (input, keys) = mpsc::channel::<Vec<u8>>();
333        let queued = Arc::clone(&self.queued);
334        thread::spawn(move || {
335            for bytes in keys {
336                if writer
337                    .write_all(&bytes)
338                    .and_then(|()| writer.flush())
339                    .is_err()
340                {
341                    // Writes now fail as the program having gone.
342                    queued.store(0, Ordering::SeqCst);
343                    break;
344                }
345                queued.fetch_sub(bytes.len(), Ordering::SeqCst);
346            }
347        });
348        self.writer = Some(input);
349        self.killer = Some(child.clone_killer());
350        self.master = Some(pty.master);
351
352        let shared = Arc::clone(&self.shared);
353        let limit = self.backpressure;
354        thread::spawn(move || {
355            let mut chunk = [0u8; 65536];
356            loop {
357                if let Some(limit) = limit
358                    && !wait_for_room(&shared, limit)
359                {
360                    break;
361                }
362                let read = match reader.read(&mut chunk) {
363                    Ok(0) | Err(_) => break,
364                    Ok(read) => read,
365                };
366                keep(&mut lock(&shared), &chunk[..read], limit.is_none());
367                notify(&shared);
368            }
369            lock(&shared).eof = true;
370            notify(&shared);
371        });
372
373        let shared = Arc::clone(&self.shared);
374        let leader = child.process_id();
375        thread::spawn(move || {
376            // Polled rather than waited for, so that a program still
377            // running a moment after it was hung up on (one that ignores
378            // SIGHUP) is killed: the waiter never waits for ever.
379            let mut killed = false;
380            let status = loop {
381                match child.try_wait() {
382                    Ok(Some(status)) => break ExitStatus::from(status),
383                    Ok(None) => {}
384                    Err(error) => break ExitStatus::with_signal(error.to_string()),
385                }
386                let hung_up = lock(&shared).hung_up;
387                if !killed && hung_up.is_some_and(|at| at.elapsed() >= KILL_GRACE) {
388                    if let Some(leader) = leader {
389                        kill_group(leader);
390                    }
391                    killed = true;
392                }
393                thread::sleep(WAIT_TURN);
394            };
395            // What it wrote before exiting is still on its way through the
396            // PTY: give the reader a moment to reach the end of it, so the
397            // exit is reported after the last output. A reader paused for
398            // room is not reading: its moment waits for it.
399            let mut waited = 0;
400            while waited < 50 {
401                let (eof, paused) = {
402                    let output = lock(&shared);
403                    (output.eof, output.paused)
404                };
405                if eof {
406                    break;
407                }
408                if !paused {
409                    waited += 1;
410                }
411                thread::sleep(Duration::from_millis(10));
412            }
413            lock(&shared).status = Some(status);
414            notify(&shared);
415        });
416        Ok(())
417    }
418
419    /// Refused with [`io::ErrorKind::WouldBlock`] while 1 MiB of input
420    /// the program has not read is held (one write more at most), so a
421    /// flood of pastes to a program that is not reading costs a bounded
422    /// amount of memory. A pane drops what is refused.
423    fn write(&mut self, bytes: &[u8]) -> io::Result<()> {
424        if self.writer.is_some() && self.queued.load(Ordering::SeqCst) >= MAX_INPUT {
425            return Err(io::Error::new(
426                io::ErrorKind::WouldBlock,
427                "the program is not reading its input",
428            ));
429        }
430        match &self.writer {
431            Some(writer) => {
432                self.queued.fetch_add(bytes.len(), Ordering::SeqCst);
433                writer.send(bytes.to_vec()).map_err(|_| {
434                    self.queued.store(0, Ordering::SeqCst);
435                    io::Error::new(io::ErrorKind::BrokenPipe, "the program has gone")
436                })
437            }
438            None => Err(io::Error::new(
439                io::ErrorKind::NotConnected,
440                "the program has not started",
441            )),
442        }
443    }
444
445    fn resize(&mut self, columns: u16, rows: u16) -> io::Result<()> {
446        match &self.master {
447            Some(master) => master
448                .resize(LocalPty::size(columns, rows))
449                .map_err(io::Error::other),
450            None => Ok(()),
451        }
452    }
453
454    fn read(&mut self) -> Vec<u8> {
455        lock(&self.shared).buffer.drain(..).collect()
456    }
457
458    fn set_notify(&mut self, notify: Notify) {
459        let waiting = {
460            let mut output = lock(&self.shared);
461            output.notify = Some(notify.clone());
462            !output.buffer.is_empty() || output.status.is_some()
463        };
464        if waiting {
465            notify();
466        }
467    }
468
469    fn exit_status(&mut self) -> Option<ExitStatus> {
470        let output = lock(&self.shared);
471        if output.buffer.is_empty() {
472            output.status.clone()
473        } else {
474            None
475        }
476    }
477
478    /// Hang up on the program (SIGHUP on Unix, as a terminal closing does;
479    /// on Windows it ends at once). One still running a second later, such
480    /// as one that ignores SIGHUP, is killed with SIGKILL, with everything
481    /// in its process group.
482    fn kill(&mut self) -> io::Result<()> {
483        {
484            let mut output = lock(&self.shared);
485            if output.status.is_some() {
486                return Ok(());
487            }
488            if self.killer.is_some() {
489                output.hung_up.get_or_insert_with(Instant::now);
490            }
491        }
492        match &mut self.killer {
493            Some(killer) => killer.kill(),
494            None => Ok(()),
495        }
496    }
497}
498
499impl Drop for LocalPty {
500    fn drop(&mut self) {
501        lock(&self.shared).closed = true;
502        let _ = self.kill();
503    }
504}
505
506/// Kill the process group the program leads (it called `setsid`, through
507/// portable-pty), with SIGKILL.
508#[cfg(unix)]
509#[allow(unsafe_code)]
510fn kill_group(leader: u32) {
511    if let Ok(group) = libc::pid_t::try_from(leader) {
512        // SAFETY: `killpg` takes plain integers. The leader has not been
513        // reaped (its waiter is the caller), so its id names its group.
514        unsafe {
515            libc::killpg(group, libc::SIGKILL);
516        }
517    }
518}
519
520/// On Windows the program ended when it was killed.
521#[cfg(not(unix))]
522fn kill_group(_leader: u32) {}
523
524/// Add `bytes` to the unread output. Without backpressure (`drop_oldest`),
525/// past [`MAX_BUFFERED`] the oldest goes; with it, nothing is ever dropped:
526/// the reader waits for room before reading more, so the buffer stays
527/// within the limit and one read, whatever the limit.
528fn keep(output: &mut Output, bytes: &[u8], drop_oldest: bool) {
529    output.buffer.extend(bytes);
530    if drop_oldest {
531        let over = output.buffer.len().saturating_sub(MAX_BUFFERED);
532        output.buffer.drain(..over);
533    }
534}
535
536/// Wait until the unread output is under `limit`; `false` once the host
537/// has gone.
538fn wait_for_room(shared: &Mutex<Output>, limit: usize) -> bool {
539    loop {
540        {
541            let mut output = lock(shared);
542            if output.closed {
543                return false;
544            }
545            output.paused = output.buffer.len() >= limit;
546            if !output.paused {
547                return true;
548            }
549        }
550        thread::sleep(Duration::from_millis(5));
551    }
552}
553
554/// What a [`ReplayHost`] was asked to do, and what it is to play: shared
555/// with its [`ReplayHandle`]s.
556#[derive(Default)]
557struct Replay {
558    /// Output to play once started.
559    script: Vec<u8>,
560    /// How it ends once the script has played, if it does.
561    script_exit: Option<ExitStatus>,
562    /// Output waiting to be read.
563    pending: Vec<u8>,
564    status: Option<ExitStatus>,
565    started: Option<(u16, u16)>,
566    written: Vec<u8>,
567    sizes: Vec<(u16, u16)>,
568    killed: bool,
569    notify: Option<Notify>,
570}
571
572/// A host that plays bytes back instead of running anything: for tests,
573/// demos and recordings. What it plays is given up front
574/// ([`output`](Self::output), [`exit`](Self::exit)) or later through a
575/// [`ReplayHandle`], which also sees what the pane sent it.
576///
577/// ```
578/// use rich_embed::{ExitStatus, ReplayHost};
579///
580/// let host = ReplayHost::new().output("hello\r\n").exit(ExitStatus::with_code(0));
581/// let handle = host.handle();
582/// assert!(handle.written().is_empty());
583/// ```
584#[derive(Default)]
585pub struct ReplayHost {
586    state: Arc<Mutex<Replay>>,
587}
588
589impl ReplayHost {
590    pub fn new() -> ReplayHost {
591        ReplayHost::default()
592    }
593
594    /// Play `bytes` as the program's output once it starts.
595    #[must_use]
596    pub fn output(self, bytes: impl AsRef<[u8]>) -> ReplayHost {
597        lock(&self.state).script.extend_from_slice(bytes.as_ref());
598        self
599    }
600
601    /// Exit with `status` once the output has played.
602    #[must_use]
603    pub fn exit(self, status: ExitStatus) -> ReplayHost {
604        lock(&self.state).script_exit = Some(status);
605        self
606    }
607
608    /// A handle to feed more output, end the program, and see what was
609    /// sent to it. It may go to another thread.
610    pub fn handle(&self) -> ReplayHandle {
611        ReplayHandle {
612            state: Arc::clone(&self.state),
613        }
614    }
615}
616
617/// Feeds a [`ReplayHost`] and reads back what it was sent.
618#[derive(Clone)]
619pub struct ReplayHandle {
620    state: Arc<Mutex<Replay>>,
621}
622
623impl ReplayHandle {
624    fn notify(&self) {
625        let notify = lock(&self.state).notify.clone();
626        if let Some(notify) = notify {
627            notify();
628        }
629    }
630
631    /// Output from the program, now (it waits for the start if the host has
632    /// not started yet).
633    pub fn feed(&self, bytes: impl AsRef<[u8]>) {
634        {
635            let mut state = lock(&self.state);
636            if state.started.is_some() {
637                state.pending.extend_from_slice(bytes.as_ref());
638            } else {
639                state.script.extend_from_slice(bytes.as_ref());
640            }
641        }
642        self.notify();
643    }
644
645    /// The program exits with `status`.
646    pub fn exit(&self, status: ExitStatus) {
647        {
648            let mut state = lock(&self.state);
649            if state.started.is_some() {
650                state.status = Some(status);
651            } else {
652                state.script_exit = Some(status);
653            }
654        }
655        self.notify();
656    }
657
658    /// Every byte the pane sent: keys, the mouse, pastes.
659    pub fn written(&self) -> Vec<u8> {
660        lock(&self.state).written.clone()
661    }
662
663    /// The bytes sent since the last call.
664    pub fn take_written(&self) -> Vec<u8> {
665        std::mem::take(&mut lock(&self.state).written)
666    }
667
668    /// The size it started at, once it has.
669    pub fn started(&self) -> Option<(u16, u16)> {
670        lock(&self.state).started
671    }
672
673    /// Every resize after the start, as columns and rows.
674    pub fn sizes(&self) -> Vec<(u16, u16)> {
675        lock(&self.state).sizes.clone()
676    }
677
678    /// Whether it was killed.
679    pub fn killed(&self) -> bool {
680        lock(&self.state).killed
681    }
682}
683
684impl PtyHost for ReplayHost {
685    fn start(&mut self, columns: u16, rows: u16) -> io::Result<()> {
686        {
687            let mut state = lock(&self.state);
688            if state.started.is_some() {
689                return Ok(());
690            }
691            state.started = Some((columns, rows));
692            let script = std::mem::take(&mut state.script);
693            state.pending.extend_from_slice(&script);
694            state.status = state.script_exit.take();
695        }
696        self.handle().notify();
697        Ok(())
698    }
699
700    fn write(&mut self, bytes: &[u8]) -> io::Result<()> {
701        lock(&self.state).written.extend_from_slice(bytes);
702        Ok(())
703    }
704
705    fn resize(&mut self, columns: u16, rows: u16) -> io::Result<()> {
706        lock(&self.state).sizes.push((columns, rows));
707        Ok(())
708    }
709
710    fn read(&mut self) -> Vec<u8> {
711        std::mem::take(&mut lock(&self.state).pending)
712    }
713
714    fn set_notify(&mut self, notify: Notify) {
715        let waiting = {
716            let mut state = lock(&self.state);
717            state.notify = Some(notify.clone());
718            !state.pending.is_empty() || state.status.is_some()
719        };
720        if waiting {
721            notify();
722        }
723    }
724
725    fn exit_status(&mut self) -> Option<ExitStatus> {
726        let state = lock(&self.state);
727        if state.pending.is_empty() {
728            state.status.clone()
729        } else {
730            None
731        }
732    }
733
734    fn kill(&mut self) -> io::Result<()> {
735        let mut state = lock(&self.state);
736        state.killed = true;
737        if state.status.is_none() && state.started.is_some() {
738            state.status = Some(ExitStatus::with_signal("Killed"));
739        }
740        Ok(())
741    }
742}
743
744#[cfg(test)]
745mod tests {
746    use super::*;
747
748    #[test]
749    fn backpressure_never_drops_output_past_the_legacy_limit() {
750        let chunk = vec![b'x'; 1 << 20];
751        // With backpressure (here above 16 MiB), every byte is kept.
752        let mut held = Output::default();
753        for _ in 0..(MAX_BUFFERED / chunk.len() + 4) {
754            keep(&mut held, &chunk, false);
755        }
756        assert_eq!(held.buffer.len(), MAX_BUFFERED + 4 * chunk.len());
757        // Without it, the oldest go past 16 MiB, as before.
758        let mut dropped = Output::default();
759        for _ in 0..(MAX_BUFFERED / chunk.len() + 4) {
760            keep(&mut dropped, &chunk, true);
761        }
762        assert_eq!(dropped.buffer.len(), MAX_BUFFERED);
763    }
764}