mirror of
https://github.com/telemt/telemt.git
synced 2026-09-21 10:08:23 +03:00
WEB: Lifecycle + Lane ownership + Diag fixes
Co-Authored-By: brekotis <93345790+brekotis@users.noreply.github.com>
This commit is contained in:
@@ -284,7 +284,7 @@ impl ListenerSlot {
|
||||
}
|
||||
|
||||
pub(super) async fn stop(&mut self) -> Result<(), String> {
|
||||
self.cancellation.cancel();
|
||||
self.request_stop();
|
||||
if let Some(task) = self.task.take() {
|
||||
task.await.map_err(|error_value| {
|
||||
format!("listener {} task failed: {error_value}", self.spec.addr)
|
||||
@@ -305,6 +305,61 @@ impl ListenerSlot {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Cancels admission synchronously before the shared shutdown deadline starts draining.
|
||||
pub(super) fn request_stop(&self) {
|
||||
self.cancellation.cancel();
|
||||
}
|
||||
|
||||
/// Joins this acceptor and its WEB connections by one process shutdown deadline.
|
||||
pub(super) async fn stop_until(
|
||||
&mut self,
|
||||
deadline: tokio::time::Instant,
|
||||
) -> Result<(), String> {
|
||||
self.request_stop();
|
||||
let mut errors = Vec::new();
|
||||
if let Some(mut task) = self.task.take() {
|
||||
let joined = if task.is_finished() {
|
||||
Some(task.await)
|
||||
} else {
|
||||
match tokio::time::timeout_at(deadline, &mut task).await {
|
||||
Ok(result) => Some(result),
|
||||
Err(_) => {
|
||||
task.abort();
|
||||
let _ = task.await;
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
match joined {
|
||||
Some(Ok(())) => {}
|
||||
Some(Err(error_value)) => errors.push(format!(
|
||||
"listener {} task failed: {error_value}",
|
||||
self.spec.addr
|
||||
)),
|
||||
None => errors.push(format!(
|
||||
"listener {} accept shutdown timed out",
|
||||
self.spec.addr
|
||||
)),
|
||||
}
|
||||
}
|
||||
self.connections.close();
|
||||
if !self.connections.is_empty()
|
||||
&& tokio::time::timeout_at(deadline, self.connections.wait())
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
errors.push(format!(
|
||||
"listener {} connection shutdown timed out",
|
||||
self.spec.addr
|
||||
));
|
||||
}
|
||||
if errors.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(errors.join("; "))
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn restart(&mut self, active_runtime: Arc<ArcSwap<RuntimeGeneration>>) {
|
||||
self.active_runtime = active_runtime.clone();
|
||||
self.cancellation = CancellationToken::new();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use arc_swap::ArcSwap;
|
||||
|
||||
@@ -13,7 +14,7 @@ use super::bind::{BoundListeners, BoundTcpListener, PreparedTcpListener, prepare
|
||||
use super::plan::{ListenerBindSpec, listener_bind_plan};
|
||||
#[cfg(unix)]
|
||||
use super::unix::UnixAcceptHandle;
|
||||
use crate::web::manager::WebProcessRuntime;
|
||||
use crate::web::manager::{WebProcessRuntime, WebShutdownOutcome};
|
||||
use crate::web::trace::WebTraceStore;
|
||||
|
||||
/// Process-owned listener inventory and accept-task lifecycle controller.
|
||||
@@ -214,24 +215,76 @@ impl ListenerManager {
|
||||
);
|
||||
}
|
||||
|
||||
/// Stops and joins every accept task before sockets are released.
|
||||
/// Stops every accept task and applies one deadline to the complete WEB ingress.
|
||||
pub(crate) async fn shutdown(&mut self) -> Result<(), String> {
|
||||
if self.web_runtime.is_none() {
|
||||
let mut errors = Vec::new();
|
||||
for slot in self.slots.values_mut() {
|
||||
if let Err(error_value) = slot.stop().await {
|
||||
errors.push(error_value);
|
||||
}
|
||||
}
|
||||
#[cfg(unix)]
|
||||
if let Some(unix) = &mut self.unix
|
||||
&& let Err(error_value) = unix.stop().await
|
||||
{
|
||||
errors.push(error_value);
|
||||
}
|
||||
self.slots.clear();
|
||||
#[cfg(unix)]
|
||||
{
|
||||
self.unix = None;
|
||||
}
|
||||
return if errors.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(errors.join("; "))
|
||||
};
|
||||
}
|
||||
let timeout_secs = self
|
||||
.active_runtime
|
||||
.load()
|
||||
.config()
|
||||
.web
|
||||
.timeouts
|
||||
.shutdown_secs;
|
||||
let now = tokio::time::Instant::now();
|
||||
let deadline = now
|
||||
.checked_add(Duration::from_secs(timeout_secs))
|
||||
.unwrap_or(now);
|
||||
for slot in self.slots.values() {
|
||||
slot.request_stop();
|
||||
}
|
||||
#[cfg(unix)]
|
||||
if let Some(unix) = &self.unix {
|
||||
unix.request_stop();
|
||||
}
|
||||
let Some(web_runtime) = self.web_runtime.take() else {
|
||||
return Err("WEB runtime disappeared during shutdown orchestration".to_string());
|
||||
};
|
||||
let drain = web_runtime.begin_shutdown();
|
||||
let mut errors = Vec::new();
|
||||
for slot in self.slots.values_mut() {
|
||||
if let Err(error_value) = slot.stop().await {
|
||||
let slot_waits = futures_util::future::join_all(
|
||||
self.slots
|
||||
.values_mut()
|
||||
.map(|slot| slot.stop_until(deadline)),
|
||||
);
|
||||
let (slot_results, web_outcome) = tokio::join!(slot_waits, drain.wait_until(deadline));
|
||||
for result in slot_results {
|
||||
if let Err(error_value) = result {
|
||||
errors.push(error_value);
|
||||
}
|
||||
}
|
||||
#[cfg(unix)]
|
||||
if let Some(unix) = &mut self.unix
|
||||
&& let Err(error_value) = unix.stop().await
|
||||
&& let Err(error_value) = unix.stop_until(deadline).await
|
||||
{
|
||||
errors.push(error_value);
|
||||
}
|
||||
self.slots.clear();
|
||||
if let Some(web_runtime) = self.web_runtime.take() {
|
||||
web_runtime.shutdown().await;
|
||||
if web_outcome == WebShutdownOutcome::DeadlineExceeded {
|
||||
errors.push("WEB ingress shutdown deadline exceeded".to_string());
|
||||
}
|
||||
self.slots.clear();
|
||||
#[cfg(unix)]
|
||||
{
|
||||
self.unix = None;
|
||||
|
||||
@@ -148,11 +148,42 @@ impl UnixAcceptHandle {
|
||||
}
|
||||
|
||||
pub(super) async fn stop(&mut self) -> Result<(), String> {
|
||||
self.cancellation.cancel();
|
||||
self.request_stop();
|
||||
if let Some(task) = self.task.take() {
|
||||
task.await
|
||||
.map_err(|error_value| format!("Unix listener task failed: {error_value}"))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Cancels Unix admission before process-owned listener waits begin.
|
||||
pub(super) fn request_stop(&self) {
|
||||
self.cancellation.cancel();
|
||||
}
|
||||
|
||||
/// Joins the Unix acceptor by the process listener deadline.
|
||||
pub(super) async fn stop_until(
|
||||
&mut self,
|
||||
deadline: tokio::time::Instant,
|
||||
) -> Result<(), String> {
|
||||
self.request_stop();
|
||||
let Some(mut task) = self.task.take() else {
|
||||
return Ok(());
|
||||
};
|
||||
if task.is_finished() {
|
||||
return task
|
||||
.await
|
||||
.map_err(|error_value| format!("Unix listener task failed: {error_value}"));
|
||||
}
|
||||
match tokio::time::timeout_at(deadline, &mut task).await {
|
||||
Ok(result) => {
|
||||
result.map_err(|error_value| format!("Unix listener task failed: {error_value}"))
|
||||
}
|
||||
Err(_) => {
|
||||
task.abort();
|
||||
let _ = task.await;
|
||||
Err("Unix listener shutdown timed out".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user