static WAITING_FOR: std::sync::Mutex<String> = std::sync::Mutex::new(String::new());
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::sync::OnceLock;
pub fn flag() -> Arc<AtomicBool> {
static FLAG: OnceLock<Arc<AtomicBool>> = OnceLock::new();
Arc::clone(FLAG.get_or_init(|| Arc::new(AtomicBool::new(true))))
}
pub fn begin(what: &str) {
if let Ok(mut waiting) = WAITING_FOR.lock() {
waiting.clear();
waiting.push_str(what);
}
flag().store(false, Ordering::Relaxed);
tracing::info!(
target: "gunnar::limits",
event = "serve.starting",
waiting_for = what,
"git transfers are refused with `starting` (EX_TEMPFAIL, a RETRY) until {} finishes: it \
is saturating the same disk a repository's first post-crash open has to read",
what
);
}
pub fn is_ready() -> bool {
flag().load(Ordering::Relaxed)
}
pub fn mark_ready(waited_for: &str, since: std::time::Instant) {
if flag().swap(true, Ordering::Relaxed) {
return;
}
tracing::info!(
target: "gunnar::limits",
event = "serve.ready",
waited_for,
ms = since.elapsed().as_millis() as u64,
"serving git: {} finished after {} ms. Until this row every git transfer was refused \
with `starting` and EX_TEMPFAIL, which is a RETRY and not a missing repository.",
waited_for,
since.elapsed().as_millis()
);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct GitListener {
pub asked: std::net::SocketAddr,
pub bound: std::net::SocketAddr,
}
impl GitListener {
pub fn serves_git(&self) -> bool {
self.bound.ip() == self.asked.ip()
&& (self.asked.port() == 0 || self.bound.port() == self.asked.port())
}
fn detail(&self) -> String {
if self.serves_git() {
format!("serving git on {}", self.bound)
} else {
format!(
"NOT serving git: the listener was asked for {} and took {} instead, which is \
what a TWIN does — it serves no git to anyone, so a clone, a fetch or a \
replication push to {} is refused by the operating system before gunnar sees \
the connection",
self.asked, self.bound, self.asked
)
}
}
}
pub fn is_serving_git(listener: Option<GitListener>) -> bool {
is_ready() && listener.map(|l| l.serves_git()).unwrap_or(true)
}
pub fn detail() -> String {
detail_with(None)
}
pub fn detail_with(listener: Option<GitListener>) -> String {
if is_ready() {
return match listener {
Some(listener) => listener.detail(),
None => "serving git".to_string(),
};
}
let what = WAITING_FOR
.lock()
.ok()
.map(|w| w.clone())
.filter(|w| !w.is_empty())
.unwrap_or_else(|| "a boot task".to_string());
format!(
"starting: {what} is still running, so git transfers are refused with EX_TEMPFAIL \
(retry); the control plane answers now"
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn readiness_is_one_latch_that_starts_open() {
let a = flag();
let b = flag();
assert!(Arc::ptr_eq(&a, &b), "two flags is two truths");
assert!(
is_ready(),
"a process with no boot task serves from its first socket"
);
begin("a test");
assert!(!is_ready());
assert!(detail().contains("starting"));
assert!(
detail().contains("a test"),
"detail() must name what begin() is waiting for: {}",
detail()
);
assert!(
!detail().contains("disk test"),
"detail() spelled the disk test into an answer about another task: {}",
detail()
);
mark_ready("a test", std::time::Instant::now());
assert!(is_ready());
assert!(detail().contains("serving"));
mark_ready("a test again", std::time::Instant::now());
assert!(is_ready());
}
#[test]
fn serving_git_is_the_listener_and_a_twin_says_no() {
let asked: std::net::SocketAddr = "0.0.0.0:2222".parse().expect("an address");
let primary = GitListener {
asked,
bound: asked,
};
assert!(
primary.serves_git(),
"the door that was asked for is the door that was taken"
);
assert_eq!(
is_serving_git(Some(primary)),
is_ready(),
"with a listener that serves, the boot window is the only thing left to say no"
);
let twin = GitListener {
asked,
bound: "127.0.0.1:41337".parse().expect("an address"),
};
assert!(
!twin.serves_git(),
"a listener on loopback is not the 0.0.0.0:2222 an operator was told about"
);
assert!(
!is_serving_git(Some(twin)),
"THE DEFECT: the twin answered `git serving` while :2222 answered ECONNREFUSED"
);
let why = twin.detail();
assert!(why.contains("NOT serving git"), "{why}");
assert!(
why.contains("TWIN"),
"an operator must be told WHY, not just no: {why}"
);
assert!(
why.contains("0.0.0.0:2222") && why.contains("127.0.0.1:41337"),
"{why}"
);
let ephemeral = GitListener {
asked: "127.0.0.1:0".parse().expect("an address"),
bound: "127.0.0.1:52001".parse().expect("an address"),
};
assert!(
ephemeral.serves_git(),
"port 0 means any port, and any port was taken"
);
assert_eq!(is_serving_git(None), is_ready(), "silence is not a refusal");
assert!(primary.detail().contains("serving git on 0.0.0.0:2222"));
}
}