use super::boundary::IngressBoundaryMiddleware;
use super::IngressKey;
use crate::{StageId, StageKey};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, OnceLock};
pub struct FilledHostedIngress {
pub stage_id: StageId,
pub stage_key: StageKey,
pub boundary: Option<Arc<dyn IngressBoundaryMiddleware>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct HostedIngressAlreadyBound;
impl std::fmt::Display for HostedIngressAlreadyBound {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"hosted ingress binding slot is already bound to a source stage"
)
}
}
impl std::error::Error for HostedIngressAlreadyBound {}
#[derive(Clone)]
pub struct HostedIngressBindingSlot {
ingress_key: IngressKey,
cell: Arc<OnceLock<FilledHostedIngress>>,
resume_live: Arc<AtomicBool>,
}
impl HostedIngressBindingSlot {
pub fn new(ingress_key: impl Into<IngressKey>) -> Self {
Self {
ingress_key: ingress_key.into(),
cell: Arc::new(OnceLock::new()),
resume_live: Arc::new(AtomicBool::new(true)),
}
}
pub fn ingress_key(&self) -> &IngressKey {
&self.ingress_key
}
pub fn fill(&self, filled: FilledHostedIngress) -> Result<(), HostedIngressAlreadyBound> {
self.cell.set(filled).map_err(|_| HostedIngressAlreadyBound)
}
pub fn filled(&self) -> Option<&FilledHostedIngress> {
self.cell.get()
}
pub fn is_filled(&self) -> bool {
self.cell.get().is_some()
}
pub fn hold_for_resume_catch_up(&self) {
self.resume_live.store(false, Ordering::Release);
}
pub fn mark_resume_live(&self) {
self.resume_live.store(true, Ordering::Release);
}
pub fn is_resume_live(&self) -> bool {
self.resume_live.load(Ordering::Acquire)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn resume_live_defaults_true_and_flag_is_shared_across_clones() {
let slot = HostedIngressBindingSlot::new("orders");
let surface_half = slot.clone();
assert!(surface_half.is_resume_live());
slot.hold_for_resume_catch_up();
assert!(!surface_half.is_resume_live());
slot.mark_resume_live();
assert!(surface_half.is_resume_live());
}
}