mirror of
https://github.com/telemt/telemt.git
synced 2026-09-05 18:16:06 +03:00
cf0cd08387
Co-Authored-By: brekotis <93345790+brekotis@users.noreply.github.com>
154 lines
4.7 KiB
Rust
154 lines
4.7 KiB
Rust
use std::net::SocketAddr;
|
|
use std::sync::Arc;
|
|
use std::sync::Weak;
|
|
use std::sync::atomic::{AtomicU64, Ordering};
|
|
use std::time::Instant;
|
|
|
|
use tokio::sync::watch;
|
|
|
|
use super::manager::WebProcessRuntime;
|
|
|
|
/// Process-owned WEB ingress lifecycle state.
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
pub(crate) enum WebRuntimeLifecycle {
|
|
/// Listener orchestration has not completed.
|
|
Starting,
|
|
/// This process has no WEB listener.
|
|
NoWebListener,
|
|
/// WEB admission and request handling are active.
|
|
Running,
|
|
/// WEB admission is closed while owned work drains.
|
|
Draining,
|
|
/// All WEB ingress work drained within the deadline.
|
|
Drained,
|
|
/// The bounded shutdown deadline expired.
|
|
DeadlineExceeded,
|
|
}
|
|
|
|
impl WebRuntimeLifecycle {
|
|
/// Returns the stable API token for this lifecycle state.
|
|
pub(crate) const fn as_str(self) -> &'static str {
|
|
match self {
|
|
Self::Starting => "starting",
|
|
Self::NoWebListener => "no_web_listener",
|
|
Self::Running => "running",
|
|
Self::Draining => "draining",
|
|
Self::Drained => "drained",
|
|
Self::DeadlineExceeded => "deadline_exceeded",
|
|
}
|
|
}
|
|
}
|
|
|
|
/// One immutable lifecycle publication consumed by the control plane.
|
|
#[derive(Clone)]
|
|
pub(crate) struct WebRuntimePublication {
|
|
/// Monotonic process-local lifecycle transition number.
|
|
pub(crate) epoch: u64,
|
|
/// Current lifecycle state.
|
|
pub(crate) lifecycle: WebRuntimeLifecycle,
|
|
/// Monotonic transition time used only for relative age.
|
|
pub(crate) since: Instant,
|
|
/// Actual WEB listener addresses frozen for this process.
|
|
pub(crate) listeners: Arc<[SocketAddr]>,
|
|
/// Weak runtime access that never extends data-plane ownership.
|
|
pub(crate) runtime: Weak<WebProcessRuntime>,
|
|
}
|
|
|
|
/// Single-writer process lifecycle publisher for WEB ingress.
|
|
#[derive(Clone)]
|
|
pub(crate) struct WebRuntimeControl {
|
|
epoch: Arc<AtomicU64>,
|
|
tx: watch::Sender<WebRuntimePublication>,
|
|
}
|
|
|
|
impl WebRuntimeControl {
|
|
/// Creates the process channel in the pre-listener `starting` state.
|
|
pub(crate) fn new() -> Self {
|
|
let publication = WebRuntimePublication {
|
|
epoch: 1,
|
|
lifecycle: WebRuntimeLifecycle::Starting,
|
|
since: Instant::now(),
|
|
listeners: Arc::from([]),
|
|
runtime: Weak::new(),
|
|
};
|
|
let (tx, _rx) = watch::channel(publication);
|
|
Self {
|
|
epoch: Arc::new(AtomicU64::new(1)),
|
|
tx,
|
|
}
|
|
}
|
|
|
|
/// Subscribes without transferring runtime ownership to the receiver.
|
|
pub(crate) fn subscribe(&self) -> watch::Receiver<WebRuntimePublication> {
|
|
self.tx.subscribe()
|
|
}
|
|
|
|
/// Publishes one lifecycle transition and optional weak runtime reference.
|
|
pub(crate) fn publish(
|
|
&self,
|
|
lifecycle: WebRuntimeLifecycle,
|
|
listeners: Arc<[SocketAddr]>,
|
|
runtime: Weak<WebProcessRuntime>,
|
|
) {
|
|
let epoch = self.epoch.fetch_add(1, Ordering::AcqRel).saturating_add(1);
|
|
self.tx.send_replace(WebRuntimePublication {
|
|
epoch,
|
|
lifecycle,
|
|
since: Instant::now(),
|
|
listeners,
|
|
runtime,
|
|
});
|
|
}
|
|
}
|
|
|
|
impl Default for WebRuntimeControl {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn publication_is_monotonic_and_does_not_require_a_runtime_owner() {
|
|
let control = WebRuntimeControl::new();
|
|
let receiver = control.subscribe();
|
|
control.publish(
|
|
WebRuntimeLifecycle::NoWebListener,
|
|
Arc::from([]),
|
|
Weak::new(),
|
|
);
|
|
|
|
let publication = receiver.borrow().clone();
|
|
assert_eq!(publication.epoch, 2);
|
|
assert_eq!(publication.lifecycle, WebRuntimeLifecycle::NoWebListener);
|
|
assert!(publication.runtime.upgrade().is_none());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn publication_keeps_only_weak_runtime_ownership() {
|
|
let generation = crate::maestro::generation::test_runtime_generation(
|
|
1,
|
|
crate::config::ProxyConfig::default(),
|
|
);
|
|
let runtime =
|
|
WebProcessRuntime::start(Arc::new(arc_swap::ArcSwap::from(generation.clone())));
|
|
let strong_before = Arc::strong_count(&runtime);
|
|
let control = WebRuntimeControl::new();
|
|
control.publish(
|
|
WebRuntimeLifecycle::Running,
|
|
Arc::from([]),
|
|
Arc::downgrade(&runtime),
|
|
);
|
|
|
|
assert_eq!(Arc::strong_count(&runtime), strong_before);
|
|
runtime.shutdown().await;
|
|
drop(runtime);
|
|
assert!(control.subscribe().borrow().runtime.upgrade().is_none());
|
|
generation.stop_sessions().await;
|
|
generation.stop_background_tasks().await;
|
|
}
|
|
}
|