use crate::release_log;
pub(crate) async fn check_github_rate_limit(client: &reqwest::Client, token: &str, threshold: u64) {
let url = "https://api.github.com/rate_limit";
let resp = match client
.get(url)
.header("Authorization", format!("Bearer {}", token))
.header("Accept", "application/vnd.github+json")
.header("User-Agent", anodizer_core::http::USER_AGENT)
.send()
.await
{
Ok(r) => r,
Err(_) => return, };
if !resp.status().is_success() {
return;
}
let body: serde_json::Value = match resp.json().await {
Ok(v) => v,
Err(_) => return,
};
let remaining = body
.pointer("/resources/core/remaining")
.and_then(|v| v.as_u64())
.unwrap_or(u64::MAX);
let reset_epoch = body
.pointer("/resources/core/reset")
.and_then(|v| v.as_u64())
.unwrap_or(0);
if remaining > threshold {
return;
}
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
let sleep_secs = if reset_epoch > now {
reset_epoch - now + 1
} else {
5 };
release_log().status(&format!(
"rate limit almost reached ({remaining} remaining), sleeping for {sleep_secs}s..."
));
let sleep = tokio::time::sleep(std::time::Duration::from_secs(sleep_secs));
tokio::pin!(sleep);
#[cfg(unix)]
{
let mut sigterm =
tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()).ok();
tokio::select! {
_ = &mut sleep => {}
_ = tokio::signal::ctrl_c() => {
release_log().warn(
"rate-limit wait interrupted by SIGINT; release will likely fail \
on the next API call",
);
}
_ = async {
match sigterm.as_mut() {
Some(s) => { s.recv().await; }
None => std::future::pending::<()>().await,
}
} => {
release_log().warn(
"rate-limit wait interrupted by SIGTERM; release will likely fail \
on the next API call",
);
}
}
}
#[cfg(not(unix))]
{
tokio::select! {
_ = &mut sleep => {}
_ = tokio::signal::ctrl_c() => {
release_log().warn(
"rate-limit wait interrupted by Ctrl-C; release will likely fail \
on the next API call",
);
}
}
}
}
#[cfg(all(test, unix))]
mod tests {
use tokio::signal::unix::{SignalKind, signal};
#[tokio::test(flavor = "current_thread")]
async fn sigterm_listener_observes_self_signal() {
let mut sigterm = signal(SignalKind::terminate())
.map_err(|e| format!("could not install SIGTERM handler: {e}"))
.ok()
.unwrap_or_else(|| panic!("SIGTERM handler install failed"));
let pid = std::process::id();
tokio::spawn(async move {
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
let _ = std::process::Command::new("kill")
.args(["-TERM", &pid.to_string()])
.status();
});
let recv = sigterm.recv();
let timeout = tokio::time::sleep(std::time::Duration::from_secs(2));
tokio::select! {
v = recv => {
assert!(v.is_some(), "signal stream closed before SIGTERM delivered");
}
_ = timeout => {
panic!("SIGTERM listener did not observe self-delivered signal within 2s");
}
}
}
#[test]
fn sigterm_signal_kind_is_constructible() {
let _ = SignalKind::terminate();
}
}