diff --git a/Cargo.lock b/Cargo.lock index 6709a6a..65ac534 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -436,7 +436,7 @@ dependencies = [ [[package]] name = "dosh" -version = "1.0.0-rc38" +version = "1.0.0-rc39" dependencies = [ "anyhow", "base64", diff --git a/Cargo.toml b/Cargo.toml index 1f9f5ab..7983d3f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dosh" -version = "1.0.0-rc38" +version = "1.0.0-rc39" edition = "2024" license = "MIT" diff --git a/src/bin/dosh-client.rs b/src/bin/dosh-client.rs index dc7371a..a8e9722 100644 --- a/src/bin/dosh-client.rs +++ b/src/bin/dosh-client.rs @@ -828,6 +828,9 @@ struct TraceReport { sent_input_events: usize, pty_write_events: usize, reconnect_events: usize, + reconnect_none_events: usize, + reconnect_live_fail_events: usize, + reconnect_ticket_fail_events: usize, unknown_resume_events: usize, ticket_attach_start_events: usize, ticket_attach_ok_events: usize, @@ -1345,6 +1348,15 @@ fn summarize_trace_file_with_mode( if event.contains("reconnect") || event.contains("resume") { report.reconnect_events += 1; } + if event == "client.reconnect_none" { + report.reconnect_none_events += 1; + } + if event == "client.reconnect_live_fail" { + report.reconnect_live_fail_events += 1; + } + if event == "client.reconnect_ticket_fail" { + report.reconnect_ticket_fail_events += 1; + } if event == "server.resume_unknown_client" { report.unknown_resume_events += 1; } @@ -1413,8 +1425,11 @@ fn print_trace_report(label: &str, report: &TraceReport) { report.hex_events ); println!( - " reconnect: events={} unknown_resume={} ticket_attach={}/{}/{} roam={}", + " reconnect: events={} none={} live_fail={} ticket_fail={} unknown_resume={} ticket_attach={}/{}/{} roam={}", report.reconnect_events, + report.reconnect_none_events, + report.reconnect_live_fail_events, + report.reconnect_ticket_fail_events, report.unknown_resume_events, report.ticket_attach_start_events, report.ticket_attach_ok_events, @@ -1452,6 +1467,12 @@ fn trace_report_warnings(report: &TraceReport) -> Vec { report.ticket_attach_reject_events )); } + if report.reconnect_none_events > 0 { + warnings.push(format!( + "{} reconnect attempt(s) exhausted live resume and ticket attach", + report.reconnect_none_events + )); + } if report.server_mouseish_pty_write_events > 0 { warnings.push(format!( "{} mouse-like input event(s) reached the server PTY", @@ -7106,32 +7127,46 @@ async fn reconnect( ("last_rendered_seq", cred.last_rendered_seq.to_string()), ], ); - if let Ok((frame, next)) = try_live_resume(socket, cred, send_seq, size.0, size.1).await { - *cred = next; - frame_buffer.clear(); - predictor.reset(); - cred.last_rendered_seq = frame.output_seq; - send_ack( - socket, - resolve_addr(&cred.udp_host, cred.udp_port)?, - cred, - send_seq, - ) - .await?; - dosh::trace::event( - "client.reconnect_live_ok", - &[ - ("output_seq", frame.output_seq.to_string()), - ("bytes", frame.bytes.len().to_string()), - ], - ); - return Ok(Some(frame)); + match try_live_resume(socket, cred, send_seq, size.0, size.1).await { + Ok((frame, next)) => { + *cred = next; + frame_buffer.clear(); + predictor.reset(); + cred.last_rendered_seq = frame.output_seq; + send_ack( + socket, + resolve_addr(&cred.udp_host, cred.udp_port)?, + cred, + send_seq, + ) + .await?; + dosh::trace::event( + "client.reconnect_live_ok", + &[ + ("output_seq", frame.output_seq.to_string()), + ("bytes", frame.bytes.len().to_string()), + ], + ); + return Ok(Some(frame)); + } + Err(err) => { + dosh::trace::event( + "client.reconnect_live_fail", + &[("reason", format!("{err:#}"))], + ); + } } - let Ok((frame, next)) = try_ticket_attach(socket, cred, size.0, size.1, Vec::new()).await - else { - dosh::trace::event("client.reconnect_none", &[]); - return Ok(None); + let (frame, next) = match try_ticket_attach(socket, cred, size.0, size.1, Vec::new()).await { + Ok(value) => value, + Err(err) => { + dosh::trace::event( + "client.reconnect_ticket_fail", + &[("reason", format!("{err:#}"))], + ); + dosh::trace::event("client.reconnect_none", &[]); + return Ok(None); + } }; *cred = next; *send_seq = 2; @@ -10832,6 +10867,32 @@ mod tests { ); } + #[test] + fn trace_report_warns_when_reconnect_exhausts_all_paths() { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("client.log"); + fs::write( + &path, + concat!( + "ts_ms=10 pid=1 event=client.start version=1.0.0\n", + "ts_ms=11 pid=1 event=client.reconnect_start session=term mode=read-write last_rendered_seq=9\n", + "ts_ms=12 pid=1 event=client.reconnect_live_fail reason='live resume timed out'\n", + "ts_ms=13 pid=1 event=client.reconnect_ticket_fail reason='ticket attach timed out'\n", + "ts_ms=14 pid=1 event=client.reconnect_none\n", + ), + ) + .unwrap(); + + let report = summarize_trace_file(&path, 10).unwrap(); + let warnings = trace_report_warnings(&report); + assert_eq!(report.reconnect_none_events, 1); + assert_eq!(report.reconnect_live_fail_events, 1); + assert_eq!(report.reconnect_ticket_fail_events, 1); + assert!(warnings.iter().any(|warning| { + warning == "1 reconnect attempt(s) exhausted live resume and ticket attach" + })); + } + #[test] fn update_installer_uses_platform_native_script() { assert_eq!(