Tiltify API query backoff (#6447)

* Tiltify API querying backoff

* fix backoff

* fix clippy

* fix2
This commit is contained in:
aecsocket
2026-06-19 20:02:22 +01:00
committed by GitHub
parent 87eec7741b
commit ac5daad280
+57 -24
View File
@@ -1,6 +1,6 @@
use std::time::{Duration, Instant};
use eyre::eyre;
use eyre::{Result, eyre};
use serde::Deserialize;
use serde_json::json;
use tokio::sync::Mutex;
@@ -13,7 +13,24 @@ use crate::{
#[derive(Debug)]
pub struct TiltifyClient {
http: HttpClient,
token: Mutex<Option<TiltifyAccessToken>>,
state: Mutex<TiltifyState>,
}
#[derive(Debug)]
struct TiltifyState {
token: Option<TiltifyAccessToken>,
rate_limited_until: Instant,
rate_limit_backoff: Duration,
}
impl TiltifyState {
fn set_fetch_backoff(&mut self) {
self.rate_limited_until = Instant::now() + self.rate_limit_backoff;
self.rate_limit_backoff = self
.rate_limit_backoff
.saturating_mul(2)
.min(TILTIFY_MAX_RATE_LIMIT_BACKOFF);
}
}
#[derive(Debug)]
@@ -28,38 +45,34 @@ struct TiltifyTokenResponse {
expires_in: u64,
}
const TILTIFY_INITIAL_RATE_LIMIT_BACKOFF: Duration = Duration::from_secs(60);
const TILTIFY_MAX_RATE_LIMIT_BACKOFF: Duration = Duration::from_secs(15 * 60);
impl TiltifyClient {
pub fn new(http: HttpClient) -> Self {
Self {
http,
token: Mutex::new(None),
state: Mutex::new(TiltifyState {
token: None,
rate_limited_until: Instant::now(),
rate_limit_backoff: TILTIFY_INITIAL_RATE_LIMIT_BACKOFF,
}),
}
}
pub async fn access_token(&self) -> eyre::Result<String> {
let mut token = self.token.lock().await;
pub async fn access_token(&self) -> Result<String> {
let mut state = self.state.lock().await;
if let Some(token) = token.as_ref()
if let Some(token) = state.token.as_ref()
&& token.expires_at > Instant::now()
{
return Ok(token.access_token.clone());
}
let response = self.fetch_access_token().await?;
let expires_at = Instant::now()
+ Duration::from_secs(response.expires_in)
.saturating_sub(Duration::from_secs(60));
let access_token = response.access_token;
if state.rate_limited_until > Instant::now() {
return Err(eyre!("waiting for rate limit to reset"));
}
*token = Some(TiltifyAccessToken {
access_token: access_token.clone(),
expires_at,
});
Ok(access_token)
}
async fn fetch_access_token(&self) -> eyre::Result<TiltifyTokenResponse> {
if ENV.TILTIFY_CLIENT_ID.is_empty()
|| ENV.TILTIFY_CLIENT_SECRET.is_empty()
{
@@ -79,13 +92,33 @@ impl TiltifyClient {
}))
.send()
.await
.wrap_err("fetching OAuth token")?
.error_for_status()
.wrap_err("fetching OAuth token")?
.inspect_err(|_| state.set_fetch_backoff())
.wrap_err("fetching OAuth token")?;
let response = match response.error_for_status() {
Ok(response) => response,
Err(error) => {
state.set_fetch_backoff();
return Err(error).wrap_err("fetching OAuth token");
}
};
let response = response
.json::<TiltifyTokenResponse>()
.await
.wrap_err("parsing OAuth token response")?;
Ok(response)
let expires_at = Instant::now()
+ Duration::from_secs(response.expires_in)
.saturating_sub(Duration::from_secs(60));
let access_token = response.access_token;
state.token = Some(TiltifyAccessToken {
access_token: access_token.clone(),
expires_at,
});
state.rate_limit_backoff = TILTIFY_INITIAL_RATE_LIMIT_BACKOFF;
Ok(access_token)
}
}