GhApiClient: Fallback to unauthorized mode if auth token is invalid (#1121)

Fixed #850

Signed-off-by: Jiahao XU <Jiahao_XU@outlook.com>
This commit is contained in:
Jiahao XU 2023-06-04 12:29:42 +10:00 committed by GitHub
parent 351b9d074a
commit 2acba14b41
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 61 additions and 35 deletions

View file

@ -1,7 +1,10 @@
use std::{ use std::{
collections::HashMap, collections::HashMap,
ops::Deref, ops::Deref,
sync::{Arc, Mutex, RwLock}, sync::{
atomic::{AtomicBool, Ordering::Relaxed},
Arc, Mutex, RwLock,
},
time::{Duration, Instant}, time::{Duration, Instant},
}; };
@ -87,9 +90,11 @@ where
#[derive(Debug)] #[derive(Debug)]
struct Inner { struct Inner {
client: remote::Client, client: remote::Client,
auth_token: Option<CompactString>,
release_artifacts: Map<GhRelease, OnceCell<Option<request::Artifacts>>>, release_artifacts: Map<GhRelease, OnceCell<Option<request::Artifacts>>>,
retry_after: Mutex<Option<Instant>>, retry_after: Mutex<Option<Instant>>,
auth_token: Option<CompactString>,
is_auth_token_valid: AtomicBool,
} }
/// Github API client for querying whether a release artifact exitsts. /// Github API client for querying whether a release artifact exitsts.
@ -116,11 +121,47 @@ impl GhApiClient {
Self(Arc::new(Inner { Self(Arc::new(Inner {
client, client,
auth_token,
release_artifacts: Default::default(), release_artifacts: Default::default(),
retry_after: Default::default(), retry_after: Default::default(),
auth_token,
is_auth_token_valid: AtomicBool::new(true),
})) }))
} }
}
enum FetchReleaseArtifactError {
Error(GhApiError),
RateLimit { retry_after: Instant },
Unauthorized,
}
impl GhApiClient {
async fn do_fetch_release_artifacts(
&self,
release: &GhRelease,
auth_token: Option<&str>,
) -> Result<Option<request::Artifacts>, FetchReleaseArtifactError> {
use request::FetchReleaseRet::*;
use FetchReleaseArtifactError as Error;
match request::fetch_release_artifacts(&self.0.client, release, auth_token).await {
Ok(ReleaseNotFound) => Ok(None),
Ok(Artifacts(artifacts)) => Ok(Some(artifacts)),
Ok(ReachedRateLimit { retry_after }) => {
let retry_after = retry_after.unwrap_or(DEFAULT_RETRY_DURATION);
let now = Instant::now();
let retry_after = now
.checked_add(retry_after)
.unwrap_or_else(|| now + DEFAULT_RETRY_DURATION);
Err(Error::RateLimit { retry_after })
}
Ok(Unauthorized) => Err(Error::Unauthorized),
Err(err) => Err(Error::Error(err)),
}
}
/// The returned future is guaranteed to be pointer size. /// The returned future is guaranteed to be pointer size.
pub async fn has_release_artifact( pub async fn has_release_artifact(
@ -130,24 +171,18 @@ impl GhApiClient {
artifact_name, artifact_name,
}: GhReleaseArtifact, }: GhReleaseArtifact,
) -> Result<HasReleaseArtifact, GhApiError> { ) -> Result<HasReleaseArtifact, GhApiError> {
enum Failure { use FetchReleaseArtifactError as Error;
Error(GhApiError),
RateLimit { retry_after: Instant },
Unauthorized,
}
let once_cell = self.0.release_artifacts.get(release.clone()); let once_cell = self.0.release_artifacts.get(release.clone());
let res = once_cell let res = once_cell
.get_or_try_init(|| { .get_or_try_init(|| {
Box::pin(async { Box::pin(async {
use request::FetchReleaseRet::*;
{ {
let mut guard = self.0.retry_after.lock().unwrap(); let mut guard = self.0.retry_after.lock().unwrap();
if let Some(retry_after) = *guard { if let Some(retry_after) = *guard {
if retry_after.elapsed().is_zero() { if retry_after.elapsed().is_zero() {
return Err(Failure::RateLimit { retry_after }); return Err(Error::RateLimit { retry_after });
} else { } else {
// Instant retry_after is already reached. // Instant retry_after is already reached.
*guard = None; *guard = None;
@ -155,28 +190,19 @@ impl GhApiClient {
}; };
} }
match request::fetch_release_artifacts( if self.0.is_auth_token_valid.load(Relaxed) {
&self.0.client, match self
release, .do_fetch_release_artifacts(&release, self.0.auth_token.as_deref())
self.0.auth_token.as_deref(),
)
.await .await
{ {
Ok(ReleaseNotFound) => Ok::<_, Failure>(None), Err(Error::Unauthorized) => {
Ok(Artifacts(artifacts)) => Ok(Some(artifacts)), self.0.is_auth_token_valid.store(false, Relaxed);
Ok(ReachedRateLimit { retry_after }) => {
let retry_after = retry_after.unwrap_or(DEFAULT_RETRY_DURATION);
let now = Instant::now();
let retry_after = now
.checked_add(retry_after)
.unwrap_or_else(|| now + DEFAULT_RETRY_DURATION);
Err(Failure::RateLimit { retry_after })
} }
Ok(Unauthorized) => Err(Failure::Unauthorized), res => return res,
Err(err) => Err(Failure::Error(err)),
} }
}
self.do_fetch_release_artifacts(&release, None).await
}) })
}) })
.await; .await;
@ -191,13 +217,13 @@ impl GhApiClient {
}) })
} }
Ok(None) => Ok(HasReleaseArtifact::NoSuchRelease), Ok(None) => Ok(HasReleaseArtifact::NoSuchRelease),
Err(Failure::Unauthorized) => Ok(HasReleaseArtifact::Unauthorized), Err(Error::Unauthorized) => Ok(HasReleaseArtifact::Unauthorized),
Err(Failure::RateLimit { retry_after }) => { Err(Error::RateLimit { retry_after }) => {
*self.0.retry_after.lock().unwrap() = Some(retry_after); *self.0.retry_after.lock().unwrap() = Some(retry_after);
Ok(HasReleaseArtifact::RateLimit { retry_after }) Ok(HasReleaseArtifact::RateLimit { retry_after })
} }
Err(Failure::Error(err)) => Err(err), Err(Error::Error(err)) => Err(err),
} }
} }
} }

View file

@ -83,7 +83,7 @@ pub(super) enum FetchReleaseRet {
/// Returns 404 if not found /// Returns 404 if not found
pub(super) async fn fetch_release_artifacts( pub(super) async fn fetch_release_artifacts(
client: &remote::Client, client: &remote::Client,
GhRelease { owner, repo, tag }: GhRelease, GhRelease { owner, repo, tag }: &GhRelease,
auth_token: Option<&str>, auth_token: Option<&str>,
) -> Result<FetchReleaseRet, GhApiError> { ) -> Result<FetchReleaseRet, GhApiError> {
let mut request_builder = client let mut request_builder = client