diff options
| author | DanConwayDev <DanConwayDev@protonmail.com> | 2025-07-15 15:03:11 +0100 |
|---|---|---|
| committer | DanConwayDev <DanConwayDev@protonmail.com> | 2025-07-15 15:03:33 +0100 |
| commit | 8cea674f58e806c22d2887cff5e6f76bc4dba0db (patch) | |
| tree | c912c086c3d9b6598e68bb5d66ecf928d28ad940 /src | |
| parent | 8d42d8c3e8e65ff1892effa2d1058d9f2422ce2b (diff) | |
feat(client): skip relays that just timeout
Prevent repeated attempts to connect to relays that have previously
failed due to connection timeouts within the current session,
improving overall performance and reliability.
Added a `relays_not_to_retry` map to the `Client` struct to store
relays to skip and their reasons. Implemented methods
`skip_relay_for_session` and `is_relay_skipped_for_session` to
manage this map.
Relays are added to the skip list upon connection timeouts. Subsequent
attempts to use these skipped relays during the same session will
immediately fail with the recorded reason, avoiding unnecessary network
calls. Progress bars are updated to reflect skipped relays and their
reasons.
Diffstat (limited to 'src')
| -rw-r--r-- | src/lib/client.rs | 130 |
1 files changed, 102 insertions, 28 deletions
diff --git a/src/lib/client.rs b/src/lib/client.rs index 3b613b6..445bf77 100644 --- a/src/lib/client.rs +++ b/src/lib/client.rs | |||
| @@ -15,11 +15,11 @@ use std::{ | |||
| 15 | fmt::{Display, Write}, | 15 | fmt::{Display, Write}, |
| 16 | fs::create_dir_all, | 16 | fs::create_dir_all, |
| 17 | path::Path, | 17 | path::Path, |
| 18 | sync::Arc, | 18 | sync::{Arc, RwLock}, |
| 19 | time::Duration, | 19 | time::Duration, |
| 20 | }; | 20 | }; |
| 21 | 21 | ||
| 22 | use anyhow::{Context, Result, bail}; | 22 | use anyhow::{Context, Result, anyhow, bail}; |
| 23 | use async_trait::async_trait; | 23 | use async_trait::async_trait; |
| 24 | use console::Style; | 24 | use console::Style; |
| 25 | use futures::{ | 25 | use futures::{ |
| @@ -60,6 +60,29 @@ pub struct Client { | |||
| 60 | more_fallback_relays: Vec<String>, | 60 | more_fallback_relays: Vec<String>, |
| 61 | blaster_relays: Vec<String>, | 61 | blaster_relays: Vec<String>, |
| 62 | fallback_signer_relays: Vec<String>, | 62 | fallback_signer_relays: Vec<String>, |
| 63 | relays_not_to_retry: Arc<RwLock<HashMap<RelayUrl, String>>>, | ||
| 64 | } | ||
| 65 | |||
| 66 | impl Client { | ||
| 67 | /// Marks a relay as skipped for the current session with a given reason. | ||
| 68 | /// This method encapsulates the write lock for the relays_not_to_retry map. | ||
| 69 | fn skip_relay_for_session(&self, relay_url: RelayUrl, reason: String) { | ||
| 70 | self.relays_not_to_retry | ||
| 71 | .write() | ||
| 72 | .unwrap() | ||
| 73 | .insert(relay_url, reason); | ||
| 74 | } | ||
| 75 | |||
| 76 | /// Checks if a relay should be skipped for the current session and returns | ||
| 77 | /// the reason if it is. This method encapsulates the read lock for the | ||
| 78 | /// relays_not_to_retry map. | ||
| 79 | fn is_relay_skipped_for_session(&self, relay_url: &RelayUrl) -> Option<String> { | ||
| 80 | self.relays_not_to_retry | ||
| 81 | .read() | ||
| 82 | .unwrap() | ||
| 83 | .get(relay_url) | ||
| 84 | .cloned() | ||
| 85 | } | ||
| 63 | } | 86 | } |
| 64 | 87 | ||
| 65 | #[cfg_attr(test, automock)] | 88 | #[cfg_attr(test, automock)] |
| @@ -127,6 +150,7 @@ impl Connect for Client { | |||
| 127 | more_fallback_relays: opts.more_fallback_relays, | 150 | more_fallback_relays: opts.more_fallback_relays, |
| 128 | blaster_relays: opts.blaster_relays, | 151 | blaster_relays: opts.blaster_relays, |
| 129 | fallback_signer_relays: opts.fallback_signer_relays, | 152 | fallback_signer_relays: opts.fallback_signer_relays, |
| 153 | relays_not_to_retry: Arc::new(RwLock::new(HashMap::new())), | ||
| 130 | } | 154 | } |
| 131 | } | 155 | } |
| 132 | 156 | ||
| @@ -135,6 +159,9 @@ impl Connect for Client { | |||
| 135 | } | 159 | } |
| 136 | 160 | ||
| 137 | async fn connect(&self, relay_url: &RelayUrl) -> Result<()> { | 161 | async fn connect(&self, relay_url: &RelayUrl) -> Result<()> { |
| 162 | if let Some(reason) = self.is_relay_skipped_for_session(relay_url) { | ||
| 163 | bail!("{reason}"); | ||
| 164 | } | ||
| 138 | self.client | 165 | self.client |
| 139 | .add_relay(relay_url) | 166 | .add_relay(relay_url) |
| 140 | .await | 167 | .await |
| @@ -150,6 +177,7 @@ impl Connect for Client { | |||
| 150 | } | 177 | } |
| 151 | 178 | ||
| 152 | if !relay.is_connected() { | 179 | if !relay.is_connected() { |
| 180 | self.skip_relay_for_session(relay_url.clone(), "connection timeout".to_string()); | ||
| 153 | bail!("connection timeout"); | 181 | bail!("connection timeout"); |
| 154 | } | 182 | } |
| 155 | Ok(()) | 183 | Ok(()) |
| @@ -244,21 +272,37 @@ impl Connect for Client { | |||
| 244 | } else { | 272 | } else { |
| 245 | None | 273 | None |
| 246 | }; | 274 | }; |
| 275 | fn update_progress_bar_with_error( | ||
| 276 | relay_url: &RelayUrl, | ||
| 277 | pb: Option<ProgressBar>, | ||
| 278 | error: &anyhow::Error, | ||
| 279 | ) { | ||
| 280 | if let Some(pb) = pb { | ||
| 281 | pb.set_style(pb_after_style(false)); | ||
| 282 | pb.set_prefix(format!("{: <11}{}", "error", relay_url)); | ||
| 283 | pb.finish_with_message( | ||
| 284 | console::style( | ||
| 285 | error.to_string().replace("relay pool error:", "error:"), | ||
| 286 | ) | ||
| 287 | .for_stderr() | ||
| 288 | .red() | ||
| 289 | .to_string(), | ||
| 290 | ); | ||
| 291 | } | ||
| 292 | } | ||
| 293 | if let Some(reason) = self.is_relay_skipped_for_session(relay.url()) { | ||
| 294 | update_progress_bar_with_error(relay.url(), pb, &anyhow!("{reason}")); | ||
| 295 | bail!("{reason}"); | ||
| 296 | } | ||
| 247 | #[allow(clippy::large_futures)] | 297 | #[allow(clippy::large_futures)] |
| 248 | match get_events_of(relay, filters, &pb).await { | 298 | match get_events_of(relay, filters, &pb).await { |
| 249 | Err(error) => { | 299 | Err(error) => { |
| 250 | if let Some(pb) = pb { | 300 | // Check error for timeout/connection issues and add to skip list |
| 251 | pb.set_style(pb_after_style(false)); | 301 | if error.to_string().contains("connection timeout") { |
| 252 | pb.set_prefix(format!("{: <11}{}", "error", relay.url())); | 302 | // Simple check, refine as needed |
| 253 | pb.finish_with_message( | 303 | self.skip_relay_for_session(relay.url().clone(), error.to_string()); |
| 254 | console::style( | ||
| 255 | error.to_string().replace("relay pool error:", "error:"), | ||
| 256 | ) | ||
| 257 | .for_stderr() | ||
| 258 | .red() | ||
| 259 | .to_string(), | ||
| 260 | ); | ||
| 261 | } | 304 | } |
| 305 | update_progress_bar_with_error(relay.url(), pb, &error); | ||
| 262 | Err(error) | 306 | Err(error) |
| 263 | } | 307 | } |
| 264 | Ok(res) => { | 308 | Ok(res) => { |
| @@ -391,25 +435,55 @@ impl Connect for Client { | |||
| 391 | } else { | 435 | } else { |
| 392 | None | 436 | None |
| 393 | }; | 437 | }; |
| 394 | 438 | // do here | |
| 439 | |||
| 440 | fn update_progress_bar_with_error( | ||
| 441 | relay_column_width: usize, | ||
| 442 | relay_url: &RelayUrl, | ||
| 443 | pb: Option<ProgressBar>, | ||
| 444 | error: &anyhow::Error, | ||
| 445 | ) { | ||
| 446 | if let Some(pb) = pb { | ||
| 447 | pb.set_style(pb_after_style(false)); | ||
| 448 | pb.set_prefix( | ||
| 449 | Style::new() | ||
| 450 | .color256(247) | ||
| 451 | .apply_to(format!("{: <relay_column_width$}", &relay_url)) | ||
| 452 | .to_string(), | ||
| 453 | ); | ||
| 454 | pb.finish_with_message( | ||
| 455 | console::style( | ||
| 456 | error.to_string().replace("relay pool error:", "error:"), | ||
| 457 | ) | ||
| 458 | .for_stderr() | ||
| 459 | .red() | ||
| 460 | .to_string(), | ||
| 461 | ); | ||
| 462 | } | ||
| 463 | } | ||
| 464 | if let Some(reason) = self.is_relay_skipped_for_session(&relay_url) { | ||
| 465 | update_progress_bar_with_error( | ||
| 466 | relay_column_width, | ||
| 467 | &relay_url, | ||
| 468 | pb, | ||
| 469 | &anyhow!("{reason}"), | ||
| 470 | ); | ||
| 471 | bail!("{reason}"); | ||
| 472 | } | ||
| 395 | #[allow(clippy::large_futures)] | 473 | #[allow(clippy::large_futures)] |
| 396 | match self.fetch_all_from_relay(git_repo_path, request, &pb).await { | 474 | match self.fetch_all_from_relay(git_repo_path, request, &pb).await { |
| 397 | Err(error) => { | 475 | Err(error) => { |
| 398 | if let Some(pb) = pb { | 476 | // Check error for timeout/connection issues and add to skip list |
| 399 | pb.set_style(pb_after_style(false)); | 477 | if error.to_string().contains("connection timeout") { |
| 400 | pb.set_prefix( | 478 | // Simple check, refine as needed |
| 401 | dim.apply_to(format!("{: <relay_column_width$}", &relay_url)) | 479 | self.skip_relay_for_session(relay_url.clone(), error.to_string()); |
| 402 | .to_string(), | ||
| 403 | ); | ||
| 404 | pb.finish_with_message( | ||
| 405 | console::style( | ||
| 406 | error.to_string().replace("relay pool error:", "error:"), | ||
| 407 | ) | ||
| 408 | .for_stderr() | ||
| 409 | .red() | ||
| 410 | .to_string(), | ||
| 411 | ); | ||
| 412 | } | 480 | } |
| 481 | update_progress_bar_with_error( | ||
| 482 | relay_column_width, | ||
| 483 | &relay_url, | ||
| 484 | pb, | ||
| 485 | &error, | ||
| 486 | ); | ||
| 413 | Err(error) | 487 | Err(error) |
| 414 | } | 488 | } |
| 415 | Ok(res) => Ok(res), | 489 | Ok(res) => Ok(res), |