Time out on a stalled transfer, not on a slow one

The HTTP client had a 60-second *total* request timeout. That is not a hang
detector; it is a floor on link speed. A face shard runs to 25 MB, so it
demanded a sustained 425 KB/s or the transfer failed — and having failed it was
retried on the next pass and failed again, for ever.

A tablet on ordinary wifi could therefore never finish taking in a library's
faces, and nothing said why: each attempt looked like a network blip rather than
an arithmetic impossibility. The catalog snapshot is 36 MB and has the same
problem.

`read_timeout` fires when no bytes arrive for the period, which is the condition
actually worth failing on. A slow transfer that is still moving now finishes,
however long it takes; a connection that has genuinely died is still caught in a
minute. The connect timeout is unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-28 23:08:16 +02:00
co-authored by Claude Opus 5
parent d70fe9da8d
commit d2af6a3981
+16 -4
View File
@@ -637,11 +637,23 @@ pub fn http_client(user_agent: &str) -> Result<reqwest::Client, RemoteError> {
// webpki-roots set is still installed alongside. // webpki-roots set is still installed alongside.
.tls_certs_only(extra_roots()) .tls_certs_only(extra_roots())
// A request that hangs forever is indistinguishable from a worker that // A request that hangs forever is indistinguishable from a worker that
// died, and cost a long time to tell apart once. Connect and total // died, and cost a long time to tell apart once. These turn that into
// timeouts turn that into an error the UI can show. Generous enough for // an error the UI can show.
// a slow phone on mobile data; the login poll has its own deadline.
.connect_timeout(std::time::Duration::from_secs(15)) .connect_timeout(std::time::Duration::from_secs(15))
.timeout(std::time::Duration::from_secs(60)) // **Inactivity, not duration.** This was a 60-second *total* timeout,
// which is not a hang detector at all — it is a floor on link speed.
// A face shard runs to 25 MB, so it demanded a sustained 425 KB/s or
// the transfer failed; and having failed it was retried on the next
// pass, and failed again, for ever. A tablet on ordinary wifi could
// therefore never finish adopting a library's faces, and nothing said
// why: each attempt looked like a network blip rather than an
// arithmetic impossibility.
//
// `read_timeout` fires when *no bytes arrive* for the given period,
// which is the condition actually worth failing on. A slow transfer
// that is still moving now finishes, however long it takes, while a
// connection that has genuinely died is still caught in a minute.
.read_timeout(std::time::Duration::from_secs(60))
.build() .build()
.map_err(|e| RemoteError::Network(e.to_string())) .map_err(|e| RemoteError::Network(e.to_string()))
} }