⎇ 53dbdf0f1deea10d6fc567ea2b43e1e75d6d60cd
crates/core/ente-zero/src/graph/shutdown.rs
Historia
Quién tocó qué
crudo
chore: monorepo inicial con arje + minga + yahweh absorbidos
1//! Cascade shutdown: SIGTERM en orden topológico (hojas primero), grace
2//! period, SIGKILL para stragglers, reap final.
3
4use super::EnteGraph;
5use nix::errno::Errno;
6use nix::sys::signal::{kill, Signal};
7use nix::sys::wait::{waitpid, WaitPidFlag, WaitStatus};
8use nix::unistd::Pid;
9use std::time::{Duration, Instant};
10use tracing::{debug, info, warn};
11
12/// Tiempo que damos a los Entes tras SIGTERM antes de escalar a SIGKILL.
13pub const SHUTDOWN_GRACE: Duration = Duration::from_secs(2);
14
15impl EnteGraph {
16 pub async fn cascade_shutdown(&mut self) {
17 let order = self.topo_order();
18 let pids: Vec<Pid> = order.iter()
19 .filter_map(|id| self.incarnated.get(id).and_then(|i| i.pid))
20 .collect();
21
22 if pids.is_empty() {
23 info!("cascade shutdown: ningún Ente encarnado, salida limpia");
24 return;
25 }
26
27 info!(
28 count = pids.len(), grace_ms = SHUTDOWN_GRACE.as_millis() as u64,
29 "SIGTERM cascade (topológico, hojas primero)"
30 );
31 for pid in &pids {
32 match kill(*pid, Signal::SIGTERM) {
33 Ok(()) => {}
34 Err(Errno::ESRCH) => {} // ya muerto, lo cosecharemos abajo
35 Err(e) => warn!(?pid, ?e, "kill SIGTERM falló"),
36 }
37 }
38
39 let deadline = Instant::now() + SHUTDOWN_GRACE;
40 while Instant::now() < deadline {
41 if !self.incarnated.values().any(|i| i.pid.is_some()) {
42 break;
43 }
44 match waitpid(None, Some(WaitPidFlag::WNOHANG)) {
45 Ok(WaitStatus::Exited(pid, code)) => {
46 self.reap_during_shutdown(pid);
47 debug!(?pid, code, "reaped (exited)");
48 }
49 Ok(WaitStatus::Signaled(pid, sig, _)) => {
50 self.reap_during_shutdown(pid);
51 debug!(?pid, ?sig, "reaped (signaled)");
52 }
53 Ok(WaitStatus::StillAlive) | Err(Errno::EINTR) => {
54 tokio::time::sleep(Duration::from_millis(50)).await;
55 }
56 Err(Errno::ECHILD) => return,
57 Ok(_) => {}
58 Err(e) => {
59 warn!(?e, "waitpid fallo en shutdown grace");
60 break;
61 }
62 }
63 }
64
65 let stragglers: Vec<Pid> = self.incarnated.values()
66 .filter_map(|i| i.pid)
67 .collect();
68
69 if stragglers.is_empty() {
70 info!("cascade shutdown completo (todos los Entes terminaron en gracia)");
71 return;
72 }
73
74 warn!(count = stragglers.len(), "stragglers post-SIGTERM, escalando a SIGKILL");
75 for pid in &stragglers {
76 let _ = kill(*pid, Signal::SIGKILL);
77 }
78 loop {
79 match waitpid(None, Some(WaitPidFlag::WNOHANG)) {
80 Ok(WaitStatus::Exited(pid, _)) | Ok(WaitStatus::Signaled(pid, _, _)) => {
81 self.reap_during_shutdown(pid);
82 }
83 Ok(WaitStatus::StillAlive) => {
84 std::thread::sleep(Duration::from_millis(20));
85 }
86 Err(Errno::ECHILD) => break,
87 _ => break,
88 }
89 if !self.incarnated.values().any(|i| i.pid.is_some()) { break; }
90 }
91 info!("cascade shutdown completo (con SIGKILL)");
92 }
93
94 fn reap_during_shutdown(&mut self, pid: Pid) {
95 let Some(id) = self.by_pid.remove(&pid.as_raw()) else { return };
96 if let Some(inc) = self.incarnated.remove(&id) {
97 self.unregister_provider(&inc.card);
98 }
99 }
100}