Retire terminal forwarding stream ids

This commit is contained in:
DuProcess
2026-07-10 22:23:34 -04:00
parent 5ca3e98a5c
commit 262dfaca04
2 changed files with 286 additions and 14 deletions
+150 -10
View File
@@ -61,6 +61,7 @@ use tokio::net::{TcpListener, TcpStream, UdpSocket};
use tokio::sync::mpsc;
const STREAM_INITIAL_WINDOW: usize = 1024 * 1024;
const STREAM_RETIRED_TOMBSTONES: usize = 16 * 1024;
const MAX_PENDING_USER_INPUT_BYTES: usize = 1024 * 1024;
const STARTUP_INPUT_HOLD: Duration = Duration::from_millis(750);
const POST_SUBMIT_ALL_INPUT_HOLD: Duration = Duration::from_millis(120);
@@ -4335,6 +4336,8 @@ async fn run_terminal(
let mut stream_sent_data: HashMap<u64, BTreeMap<u64, PendingStreamChunk>> = HashMap::new();
let mut stream_next_recv_offset: HashMap<u64, u64> = HashMap::new();
let mut stream_recv_pending: HashMap<u64, BTreeMap<u64, Vec<u8>>> = HashMap::new();
let mut stream_retired: HashSet<u64> = HashSet::new();
let mut stream_retired_order: VecDeque<u64> = VecDeque::new();
let mut pending_user_input: VecDeque<PendingUserInput> = VecDeque::new();
let mut pending_user_input_bytes = 0usize;
let mut startup_input_hold_until: Option<Instant> = None;
@@ -4827,6 +4830,18 @@ async fn run_terminal(
continue;
};
last_packet_at = Instant::now();
if stream_retired.contains(&open.stream_id) {
send_stream_open_reject(
&socket,
addr,
&cred,
&mut send_seq,
open.stream_id,
"stream closed".to_string(),
)
.await?;
continue;
}
if opened_streams.contains(&open.stream_id) {
send_stream_open_ok(
&socket,
@@ -5024,7 +5039,7 @@ async fn run_terminal(
{
let _ = writer.write_all(&[5, 5, 0, 1, 0, 0, 0, 0, 0, 0]).await;
}
cleanup_stream_state(
retire_stream_state(
reject.stream_id,
&mut opened_streams,
&mut stream_send_credit,
@@ -5034,6 +5049,8 @@ async fn run_terminal(
&mut stream_sent_data,
&mut stream_next_recv_offset,
&mut stream_recv_pending,
&mut stream_retired,
&mut stream_retired_order,
);
stream_writers.remove(&reject.stream_id);
}
@@ -5113,7 +5130,7 @@ async fn run_terminal(
};
last_packet_at = Instant::now();
pending_socks_replies.remove(&close.stream_id);
cleanup_stream_state(
retire_stream_state(
close.stream_id,
&mut opened_streams,
&mut stream_send_credit,
@@ -5123,6 +5140,8 @@ async fn run_terminal(
&mut stream_sent_data,
&mut stream_next_recv_offset,
&mut stream_recv_pending,
&mut stream_retired,
&mut stream_retired_order,
);
stream_writers.remove(&close.stream_id);
#[cfg(unix)]
@@ -5175,7 +5194,7 @@ async fn run_terminal(
}
Some(ForwardEvent::Close { stream_id }) => {
pending_socks_replies.remove(&stream_id);
cleanup_stream_state(
retire_stream_state(
stream_id,
&mut opened_streams,
&mut stream_send_credit,
@@ -5185,6 +5204,8 @@ async fn run_terminal(
&mut stream_sent_data,
&mut stream_next_recv_offset,
&mut stream_recv_pending,
&mut stream_retired,
&mut stream_retired_order,
);
stream_writers.remove(&stream_id);
#[cfg(unix)]
@@ -6188,6 +6209,77 @@ fn cleanup_stream_state(
stream_recv_pending.remove(&stream_id);
}
#[allow(clippy::too_many_arguments)]
fn stream_state_exists(
stream_id: u64,
opened_streams: &HashSet<u64>,
stream_send_credit: &HashMap<u64, usize>,
stream_pending_data: &HashMap<u64, VecDeque<Vec<u8>>>,
stream_pending_opens: &HashMap<u64, PendingStreamOpen>,
stream_next_send_offset: &HashMap<u64, u64>,
stream_sent_data: &HashMap<u64, BTreeMap<u64, PendingStreamChunk>>,
stream_next_recv_offset: &HashMap<u64, u64>,
stream_recv_pending: &HashMap<u64, BTreeMap<u64, Vec<u8>>>,
) -> bool {
opened_streams.contains(&stream_id)
|| stream_send_credit.contains_key(&stream_id)
|| stream_pending_data.contains_key(&stream_id)
|| stream_pending_opens.contains_key(&stream_id)
|| stream_next_send_offset.contains_key(&stream_id)
|| stream_sent_data.contains_key(&stream_id)
|| stream_next_recv_offset.contains_key(&stream_id)
|| stream_recv_pending.contains_key(&stream_id)
}
#[allow(clippy::too_many_arguments)]
fn retire_stream_state(
stream_id: u64,
opened_streams: &mut HashSet<u64>,
stream_send_credit: &mut HashMap<u64, usize>,
stream_pending_data: &mut HashMap<u64, VecDeque<Vec<u8>>>,
stream_pending_opens: &mut HashMap<u64, PendingStreamOpen>,
stream_next_send_offset: &mut HashMap<u64, u64>,
stream_sent_data: &mut HashMap<u64, BTreeMap<u64, PendingStreamChunk>>,
stream_next_recv_offset: &mut HashMap<u64, u64>,
stream_recv_pending: &mut HashMap<u64, BTreeMap<u64, Vec<u8>>>,
stream_retired: &mut HashSet<u64>,
stream_retired_order: &mut VecDeque<u64>,
) {
let had_state = stream_state_exists(
stream_id,
opened_streams,
stream_send_credit,
stream_pending_data,
stream_pending_opens,
stream_next_send_offset,
stream_sent_data,
stream_next_recv_offset,
stream_recv_pending,
);
cleanup_stream_state(
stream_id,
opened_streams,
stream_send_credit,
stream_pending_data,
stream_pending_opens,
stream_next_send_offset,
stream_sent_data,
stream_next_recv_offset,
stream_recv_pending,
);
if !had_state || STREAM_RETIRED_TOMBSTONES == 0 {
return;
}
if stream_retired.insert(stream_id) {
stream_retired_order.push_back(stream_id);
}
while stream_retired_order.len() > STREAM_RETIRED_TOMBSTONES {
if let Some(expired) = stream_retired_order.pop_front() {
stream_retired.remove(&expired);
}
}
}
fn ack_stream_data(
stream_sent_data: &mut HashMap<u64, BTreeMap<u64, PendingStreamChunk>>,
stream_send_credit: &mut HashMap<u64, usize>,
@@ -7481,12 +7573,12 @@ mod tests {
post_submit_hold_duration, queue_pending_user_input, raw_contains_host_table,
recv_response_until, refresh_live_addr, release_tag_download_url,
release_tag_from_effective_url, release_version_from_tag, render_status_clear,
render_status_overlay, requested_env, resolved_startup_command, retransmit_stream_opens,
rewrite_forward_command, selected_predict_mode, selected_udp_host, server_version_mismatch,
should_flush_terminal_input_after_contact, should_hold_during_startup_gate,
should_hold_post_submit_input, should_repaint_idle_alternate_screen,
split_after_command_submit, ssh_destination_host, ssh_username, ssh_with_user,
startup_command, status_ssh_target, strip_stale_mouse_reports,
render_status_overlay, requested_env, resolved_startup_command, retire_stream_state,
retransmit_stream_opens, rewrite_forward_command, selected_predict_mode, selected_udp_host,
server_version_mismatch, should_flush_terminal_input_after_contact,
should_hold_during_startup_gate, should_hold_post_submit_input,
should_repaint_idle_alternate_screen, split_after_command_submit, ssh_destination_host,
ssh_username, ssh_with_user, startup_command, status_ssh_target, strip_stale_mouse_reports,
strip_terminal_focus_reports, terminal_private_mode_transition, toml_bare_key_or_quoted,
update_check_requested, update_version_status, upsert_managed_block, valid_forward_host,
vscode_safe_alias,
@@ -7496,7 +7588,7 @@ mod tests {
use dosh::protocol::{self, Frame, PacketKind};
use dosh::udp::is_transient_udp_send_error;
use ed25519_dalek::{SigningKey, VerifyingKey};
use std::collections::{BTreeMap, HashMap, VecDeque};
use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
use std::fs;
use std::time::{Duration, Instant};
@@ -7636,6 +7728,54 @@ mod tests {
assert!(stream_recv_pending.contains_key(&other_stream_id));
}
#[test]
fn retire_stream_state_tombstones_only_real_stream_state() {
let stream_id = 42;
let mut opened_streams = HashSet::from([stream_id]);
let mut stream_send_credit = HashMap::from([(stream_id, 12)]);
let mut stream_pending_data = HashMap::new();
let mut stream_pending_opens = HashMap::new();
let mut stream_next_send_offset = HashMap::new();
let mut stream_sent_data = HashMap::new();
let mut stream_next_recv_offset = HashMap::new();
let mut stream_recv_pending = HashMap::new();
let mut stream_retired = HashSet::new();
let mut stream_retired_order = VecDeque::new();
retire_stream_state(
stream_id,
&mut opened_streams,
&mut stream_send_credit,
&mut stream_pending_data,
&mut stream_pending_opens,
&mut stream_next_send_offset,
&mut stream_sent_data,
&mut stream_next_recv_offset,
&mut stream_recv_pending,
&mut stream_retired,
&mut stream_retired_order,
);
retire_stream_state(
99,
&mut opened_streams,
&mut stream_send_credit,
&mut stream_pending_data,
&mut stream_pending_opens,
&mut stream_next_send_offset,
&mut stream_sent_data,
&mut stream_next_recv_offset,
&mut stream_recv_pending,
&mut stream_retired,
&mut stream_retired_order,
);
assert!(!opened_streams.contains(&stream_id));
assert!(!stream_send_credit.contains_key(&stream_id));
assert!(stream_retired.contains(&stream_id));
assert!(!stream_retired.contains(&99));
assert_eq!(stream_retired_order, VecDeque::from([stream_id]));
}
#[test]
fn managed_ssh_block_is_replaced_not_duplicated() {
let dir = tempfile::tempdir().unwrap();