From 8695ae7eb6af5f21a39cc3c2b5a84decb9577728 Mon Sep 17 00:00:00 2001 From: Benjamin Pannell Date: Fri, 18 Nov 2022 22:13:23 +0000 Subject: [PATCH] feat: Add support for retrying failed requests to GitHub --- src/online/service/github.rs | 88 +++++++++++++++++++++++++----------- 1 file changed, 62 insertions(+), 26 deletions(-) diff --git a/src/online/service/github.rs b/src/online/service/github.rs index c8b7de66..375667d2 100644 --- a/src/online/service/github.rs +++ b/src/online/service/github.rs @@ -1,6 +1,8 @@ +use std::time::Duration; + use super::*; use crate::errors; -use reqwest::{Method, Request, StatusCode}; +use reqwest::{Method, Request, StatusCode, Url}; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; @@ -113,7 +115,7 @@ impl GitHubService { } } - async fn make_request, T: DeserializeOwned>( + async fn make_request + Clone, T: DeserializeOwned>( &self, core: &Core, service: &Service, @@ -122,7 +124,7 @@ impl GitHubService { body: B, acceptable: Vec, ) -> Result, Error> { - let url = uri.parse().map_err(|e| { + let url: Url = uri.parse().map_err(|e| { errors::system_with_internal( &format!("Unable to parse GitHub API URL '{}'.", uri), "Please report this error to us by opening an issue on GitHub.", @@ -132,29 +134,63 @@ impl GitHubService { let token = core.keychain().get_token(&service.name)?; - let mut req = Request::new(method, url); - - *req.body_mut() = Some(body.into()); - - let headers = req.headers_mut(); - - headers.append("User-Agent", version!("Git-Tool/v").parse()?); - headers.append("Accept", "application/vnd.github.v3+json".parse()?); - headers.append("Authorization", format!("token {}", token).parse()?); - - let resp = core.http_client().request(req).await?; - - match resp.status() { - status if acceptable.contains(&status) => { - let result = resp.json().await?; - - Ok(Ok(result)) - } - status => { - let mut result: GitHubErrorResponse = resp.json().await?; - result.http_status_code = status; - - Ok(Err(result)) + let mut remaining_attempts = 3; + let retryable = vec![ + StatusCode::TOO_MANY_REQUESTS, + StatusCode::INTERNAL_SERVER_ERROR, + StatusCode::BAD_GATEWAY, + StatusCode::SERVICE_UNAVAILABLE, + ]; + + loop { + remaining_attempts -= 1; + + let mut req = Request::new(method.clone(), url.clone()); + + *req.body_mut() = Some(body.clone().into()); + + let headers = req.headers_mut(); + + headers.append("User-Agent", version!("Git-Tool/v").parse()?); + headers.append("Accept", "application/vnd.github.v3+json".parse()?); + headers.append("Authorization", format!("token {}", token).parse()?); + + match core.http_client().request(req).await { + Ok(resp) if acceptable.contains(&resp.status()) => { + let result = resp.json().await?; + + return Ok(Ok(result)); + } + Ok(resp) if remaining_attempts > 0 && retryable.contains(&resp.status()) => { + tracing::warn!( + "GitHub API request failed with status code {}. Retrying...", + resp.status() + ); + + tokio::time::sleep(Duration::from_secs(1)).await; + + continue; + } + Ok(resp) => { + let status = resp.status(); + let mut result: GitHubErrorResponse = resp.json().await?; + result.http_status_code = status; + + return Ok(Err(result)); + } + Err(error) if remaining_attempts > 0 => { + tracing::warn!( + "GitHub API request failed with error {}. Retrying...", + error + ); + + tokio::time::sleep(Duration::from_secs(1)).await; + + continue; + } + Err(error) => { + return Err(error); + } } } }