Retry wake repaint after local sleep
ci / test (push) Waiting to run
ci / fuzz-smoke (push) Waiting to run
ci / windows-client (push) Waiting to run
ci / package-release (linux-x86_64, ubuntu-latest) (push) Waiting to run
ci / package-release (macos-aarch64, macos-14) (push) Waiting to run
ci / package-release (macos-x86_64, macos-13) (push) Waiting to run
ci / package-release (windows-x86_64, windows-latest) (push) Waiting to run
ci / publish-gitea-release (push) Blocked by required conditions
ci / remote-bench (push) Waiting to run

This commit is contained in:
DuProcess
2026-07-12 21:47:02 -04:00
parent fca1f9a08e
commit 695f356411
+86 -28
View File
@@ -76,6 +76,7 @@ const FOCUS_REPAINT_COOLDOWN: Duration = Duration::from_secs(1);
const ALT_SCREEN_IDLE_REPAINT_AFTER: Duration = Duration::from_secs(15); const ALT_SCREEN_IDLE_REPAINT_AFTER: Duration = Duration::from_secs(15);
const LOCAL_SLEEP_REPAINT_AFTER: Duration = Duration::from_secs(5); const LOCAL_SLEEP_REPAINT_AFTER: Duration = Duration::from_secs(5);
const LOCAL_SLEEP_REPAINT_RETRY_AFTER: Duration = Duration::from_secs(1); const LOCAL_SLEEP_REPAINT_RETRY_AFTER: Duration = Duration::from_secs(1);
const LOCAL_SLEEP_REPAINT_RETRY_WINDOW: Duration = Duration::from_secs(10);
/// Sentinel `target_host` the server uses on a server-initiated `StreamOpen` that /// Sentinel `target_host` the server uses on a server-initiated `StreamOpen` that
/// represents an SSH-agent connection (rather than a TCP target). The client /// represents an SSH-agent connection (rather than a TCP target). The client
@@ -5839,6 +5840,7 @@ async fn run_terminal(
let mut last_focus_repaint_at = Instant::now() - FOCUS_REPAINT_COOLDOWN; let mut last_focus_repaint_at = Instant::now() - FOCUS_REPAINT_COOLDOWN;
let mut last_idle_repaint_attempt_at = Instant::now() - ALT_SCREEN_IDLE_REPAINT_AFTER; let mut last_idle_repaint_attempt_at = Instant::now() - ALT_SCREEN_IDLE_REPAINT_AFTER;
let mut last_status_tick_at = Instant::now(); let mut last_status_tick_at = Instant::now();
let mut wake_repaint_retry_until: Option<Instant> = None;
if let Some(frame) = first_frame { if let Some(frame) = first_frame {
if !forward_only { if !forward_only {
render_frame(&frame)?; render_frame(&frame)?;
@@ -5946,6 +5948,7 @@ async fn run_terminal(
last_terminal_frame_at = Instant::now(); last_terminal_frame_at = Instant::now();
last_packet_at = Instant::now(); last_packet_at = Instant::now();
last_focus_repaint_at = Instant::now(); last_focus_repaint_at = Instant::now();
wake_repaint_retry_until = None;
refreshed_before_input = true; refreshed_before_input = true;
flush_pending_user_input( flush_pending_user_input(
&socket, &socket,
@@ -5995,6 +5998,7 @@ async fn run_terminal(
predictor.observe_output(&frame.bytes); predictor.observe_output(&frame.bytes);
last_terminal_frame_at = Instant::now(); last_terminal_frame_at = Instant::now();
last_packet_at = Instant::now(); last_packet_at = Instant::now();
wake_repaint_retry_until = None;
flush_pending_user_input( flush_pending_user_input(
&socket, &socket,
addr, addr,
@@ -6279,6 +6283,7 @@ async fn run_terminal(
render_frame(&frame)?; render_frame(&frame)?;
predictor.observe_output(&frame.bytes); predictor.observe_output(&frame.bytes);
last_terminal_frame_at = Instant::now(); last_terminal_frame_at = Instant::now();
wake_repaint_retry_until = None;
} }
last_packet_at = Instant::now(); last_packet_at = Instant::now();
flush_pending_user_input( flush_pending_user_input(
@@ -6337,6 +6342,7 @@ async fn run_terminal(
render_frame(&frame)?; render_frame(&frame)?;
predictor.observe_output(&frame.bytes); predictor.observe_output(&frame.bytes);
last_terminal_frame_at = Instant::now(); last_terminal_frame_at = Instant::now();
wake_repaint_retry_until = None;
} }
last_packet_at = Instant::now(); last_packet_at = Instant::now();
flush_pending_user_input( flush_pending_user_input(
@@ -6463,6 +6469,7 @@ async fn run_terminal(
render_frame(&frame)?; render_frame(&frame)?;
predictor.observe_output(&frame.bytes); predictor.observe_output(&frame.bytes);
last_terminal_frame_at = Instant::now(); last_terminal_frame_at = Instant::now();
wake_repaint_retry_until = None;
} }
last_packet_at = Instant::now(); last_packet_at = Instant::now();
flush_pending_user_input( flush_pending_user_input(
@@ -6878,6 +6885,20 @@ async fn run_terminal(
let status_tick_at = Instant::now(); let status_tick_at = Instant::now();
let status_tick_gap = status_tick_at.duration_since(last_status_tick_at); let status_tick_gap = status_tick_at.duration_since(last_status_tick_at);
last_status_tick_at = status_tick_at; last_status_tick_at = status_tick_at;
if status_tick_gap >= LOCAL_SLEEP_REPAINT_AFTER {
wake_repaint_retry_until =
Some(status_tick_at + LOCAL_SLEEP_REPAINT_RETRY_WINDOW);
dosh::trace::event(
"client.wake_repaint_retry_arm",
&[
("tick_gap_ms", status_tick_gap.as_millis().to_string()),
(
"window_ms",
LOCAL_SLEEP_REPAINT_RETRY_WINDOW.as_millis().to_string(),
),
],
);
}
let mut repainted_this_tick = false; let mut repainted_this_tick = false;
let stale = last_packet_at.elapsed(); let stale = last_packet_at.elapsed();
if stale >= Duration::from_secs(reconnect_timeout_secs.max(1)) { if stale >= Duration::from_secs(reconnect_timeout_secs.max(1)) {
@@ -6900,6 +6921,7 @@ async fn run_terminal(
predictor.observe_output(&frame.bytes); predictor.observe_output(&frame.bytes);
last_terminal_frame_at = Instant::now(); last_terminal_frame_at = Instant::now();
last_idle_repaint_attempt_at = Instant::now(); last_idle_repaint_attempt_at = Instant::now();
wake_repaint_retry_until = None;
repainted_this_tick = true; repainted_this_tick = true;
} }
last_packet_at = Instant::now(); last_packet_at = Instant::now();
@@ -6948,6 +6970,7 @@ async fn run_terminal(
last_terminal_frame_at, last_terminal_frame_at,
last_idle_repaint_attempt_at, last_idle_repaint_attempt_at,
status_tick_gap, status_tick_gap,
wake_repaint_retry_until,
now, now,
) { ) {
last_idle_repaint_attempt_at = now; last_idle_repaint_attempt_at = now;
@@ -6977,6 +7000,7 @@ async fn run_terminal(
predictor.observe_output(&frame.bytes); predictor.observe_output(&frame.bytes);
last_terminal_frame_at = Instant::now(); last_terminal_frame_at = Instant::now();
last_packet_at = Instant::now(); last_packet_at = Instant::now();
wake_repaint_retry_until = None;
flush_pending_user_input( flush_pending_user_input(
&socket, &socket,
addr, addr,
@@ -7483,12 +7507,14 @@ fn should_repaint_idle_terminal(
last_terminal_frame_at: Instant, last_terminal_frame_at: Instant,
last_attempt_at: Instant, last_attempt_at: Instant,
status_tick_gap: Duration, status_tick_gap: Duration,
wake_repaint_retry_until: Option<Instant>,
now: Instant, now: Instant,
) -> bool { ) -> bool {
let sleep_wake_gap = status_tick_gap >= LOCAL_SLEEP_REPAINT_AFTER; let sleep_wake_gap = status_tick_gap >= LOCAL_SLEEP_REPAINT_AFTER;
let wake_retry_active = wake_repaint_retry_until.is_some_and(|deadline| now < deadline);
let stale_alternate_screen = alternate_screen let stale_alternate_screen = alternate_screen
&& now.duration_since(last_terminal_frame_at) >= ALT_SCREEN_IDLE_REPAINT_AFTER; && now.duration_since(last_terminal_frame_at) >= ALT_SCREEN_IDLE_REPAINT_AFTER;
if sleep_wake_gap { if sleep_wake_gap || wake_retry_active {
return now.duration_since(last_attempt_at) >= LOCAL_SLEEP_REPAINT_RETRY_AFTER; return now.duration_since(last_attempt_at) >= LOCAL_SLEEP_REPAINT_RETRY_AFTER;
} }
stale_alternate_screen && now.duration_since(last_attempt_at) >= ALT_SCREEN_IDLE_REPAINT_AFTER stale_alternate_screen && now.duration_since(last_attempt_at) >= ALT_SCREEN_IDLE_REPAINT_AFTER
@@ -9441,33 +9467,34 @@ const TERMINAL_CLEANUP: &[u8] = concat!(
mod tests { mod tests {
use super::{ use super::{
ALT_SCREEN_IDLE_REPAINT_AFTER, CachedCredential, DisconnectStatus, DynamicForward, ALT_SCREEN_IDLE_REPAINT_AFTER, CachedCredential, DisconnectStatus, DynamicForward,
FRAME_GAP_RESYNC_AFTER_MS, FrameBuffer, LOCAL_SLEEP_REPAINT_AFTER, LocalForward, FRAME_GAP_RESYNC_AFTER_MS, FrameBuffer, LOCAL_SLEEP_REPAINT_AFTER,
MAX_PENDING_USER_INPUT_BYTES, NativeIdentityContext, POST_RECONNECT_STALE_INPUT_GRACE, LOCAL_SLEEP_REPAINT_RETRY_WINDOW, LocalForward, MAX_PENDING_USER_INPUT_BYTES,
POST_SUBMIT_ALL_INPUT_HOLD, PendingStreamOpen, PredictMode, Predictor, NativeIdentityContext, POST_RECONNECT_STALE_INPUT_GRACE, POST_SUBMIT_ALL_INPUT_HOLD,
RESTART_STATUS_SCRIPT, RemoteForward, STARTUP_INPUT_HOLD, SshConfig, SshPathTokenContext, PendingStreamOpen, PredictMode, Predictor, RESTART_STATUS_SCRIPT, RemoteForward,
StartupGateMode, StatusAction, UpdateOptions, UpdateRole, auth_allows, cache_key, STARTUP_INPUT_HOLD, SshConfig, SshPathTokenContext, StartupGateMode, StatusAction,
cache_server_prefix, cleanup_stream_state, clear_cached_credentials, UpdateOptions, UpdateRole, auth_allows, cache_key, cache_server_prefix,
ensure_tui_safe_status_overlay, expand_ssh_path_tokens, input_contains_focus_in, cleanup_stream_state, clear_cached_credentials, ensure_tui_safe_status_overlay,
input_matches_escape, is_local_status_target, is_resume_response_for_client, expand_ssh_path_tokens, input_contains_focus_in, input_matches_escape,
latest_release_download_url, load_first_native_identity_with_prompt, is_local_status_target, is_resume_response_for_client, latest_release_download_url,
native_proxy_udp_warning, newest_client_trace_path_from, parse_dynamic_forward, load_first_native_identity_with_prompt, native_proxy_udp_warning,
parse_escape_key, parse_local_forward, parse_remote_forward, parse_ssh_config, newest_client_trace_path_from, parse_dynamic_forward, parse_escape_key,
parse_trace_line, parse_trace_options, parse_trace_report_options, parse_trace_summary, parse_local_forward, parse_remote_forward, parse_ssh_config, parse_trace_line,
parse_update_options, post_submit_hold_duration, queue_pending_user_input, parse_trace_options, parse_trace_report_options, parse_trace_summary, parse_update_options,
queue_stale_pending_user_input, raw_contains_host_table, recv_response_until, post_submit_hold_duration, queue_pending_user_input, queue_stale_pending_user_input,
refresh_live_addr, release_tag_download_url, release_tag_from_effective_url, raw_contains_host_table, recv_response_until, refresh_live_addr, release_tag_download_url,
release_version_from_tag, render_status_clear, render_status_overlay, requested_env, release_tag_from_effective_url, release_version_from_tag, render_status_clear,
resolved_startup_command, retire_stream_state, retransmit_stream_opens, render_status_overlay, requested_env, resolved_startup_command, retire_stream_state,
rewrite_forward_command, sanitize_trace_name, selected_predict_mode, selected_udp_host, retransmit_stream_opens, rewrite_forward_command, sanitize_trace_name,
server_version_mismatch, should_flush_terminal_input_after_contact, selected_predict_mode, selected_udp_host, server_version_mismatch,
should_health_log_client_start, should_hold_during_startup_gate, should_flush_terminal_input_after_contact, should_health_log_client_start,
should_hold_post_submit_input, should_reconnect_before_input_for_local_sleep, should_hold_during_startup_gate, should_hold_post_submit_input,
should_repaint_idle_terminal, should_strip_unowned_terminal_reports, should_reconnect_before_input_for_local_sleep, should_repaint_idle_terminal,
split_after_command_submit, split_trace_tokens, ssh_command_target, ssh_config_uses_proxy, should_strip_unowned_terminal_reports, split_after_command_submit, split_trace_tokens,
ssh_destination_host, ssh_username, ssh_with_user, startup_command, status_ssh_target, ssh_command_target, ssh_config_uses_proxy, ssh_destination_host, ssh_username,
strip_stale_mouse_reports, strip_terminal_focus_reports, strip_unowned_terminal_reports, ssh_with_user, startup_command, status_ssh_target, strip_stale_mouse_reports,
summarize_trace_file, summarize_trace_file_with_mode, terminal_private_mode_transition, strip_terminal_focus_reports, strip_unowned_terminal_reports, summarize_trace_file,
toml_bare_key_or_quoted, top_trace_events, trace_report_warnings, unix_update_script, summarize_trace_file_with_mode, terminal_private_mode_transition, toml_bare_key_or_quoted,
top_trace_events, trace_report_warnings, unix_update_script,
update_binary_version_for_installer, update_installer_url, update_version_status, update_binary_version_for_installer, update_installer_url, update_version_status,
upsert_managed_block, valid_forward_host, vscode_safe_alias, windows_update_script, upsert_managed_block, valid_forward_host, vscode_safe_alias, windows_update_script,
}; };
@@ -11692,6 +11719,7 @@ mod tests {
stale, stale,
stale, stale,
Duration::from_secs(1), Duration::from_secs(1),
None,
now now
)); ));
assert!(should_repaint_idle_terminal( assert!(should_repaint_idle_terminal(
@@ -11699,6 +11727,7 @@ mod tests {
recent, recent,
stale, stale,
LOCAL_SLEEP_REPAINT_AFTER + Duration::from_secs(1), LOCAL_SLEEP_REPAINT_AFTER + Duration::from_secs(1),
None,
now now
)); ));
assert!(should_repaint_idle_terminal( assert!(should_repaint_idle_terminal(
@@ -11706,6 +11735,7 @@ mod tests {
recent, recent,
recent, recent,
LOCAL_SLEEP_REPAINT_AFTER + Duration::from_secs(1), LOCAL_SLEEP_REPAINT_AFTER + Duration::from_secs(1),
Some(now + LOCAL_SLEEP_REPAINT_RETRY_WINDOW),
now now
)); ));
assert!(!should_repaint_idle_terminal( assert!(!should_repaint_idle_terminal(
@@ -11713,6 +11743,7 @@ mod tests {
recent, recent,
just_attempted, just_attempted,
LOCAL_SLEEP_REPAINT_AFTER + Duration::from_secs(1), LOCAL_SLEEP_REPAINT_AFTER + Duration::from_secs(1),
Some(now + LOCAL_SLEEP_REPAINT_RETRY_WINDOW),
now now
)); ));
assert!(!should_repaint_idle_terminal( assert!(!should_repaint_idle_terminal(
@@ -11720,6 +11751,7 @@ mod tests {
stale, stale,
stale, stale,
Duration::from_secs(1), Duration::from_secs(1),
None,
now now
)); ));
assert!(!should_repaint_idle_terminal( assert!(!should_repaint_idle_terminal(
@@ -11727,6 +11759,7 @@ mod tests {
recent, recent,
stale, stale,
Duration::from_secs(1), Duration::from_secs(1),
None,
now now
)); ));
assert!(should_repaint_idle_terminal( assert!(should_repaint_idle_terminal(
@@ -11734,6 +11767,31 @@ mod tests {
stale, stale,
recent, recent,
LOCAL_SLEEP_REPAINT_AFTER + Duration::from_secs(1), LOCAL_SLEEP_REPAINT_AFTER + Duration::from_secs(1),
None,
now
));
assert!(should_repaint_idle_terminal(
false,
recent,
stale,
Duration::from_secs(1),
Some(now + LOCAL_SLEEP_REPAINT_RETRY_WINDOW),
now
));
assert!(!should_repaint_idle_terminal(
false,
recent,
just_attempted,
Duration::from_secs(1),
Some(now + LOCAL_SLEEP_REPAINT_RETRY_WINDOW),
now
));
assert!(!should_repaint_idle_terminal(
false,
recent,
stale,
Duration::from_secs(1),
Some(now - Duration::from_millis(1)),
now now
)); ));
} }