From 03f9c2d3bd73bc280f1da88d160794a0a0124297 Mon Sep 17 00:00:00 2001 From: Jean Date: Sat, 18 Jul 2026 23:20:34 +0000 Subject: [PATCH] dictserv: retry a transient lookup once, and end the read on a no-match/error status instead of blocking for a 250 that never comes --- src/dict.rs | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/src/dict.rs b/src/dict.rs index 611c826..c179e16 100644 --- a/src/dict.rs +++ b/src/dict.rs @@ -7,22 +7,28 @@ use std::time::Duration; use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; use tokio::net::TcpStream; -const TIMEOUT: Duration = Duration::from_secs(6); +const ATTEMPT_TIMEOUT: Duration = Duration::from_secs(6); +const ATTEMPTS: usize = 2; // one retry — a database that's briefly slow under load often answers next try const MAX_LEN: usize = 400; // keep a spoken reply to about one IRC line // Look `word` up in `database` on `server`. Never errors out to the caller: on a // timeout, connection failure, or no match it returns a short human message, so -// the bot always has something safe to say. +// the bot always has something safe to say. A transient failure (timeout or +// connection error) is retried once, since dict.org databases can be momentarily +// slow and then recover; a clean "no match" is definitive and returns at once. pub async fn lookup(server: &str, database: &str, word: &str) -> String { let word = sanitize(word); if word.is_empty() { return "nothing to look up.".to_string(); } - match tokio::time::timeout(TIMEOUT, exchange(server, database, &word)).await { - Ok(Ok(Some(def))) => def, - Ok(Ok(None)) => format!("no definition found for \x02{word}\x02."), - Ok(Err(_)) | Err(_) => "the dictionary is unavailable right now.".to_string(), + for _ in 0..ATTEMPTS { + match tokio::time::timeout(ATTEMPT_TIMEOUT, exchange(server, database, &word)).await { + Ok(Ok(Some(def))) => return def, + Ok(Ok(None)) => return format!("no definition found for \x02{word}\x02."), + Ok(Err(_)) | Err(_) => continue, // transient — try again, then give up + } } + "the dictionary is unavailable right now.".to_string() } // One request/response round trip. Returns the first definition's text, or None @@ -36,15 +42,24 @@ async fn exchange(server: &str, database: &str, word: &str) -> std::io::Result MAX_LEN * 8 { - break; // guard against a runaway server + if stop || raw.len() > MAX_LEN * 8 { + break; } } let _ = wr.write_all(b"QUIT\r\n").await;