collab-net: wait longer for one-shot server replies

Publishing uploads a whole file, and a large project on a slow link takes
the server several minutes to receive and answer. The one-shot request
connections (hosting, the publish upload, a library, a plugin directory)
waited the session idle timeout (45s), so a large publish timed out with
"no answer from the server" before the server finished. Add a dedicated
request_reply_timeout (5m) for these connections, keeping the session idle
timeout unchanged.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Mojtaba Jafarzadeh
2026-10-24 22:35:05 +03:30
co-authored by Claude
parent 28de91d9ae
commit 2a08ad3244
2 changed files with 25 additions and 3 deletions
+20
View File
@@ -24,6 +24,12 @@ pub struct NetLimits {
pub hello_timeout: Duration,
/// A connection with no traffic for this long is dead.
pub idle_timeout: Duration,
/// How long a one-shot request connection (hosting, a library, a plugin directory — and, most
/// importantly, publishing a file) waits for the server's reply before giving up. Publishing
/// uploads a whole file and a large one on a slow link takes the server a while to receive and
/// answer, so this is longer than `idle_timeout` (a large project on a slow connection can
/// easily need several minutes).
pub request_reply_timeout: Duration,
/// A quiet client pings this often (keeps it under `idle_timeout`).
pub keepalive: Duration,
/// Messages queued for one client before it counts as stuck and is disconnected.
@@ -68,6 +74,7 @@ impl Default for NetLimits {
burst: 400.0,
hello_timeout: Duration::from_secs(10),
idle_timeout: Duration::from_secs(45),
request_reply_timeout: Duration::from_secs(300),
keepalive: Duration::from_secs(15),
queue: 1024,
write_timeout: Duration::from_secs(10),
@@ -85,3 +92,16 @@ impl Default for NetLimits {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn request_reply_wait_is_longer_than_idle() {
let d = NetLimits::default();
// Publishing uploads a whole file; a slow link must not cut it off at the session idle
// timeout. Keep the reply wait comfortably above it.
assert!(d.request_reply_timeout > d.idle_timeout);
}
}
+5 -3
View File
@@ -196,13 +196,15 @@ pub fn plugins(server: &Endpoint, key: &str, req: PluginReq, limits: &NetLimits,
Ok(r)
}
/// A WebSocket to `server` that waits for answers up to the idle timeout (a large upload takes the
/// server a while to keep).
/// A WebSocket to `server` that waits for answers up to the request-reply timeout (a large upload
/// takes the server a while to keep).
fn connect(server: &Endpoint, limits: &NetLimits, timeout: Duration) -> Result<Ws, String> {
let (stream, _, _) = open(server, timeout, limits)?;
let (ws, _) = tungstenite::client::client_with_config(server.url(), stream, Some(config(limits.max_server_message)))
.map_err(|e| format!("{} doesn't answer as a FigCraft server: {e}", server.display()))?;
ws.get_ref().set_read_timeout(Some(limits.idle_timeout)).map_err(|e| e.to_string())?;
// A publish uploads a whole file; a large project on a slow link can take several minutes to
// receive and answer. The wait uses `request_reply_timeout`, not the session `idle_timeout`.
ws.get_ref().set_read_timeout(Some(limits.request_reply_timeout)).map_err(|e| e.to_string())?;
Ok(ws)
}