use std::{
collections::HashSet,
sync::{Mutex, PoisonError},
time::Duration,
};
use once_cell::sync::Lazy;
use tokio::sync::Notify;
use super::RouteSourceKind;
pub const READY_TIMEOUT: Duration = Duration::from_secs(10);
static READINESS: Lazy<Readiness> = Lazy::new(Readiness::default);
#[derive(Default)]
pub struct Readiness {
applied: Mutex<HashSet<RouteSourceKind>>,
changed: Notify,
}
impl Readiness {
pub fn applied(&self, source: RouteSourceKind) {
let mut applied = self.applied.lock().unwrap_or_else(PoisonError::into_inner);
if applied.insert(source) {
self.changed.notify_waiters();
}
}
pub async fn sources(&self, sources: &[RouteSourceKind]) {
loop {
let changed = self.changed.notified();
{
let applied = self.applied.lock().unwrap_or_else(PoisonError::into_inner);
if sources.iter().all(|source| applied.contains(source)) {
return;
}
}
changed.await;
}
}
}
pub fn applied(source: RouteSourceKind) {
READINESS.applied(source);
}
pub async fn wait(sources: &[RouteSourceKind], certificates: bool) -> bool {
let ready = async {
READINESS.sources(sources).await;
if certificates {
while !all_certificates().await {
tokio::time::sleep(Duration::from_millis(100)).await;
}
}
};
tokio::time::timeout(READY_TIMEOUT, ready).await.is_ok()
}
async fn all_certificates() -> bool {
let store = crate::stores::global::get_store();
for (host, _) in crate::stores::lookup::tls_hosts() {
if store.get_certificate(&host).await.is_none() {
return false;
}
}
true
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use crate::routing::RouteSourceKind;
use super::Readiness;
#[tokio::test]
async fn test_waits_for_every_source() {
let readiness = Readiness::default();
readiness.applied(RouteSourceKind::File);
let waiting = readiness.sources(&[RouteSourceKind::File, RouteSourceKind::Docker]);
tokio::pin!(waiting);
assert!(
tokio::time::timeout(Duration::from_millis(100), &mut waiting)
.await
.is_err()
);
readiness.applied(RouteSourceKind::Docker);
assert!(tokio::time::timeout(Duration::from_millis(100), waiting)
.await
.is_ok());
}
}