//! The single writer of stdout. Control frames (results, errors, pings, watch events) always go before bulk command //! output, so a slow link delays output but never a cancel's result or a ping. Bulk output is bounded: readers of //! commands without an output window wait while too much of it is unsent, which slows the command down like a full //! pipe would. use crate::frame::{self, Frame}; use std::collections::VecDeque; use std::io::Write; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::{Arc, Condvar, Mutex}; use std::time::Duration; /// Unsent bulk bytes above which unwindowed command output waits. const BULK_LIMIT: usize = 4 * 1024 * 1024; #[derive(Default)] struct Queues { control: VecDeque, bulk: VecDeque<(Frame, Option>)>, bulk_bytes: usize, closed: bool, } #[derive(Default)] pub struct Output { queues: Mutex, /// Signalled when a frame is queued or the writer stops. queued: Condvar, /// Signalled when bulk bytes were written. drained: Condvar, } impl Output { pub fn control(&self, frame: Frame) { let mut queues = self.queues.lock().unwrap(); queues.control.push_back(frame); self.queued.notify_one(); } /// Queue bulk output; `unsent` counts this sender's frames until they are written. pub fn bulk(&self, frame: Frame, unsent: Option>) { let mut queues = self.queues.lock().unwrap(); queues.bulk_bytes += frame.payload.len(); if let Some(counter) = &unsent { counter.fetch_add(1, Ordering::SeqCst); } queues.bulk.push_back((frame, unsent)); self.queued.notify_one(); } /// Wait while more than the bulk limit is unsent, unless `stop` is set. pub fn wait_for_room(&self, stop: &AtomicBool) { let mut queues = self.queues.lock().unwrap(); while queues.bulk_bytes > BULK_LIMIT && !queues.closed && !stop.load(Ordering::SeqCst) { queues = self .drained .wait_timeout(queues, Duration::from_millis(100)) .unwrap() .0; } } pub fn close(&self) { let mut queues = self.queues.lock().unwrap(); queues.closed = true; self.queued.notify_all(); self.drained.notify_all(); } /// Write frames to `writer` until closed or a write fails. pub fn run(&self, writer: &mut impl Write) { loop { let (frame, unsent) = { let mut queues = self.queues.lock().unwrap(); loop { if let Some(frame) = queues.control.pop_front() { break (frame, None); } if let Some((frame, unsent)) = queues.bulk.pop_front() { queues.bulk_bytes -= frame.payload.len(); break (frame, unsent); } if queues.closed { return; } queues = self.queued.wait(queues).unwrap(); } }; let failed = frame::write_frame(writer, &frame).is_err(); if let Some(counter) = unsent { counter.fetch_sub(1, Ordering::SeqCst); } self.drained.notify_all(); if failed { self.close(); return; } } } }