feat(trello): support exponential backoff with jitter
This commit is contained in:
parent
0e123898db
commit
4cdfdaec6f
4 changed files with 52 additions and 19 deletions
|
@ -18,6 +18,7 @@ inquire = "0.7"
|
||||||
kameo = "0.13"
|
kameo = "0.13"
|
||||||
# kxio = {path = "../kxio/"}
|
# kxio = {path = "../kxio/"}
|
||||||
kxio = "4.0"
|
kxio = "4.0"
|
||||||
|
rand = "0.8"
|
||||||
reqwest = { version = "0.12" , features = ["multipart", "stream"]}
|
reqwest = { version = "0.12" , features = ["multipart", "stream"]}
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
|
24
src/macros/backoff.rs
Normal file
24
src/macros/backoff.rs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! with_exponential_backoff {
|
||||||
|
($ctx:expr, $operation:expr) => {{
|
||||||
|
let mut backoff_secs = 1;
|
||||||
|
loop {
|
||||||
|
match $operation {
|
||||||
|
Err(kxio::net::Error::Reqwest(e))
|
||||||
|
if e.status() == Some(kxio::net::StatusCode::TOO_MANY_REQUESTS) =>
|
||||||
|
{
|
||||||
|
backoff_secs *= 2;
|
||||||
|
let jitter = rand::random::<u64>() % 10;
|
||||||
|
let backoff_secs = 60.min(backoff_secs + jitter);
|
||||||
|
$crate::p!(
|
||||||
|
$ctx.prt,
|
||||||
|
">> Too many requests, backing off for {}s",
|
||||||
|
backoff_secs
|
||||||
|
);
|
||||||
|
tokio::time::sleep(tokio::time::Duration::from_secs(backoff_secs)).await;
|
||||||
|
}
|
||||||
|
result => break result,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
//
|
//
|
||||||
mod actor;
|
mod actor;
|
||||||
|
mod backoff;
|
||||||
mod newtype;
|
mod newtype;
|
||||||
mod print;
|
mod print;
|
||||||
mod send;
|
mod send;
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
//
|
//
|
||||||
use std::collections::HashMap;
|
use std::{collections::HashMap, path::PathBuf};
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use color_eyre::eyre::Context;
|
use color_eyre::eyre::Context;
|
||||||
use kxio::net::{Net, ReqBuilder};
|
use kxio::net::{Net, ReqBuilder};
|
||||||
|
@ -11,15 +10,15 @@ use crate::{
|
||||||
trello::model::{
|
trello::model::{
|
||||||
attachment::{TrelloAttachment, TrelloAttachmentId},
|
attachment::{TrelloAttachment, TrelloAttachmentId},
|
||||||
board::{TrelloBoard, TrelloBoardId},
|
board::{TrelloBoard, TrelloBoardId},
|
||||||
card::TrelloCardId,
|
card::{TrelloCardId, TrelloLongCard, TrelloShortCard},
|
||||||
card::{TrelloLongCard, TrelloShortCard},
|
|
||||||
list::TrelloListId,
|
list::TrelloListId,
|
||||||
},
|
},
|
||||||
FullCtx,
|
with_exponential_backoff, FullCtx,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) struct TrelloClient<'ctx> {
|
pub(crate) struct TrelloClient<'ctx> {
|
||||||
ctx: &'ctx FullCtx,
|
ctx: &'ctx FullCtx,
|
||||||
|
// 300 requests per 10 seconds for each API key and 100 requests per 10 second interval for each token
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'ctx> TrelloClient<'ctx> {
|
impl<'ctx> TrelloClient<'ctx> {
|
||||||
|
@ -55,11 +54,15 @@ impl<'ctx> TrelloClient<'ctx> {
|
||||||
url: impl Into<String>,
|
url: impl Into<String>,
|
||||||
custom: fn(&Net, String) -> ReqBuilder,
|
custom: fn(&Net, String) -> ReqBuilder,
|
||||||
) -> APIResult<T> {
|
) -> APIResult<T> {
|
||||||
|
let url = url.into();
|
||||||
APIResult::new(
|
APIResult::new(
|
||||||
custom(&self.ctx.net, self.url(url))
|
with_exponential_backoff!(
|
||||||
.headers(self.common_headers())
|
&self.ctx,
|
||||||
.send()
|
custom(&self.ctx.net, self.url(url.clone()))
|
||||||
.await,
|
.headers(self.common_headers())
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
),
|
||||||
&self.ctx.prt,
|
&self.ctx.prt,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -77,16 +80,20 @@ impl<'ctx> TrelloClient<'ctx> {
|
||||||
.cloned()
|
.cloned()
|
||||||
.unwrap_or_else(|| PathBuf::from(attachment.file_name));
|
.unwrap_or_else(|| PathBuf::from(attachment.file_name));
|
||||||
let file_name = self.ctx.fs.base().join(file_name);
|
let file_name = self.ctx.fs.base().join(file_name);
|
||||||
let resp = self
|
let resp = with_exponential_backoff!(
|
||||||
.ctx
|
&self.ctx,
|
||||||
.net
|
self.ctx
|
||||||
.get(url)
|
.net
|
||||||
.headers(self.auth_headers())
|
.get(url.clone())
|
||||||
.header("accept", "application/octet")
|
.headers(self.auth_headers())
|
||||||
.send()
|
.header("accept", "application/octet")
|
||||||
.await?
|
.send()
|
||||||
.bytes()
|
.await?
|
||||||
.await?;
|
.bytes()
|
||||||
|
.await
|
||||||
|
.map_err(Into::into)
|
||||||
|
)
|
||||||
|
.context("downloading attachment")?;
|
||||||
let file = self.ctx.fs.file(&file_name);
|
let file = self.ctx.fs.file(&file_name);
|
||||||
file.write(resp).context("writing to disk")?;
|
file.write(resp).context("writing to disk")?;
|
||||||
Ok(file_name)
|
Ok(file_name)
|
||||||
|
|
Loading…
Reference in a new issue