mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 10:18:54 +00:00
fix(gateway): stop reconnecting on client errors (#2464)
Co-authored-by: Jamil <jamilbk@users.noreply.github.com>
This commit is contained in:
@@ -10,7 +10,10 @@ use futures::{future, TryFutureExt};
|
||||
use phoenix_channel::SecureUrl;
|
||||
use secrecy::{Secret, SecretString};
|
||||
use std::convert::Infallible;
|
||||
use std::pin::pin;
|
||||
use std::sync::Arc;
|
||||
use tokio::signal::ctrl_c;
|
||||
use tokio_tungstenite::tungstenite;
|
||||
use tracing_subscriber::layer;
|
||||
use url::Url;
|
||||
|
||||
@@ -30,17 +33,20 @@ async fn main() -> Result<()> {
|
||||
)?;
|
||||
let tunnel = Arc::new(Tunnel::new(private_key, CallbackHandler).await?);
|
||||
|
||||
tokio::spawn(backoff::future::retry_notify(
|
||||
let task = pin!(backoff::future::retry_notify(
|
||||
ExponentialBackoffBuilder::default()
|
||||
.with_max_elapsed_time(None)
|
||||
.build(),
|
||||
move || run(tunnel.clone(), connect_url.clone()).map_err(backoff::Error::transient),
|
||||
move || run(tunnel.clone(), connect_url.clone()).map_err(to_backoff),
|
||||
|error, t| {
|
||||
tracing::warn!(retry_in = ?t, "Error connecting to portal: {error:#}");
|
||||
},
|
||||
));
|
||||
let ctrl_c = pin!(ctrl_c().map_err(anyhow::Error::new));
|
||||
|
||||
tokio::signal::ctrl_c().await?;
|
||||
future::try_select(task, ctrl_c)
|
||||
.await
|
||||
.map_err(|e| e.factor_first().0)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -64,7 +70,19 @@ async fn run(
|
||||
|
||||
let mut eventloop = Eventloop::new(tunnel, portal);
|
||||
|
||||
future::poll_fn(|cx| eventloop.poll(cx)).await
|
||||
future::poll_fn(|cx| eventloop.poll(cx))
|
||||
.await
|
||||
.context("Eventloop failed")
|
||||
}
|
||||
|
||||
/// Maps our [`anyhow::Error`] to either a permanent or transient [`backoff`] error.
|
||||
fn to_backoff(e: anyhow::Error) -> backoff::Error<anyhow::Error> {
|
||||
// As per HTTP spec, retrying client-errors without modifying the request is pointless. Thus we abort the backoff.
|
||||
if e.chain().any(is_client_error) {
|
||||
return backoff::Error::permanent(e);
|
||||
}
|
||||
|
||||
backoff::Error::transient(e)
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -80,3 +98,32 @@ struct Cli {
|
||||
#[command(flatten)]
|
||||
common: CommonArgs,
|
||||
}
|
||||
|
||||
/// Checks whether the given [`std::error::Error`] is in-fact an HTTP error with a 4xx status code.
|
||||
fn is_client_error(e: &(dyn std::error::Error + 'static)) -> bool {
|
||||
let Some(tungstenite::Error::Http(r)) = e.downcast_ref() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
r.status().is_client_error()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn backoff_permanent_on_client_error() {
|
||||
let error =
|
||||
anyhow::Error::new(phoenix_channel::Error::WebSocket(tungstenite::Error::Http(
|
||||
tungstenite::http::Response::builder()
|
||||
.status(400)
|
||||
.body(None)
|
||||
.unwrap(),
|
||||
)));
|
||||
|
||||
let backoff = to_backoff(error);
|
||||
|
||||
assert!(matches!(backoff, backoff::Error::Permanent(_)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ pub struct UnexpectedEventDuringInit(String);
|
||||
pub enum Error {
|
||||
#[error("provided URI is missing a host")]
|
||||
MissingHost,
|
||||
#[error(transparent)]
|
||||
#[error("websocket failed")]
|
||||
WebSocket(#[from] tokio_tungstenite::tungstenite::Error),
|
||||
#[error("failed to serialize message")]
|
||||
Serde(#[from] serde_json::Error),
|
||||
|
||||
Reference in New Issue
Block a user