feat(viewer): time out the initial connect instead of hanging forever
endpoint.connect() has no built-in deadline, so an offline host, a stale share code, or an unreachable relay left the viewer spinning silently with no feedback — surfacing in the GUI as a permanent "Connecting…" with no error. Wrap the connect in a 15s tokio::time::timeout (matching the host's online() cap) and race it against ctrl-c, bailing with an actionable message. The error reaches stderr, so the GUI's ChildProc stderr-tail path renders it on the viewer screen. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+34
-2
@@ -1,12 +1,19 @@
|
||||
use anyhow::{Context, Result};
|
||||
use anyhow::{Context, Result, bail};
|
||||
use iroh::Endpoint;
|
||||
use iroh::endpoint::presets;
|
||||
use iroh_tickets::endpoint::EndpointTicket;
|
||||
use std::time::Duration;
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
use crate::cli::ViewerOpts;
|
||||
use crate::common::{alpn::ALPN, output, signal};
|
||||
|
||||
/// Cap on the initial QUIC connect. `endpoint.connect()` has no built-in
|
||||
/// deadline, so an offline host / stale code / unreachable relay otherwise
|
||||
/// hangs forever with no feedback (the silent "connecting…" failure mode).
|
||||
/// Matches the host's 15s `online()` cap.
|
||||
const CONNECT_TIMEOUT: Duration = Duration::from_secs(15);
|
||||
|
||||
pub async fn run(ticket: EndpointTicket, opts: ViewerOpts) -> Result<()> {
|
||||
let cancel = signal::install_ctrl_c();
|
||||
|
||||
@@ -17,7 +24,32 @@ pub async fn run(ticket: EndpointTicket, opts: ViewerOpts) -> Result<()> {
|
||||
|
||||
let addr = ticket.endpoint_addr().clone();
|
||||
tracing::info!(remote = %addr.id, "connecting to host");
|
||||
let conn = endpoint.connect(addr, ALPN).await?;
|
||||
|
||||
// Bound the connect attempt and let ctrl-c abort it, so the viewer fails
|
||||
// loud (and the GUI surfaces the error) instead of spinning indefinitely.
|
||||
let conn = tokio::select! {
|
||||
_ = cancel.cancelled() => {
|
||||
tracing::info!("ctrl-c received before the connection was established");
|
||||
endpoint.close().await;
|
||||
return Ok(());
|
||||
}
|
||||
result = tokio::time::timeout(CONNECT_TIMEOUT, endpoint.connect(addr, ALPN)) => match result {
|
||||
Ok(Ok(conn)) => conn,
|
||||
Ok(Err(e)) => {
|
||||
endpoint.close().await;
|
||||
bail!("failed to connect to the host: {e:#}");
|
||||
}
|
||||
Err(_) => {
|
||||
endpoint.close().await;
|
||||
bail!(
|
||||
"couldn't reach the host within {}s — it may be offline, the share \
|
||||
code may be stale, or the relay may be unreachable. Check that the \
|
||||
host is running, then re-copy the code and try again.",
|
||||
CONNECT_TIMEOUT.as_secs()
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
let (quic_send, quic_recv) = conn.open_bi().await?;
|
||||
|
||||
let listener = TcpListener::bind(("127.0.0.1", opts.port)).await?;
|
||||
|
||||
Reference in New Issue
Block a user