Bound terminal output under sustained load
ci / test (push) Canceled after 0s
ci / fuzz-smoke (push) Canceled after 0s
ci / macos-client (macos-aarch64, macos-14) (push) Canceled after 0s
ci / macos-client (macos-x86_64, macos-13) (push) Canceled after 0s
ci / windows-client (push) Canceled after 0s
ci / package-release (linux-x86_64, ubuntu-latest, , , ) (push) Canceled after 0s
ci / package-release (macos-aarch64, macos-14, , , ) (push) Canceled after 0s
ci / package-release (macos-x86_64, macos-13, , , ) (push) Canceled after 0s
ci / package-release (windows-aarch64, windows-latest, aarch64, windows, aarch64-pc-windows-msvc) (push) Canceled after 0s
ci / package-release (windows-x86_64, windows-latest, , , ) (push) Canceled after 0s
ci / remote-bench (push) Canceled after 0s
ci / publish-gitea-release (push) Canceled after 0s

This commit is contained in:
DuProcess
2026-07-17 19:08:56 -04:00
parent d4de2915a1
commit d0917c952f
3 changed files with 75 additions and 38 deletions
+33 -28
View File
@@ -27,7 +27,9 @@ use dosh::protocol::{
StreamClose, StreamData, StreamEof, StreamOpen, StreamOpenOk, StreamOpenReject,
StreamWindowAdjust, TicketAttachBody, TicketAttachEnvelope, TicketAttachOkEnvelope,
};
use dosh::pty::{PtyHandle, PtyOutput, adopt_pty_from_fd, spawn_pty_session};
use dosh::pty::{
PTY_OUTPUT_QUEUE_CAPACITY, PtyHandle, PtyOutput, adopt_pty_from_fd, spawn_pty_session,
};
use dosh::udp::{is_transient_udp_error, is_transient_udp_send_error};
use sha2::{Digest, Sha256};
use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
@@ -209,7 +211,7 @@ async fn serve(config_path: Option<std::path::PathBuf>) -> Result<()> {
],
);
let (pty_tx, mut pty_rx) = mpsc::unbounded_channel();
let (pty_tx, mut pty_rx) = mpsc::channel(PTY_OUTPUT_QUEUE_CAPACITY);
let state = Arc::new(Mutex::new(ServerState::new(
config.clone(),
secret,
@@ -242,6 +244,7 @@ async fn serve(config_path: Option<std::path::PathBuf>) -> Result<()> {
let retransmit_socket = Arc::clone(&socket);
tokio::spawn(async move {
let mut interval = tokio::time::interval(Duration::from_millis(100));
interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
loop {
interval.tick().await;
if let Err(err) = retransmit_pending(&retransmit_state, &retransmit_socket).await {
@@ -256,6 +259,7 @@ async fn serve(config_path: Option<std::path::PathBuf>) -> Result<()> {
let cleanup_state = Arc::clone(&state);
tokio::spawn(async move {
let mut interval = tokio::time::interval(Duration::from_secs(5));
interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
loop {
interval.tick().await;
cleanup_disconnected_clients(&cleanup_state);
@@ -270,6 +274,7 @@ async fn serve(config_path: Option<std::path::PathBuf>) -> Result<()> {
let flush_state = Arc::clone(&state);
tokio::spawn(async move {
let mut interval = tokio::time::interval(SCREEN_PERSIST_MAX_AGE);
interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
loop {
interval.tick().await;
flush_persistent_screens(&flush_state);
@@ -293,7 +298,7 @@ async fn serve(config_path: Option<std::path::PathBuf>) -> Result<()> {
struct ServerState {
config: ServerConfig,
secret: [u8; 32],
pty_tx: mpsc::UnboundedSender<PtyOutput>,
pty_tx: mpsc::Sender<PtyOutput>,
sessions: HashMap<String, Session>,
pending_native: HashMap<[u8; 16], PendingNativeAuth>,
next_server_stream_id: u64,
@@ -504,7 +509,7 @@ impl ServerState {
fn new(
config: ServerConfig,
secret: [u8; 32],
pty_tx: mpsc::UnboundedSender<PtyOutput>,
pty_tx: mpsc::Sender<PtyOutput>,
) -> Self {
let per_minute = config.native_auth_rate_limit_per_minute;
Self {
@@ -4517,7 +4522,7 @@ mod tests {
#[test]
fn persists_terminal_sessions_when_enabled() {
let (pty_tx, _rx) = mpsc::unbounded_channel();
let (pty_tx, _rx) = mpsc::channel(PTY_OUTPUT_QUEUE_CAPACITY);
let config = ServerConfig {
persist_sessions: true,
prewarm_sessions: vec!["default".to_string()],
@@ -4531,7 +4536,7 @@ mod tests {
#[test]
fn persist_disabled_never_persists() {
let (pty_tx, _rx) = mpsc::unbounded_channel();
let (pty_tx, _rx) = mpsc::channel(PTY_OUTPUT_QUEUE_CAPACITY);
let config = ServerConfig {
persist_sessions: false,
prewarm_sessions: vec!["default".to_string()],
@@ -4655,7 +4660,7 @@ mod tests {
#[tokio::test]
async fn no_client_pty_output_before_first_reattach_keeps_restored_screen() {
let restored = b"\x1b[?1049lRESTORED_AFTER_RESTART".to_vec();
let (pty_tx, _rx) = mpsc::unbounded_channel();
let (pty_tx, _rx) = mpsc::channel(PTY_OUTPUT_QUEUE_CAPACITY);
let state = Arc::new(Mutex::new(ServerState::new(
ServerConfig {
persist_sessions: true,
@@ -4733,7 +4738,7 @@ mod tests {
#[tokio::test]
async fn unknown_resume_reject_keeps_client_id() {
let (pty_tx, _rx) = mpsc::unbounded_channel();
let (pty_tx, _rx) = mpsc::channel(PTY_OUTPUT_QUEUE_CAPACITY);
let state = Arc::new(Mutex::new(ServerState::new(
ServerConfig::default(),
[0u8; 32],
@@ -4957,7 +4962,7 @@ mod tests {
#[test]
fn client_index_stays_in_sync_with_session_clients() {
let (pty_tx, _pty_rx) = mpsc::unbounded_channel();
let (pty_tx, _pty_rx) = mpsc::channel(PTY_OUTPUT_QUEUE_CAPACITY);
let mut state = ServerState::new(ServerConfig::default(), [0u8; 32], pty_tx);
state
.ensure_session("work", 80, 24, "forward-only", &[])
@@ -5004,7 +5009,7 @@ mod tests {
#[test]
fn cleanup_purges_timed_out_clients_from_index() {
let (pty_tx, _pty_rx) = mpsc::unbounded_channel();
let (pty_tx, _pty_rx) = mpsc::channel(PTY_OUTPUT_QUEUE_CAPACITY);
let config = ServerConfig {
client_timeout_secs: 1,
..ServerConfig::default()
@@ -5052,7 +5057,7 @@ mod tests {
#[tokio::test]
async fn forged_plaintext_detach_does_not_remove_client() {
let (pty_tx, _pty_rx) = mpsc::unbounded_channel();
let (pty_tx, _pty_rx) = mpsc::channel(PTY_OUTPUT_QUEUE_CAPACITY);
let mut state = ServerState::new(ServerConfig::default(), [0u8; 32], pty_tx);
state
.ensure_session("work", 80, 24, "forward-only", &[])
@@ -5078,7 +5083,7 @@ mod tests {
#[tokio::test]
async fn authenticated_detach_removes_client() {
let (pty_tx, _pty_rx) = mpsc::unbounded_channel();
let (pty_tx, _pty_rx) = mpsc::channel(PTY_OUTPUT_QUEUE_CAPACITY);
let mut state = ServerState::new(ServerConfig::default(), [0u8; 32], pty_tx);
state
.ensure_session("work", 80, 24, "forward-only", &[])
@@ -5109,7 +5114,7 @@ mod tests {
#[tokio::test]
async fn duplicate_stream_open_for_open_stream_resends_ok() {
let (pty_tx, _pty_rx) = mpsc::unbounded_channel();
let (pty_tx, _pty_rx) = mpsc::channel(PTY_OUTPUT_QUEUE_CAPACITY);
let mut state = ServerState::new(ServerConfig::default(), [0u8; 32], pty_tx);
let client_id = [11u8; 16];
let session_key = [12u8; 32];
@@ -5173,7 +5178,7 @@ mod tests {
#[tokio::test]
async fn retired_stream_open_is_rejected_not_reopened() {
let (pty_tx, _pty_rx) = mpsc::unbounded_channel();
let (pty_tx, _pty_rx) = mpsc::channel(PTY_OUTPUT_QUEUE_CAPACITY);
let mut state = ServerState::new(ServerConfig::default(), [0u8; 32], pty_tx);
let client_id = [15u8; 16];
let session_key = [16u8; 32];
@@ -5243,7 +5248,7 @@ mod tests {
#[tokio::test]
async fn pending_server_stream_open_is_retransmitted() {
let (pty_tx, _pty_rx) = mpsc::unbounded_channel();
let (pty_tx, _pty_rx) = mpsc::channel(PTY_OUTPUT_QUEUE_CAPACITY);
let mut state = ServerState::new(ServerConfig::default(), [0u8; 32], pty_tx);
let client_id = [13u8; 16];
let session_key = [14u8; 32];
@@ -5298,7 +5303,7 @@ mod tests {
#[tokio::test]
async fn server_stream_retransmit_uses_observed_rtt() {
let (pty_tx, _pty_rx) = mpsc::unbounded_channel();
let (pty_tx, _pty_rx) = mpsc::channel(PTY_OUTPUT_QUEUE_CAPACITY);
let mut state = ServerState::new(ServerConfig::default(), [0u8; 32], pty_tx);
let client_id = [15u8; 16];
let session_key = [16u8; 32];
@@ -5357,7 +5362,7 @@ mod tests {
#[test]
fn forward_only_session_does_not_allocate_pty() {
let (pty_tx, _pty_rx) = mpsc::unbounded_channel();
let (pty_tx, _pty_rx) = mpsc::channel(PTY_OUTPUT_QUEUE_CAPACITY);
let mut state = ServerState::new(ServerConfig::default(), [0u8; 32], pty_tx);
state
@@ -5369,7 +5374,7 @@ mod tests {
#[test]
fn cleanup_reaps_abandoned_sessions_but_keeps_prewarmed() {
let (pty_tx, _pty_rx) = mpsc::unbounded_channel();
let (pty_tx, _pty_rx) = mpsc::channel(PTY_OUTPUT_QUEUE_CAPACITY);
let config = ServerConfig {
client_timeout_secs: 1,
prewarm_sessions: vec!["default".to_string()],
@@ -5454,7 +5459,7 @@ mod tests {
#[tokio::test]
async fn stream_data_waits_for_open_and_credit() {
let (pty_tx, _pty_rx) = mpsc::unbounded_channel();
let (pty_tx, _pty_rx) = mpsc::channel(PTY_OUTPUT_QUEUE_CAPACITY);
let mut state = ServerState::new(ServerConfig::default(), [0u8; 32], pty_tx);
let client_id = [7u8; 16];
let session_key = [9u8; 32];
@@ -5558,7 +5563,7 @@ mod tests {
#[tokio::test]
async fn stream_eof_to_client_does_not_retire_stream() {
let (pty_tx, _pty_rx) = mpsc::unbounded_channel();
let (pty_tx, _pty_rx) = mpsc::channel(PTY_OUTPUT_QUEUE_CAPACITY);
let mut state = ServerState::new(ServerConfig::default(), [0u8; 32], pty_tx);
let client_id = [13u8; 16];
let session_key = [14u8; 32];
@@ -5647,7 +5652,7 @@ mod tests {
#[tokio::test]
async fn pending_server_stream_eof_is_retransmitted() {
let (pty_tx, _pty_rx) = mpsc::unbounded_channel();
let (pty_tx, _pty_rx) = mpsc::channel(PTY_OUTPUT_QUEUE_CAPACITY);
let mut state = ServerState::new(ServerConfig::default(), [0u8; 32], pty_tx);
let client_id = [17u8; 16];
let session_key = [18u8; 32];
@@ -5707,7 +5712,7 @@ mod tests {
#[tokio::test]
async fn pending_server_stream_close_is_retransmitted() {
let (pty_tx, _pty_rx) = mpsc::unbounded_channel();
let (pty_tx, _pty_rx) = mpsc::channel(PTY_OUTPUT_QUEUE_CAPACITY);
let mut state = ServerState::new(ServerConfig::default(), [0u8; 32], pty_tx);
let client_id = [19u8; 16];
let session_key = [20u8; 32];
@@ -5763,7 +5768,7 @@ mod tests {
#[tokio::test]
async fn pending_server_stream_close_expires_after_attempt_cap() {
let (pty_tx, _pty_rx) = mpsc::unbounded_channel();
let (pty_tx, _pty_rx) = mpsc::channel(PTY_OUTPUT_QUEUE_CAPACITY);
let mut state = ServerState::new(ServerConfig::default(), [0u8; 32], pty_tx);
let client_id = [21u8; 16];
let session_key = [22u8; 32];
@@ -5808,7 +5813,7 @@ mod tests {
#[tokio::test]
async fn pending_server_stream_window_adjust_is_retransmitted() {
let (pty_tx, _pty_rx) = mpsc::unbounded_channel();
let (pty_tx, _pty_rx) = mpsc::channel(PTY_OUTPUT_QUEUE_CAPACITY);
let mut state = ServerState::new(ServerConfig::default(), [0u8; 32], pty_tx);
let client_id = [23u8; 16];
let session_key = [24u8; 32];
@@ -5871,7 +5876,7 @@ mod tests {
#[tokio::test]
async fn pending_server_stream_window_adjust_expires_after_attempt_cap() {
let (pty_tx, _pty_rx) = mpsc::unbounded_channel();
let (pty_tx, _pty_rx) = mpsc::channel(PTY_OUTPUT_QUEUE_CAPACITY);
let mut state = ServerState::new(ServerConfig::default(), [0u8; 32], pty_tx);
let client_id = [25u8; 16];
let session_key = [26u8; 32];
@@ -5922,7 +5927,7 @@ mod tests {
#[tokio::test]
async fn server_stream_send_splits_large_writes() {
let (pty_tx, _pty_rx) = mpsc::unbounded_channel();
let (pty_tx, _pty_rx) = mpsc::channel(PTY_OUTPUT_QUEUE_CAPACITY);
let mut state = ServerState::new(ServerConfig::default(), [0u8; 32], pty_tx);
let client_id = [11u8; 16];
let session_key = [12u8; 32];
@@ -6029,7 +6034,7 @@ mod tests {
#[tokio::test]
async fn blocked_stream_data_does_not_block_terminal_frames() {
let (pty_tx, _pty_rx) = mpsc::unbounded_channel();
let (pty_tx, _pty_rx) = mpsc::channel(PTY_OUTPUT_QUEUE_CAPACITY);
let mut state = ServerState::new(ServerConfig::default(), [0u8; 32], pty_tx);
let client_id = [8u8; 16];
let session_key = [10u8; 32];
@@ -6134,7 +6139,7 @@ mod tests {
#[tokio::test]
async fn live_terminal_output_is_never_replaced_by_paced_snapshots() {
let (pty_tx, _pty_rx) = mpsc::unbounded_channel();
let (pty_tx, _pty_rx) = mpsc::channel(PTY_OUTPUT_QUEUE_CAPACITY);
let config = ServerConfig {
output_frame_interval_ms: 1000,
..ServerConfig::default()