Skip to main content

Mountain/RunTime/Shutdown/
ShutdownCocoonWithRetry.rs

1#![allow(non_snake_case)]
2
3//! Send `$shutdown` over gRPC to Cocoon (3 attempts), then SIGKILL the child
4//! regardless of gRPC outcome. The hard-kill (Atom I6) is critical: a gRPC
5//! failure (transport error, broken pipe) used to leave the child orphaned,
6//! holding port 50052, and the next Mountain launch hit EADDRINUSE with the
7//! extension host stuck in degraded mode.
8
9use std::sync::Arc;
10
11use CommonLibrary::{Environment::Requires::Requires, Error::CommonError::CommonError, IPC::IPCProvider::IPCProvider};
12
13use crate::{RunTime::ApplicationRunTime::ApplicationRunTime, dev_log};
14
15impl ApplicationRunTime {
16	pub async fn ShutdownCocoonWithRetry(&self) -> Result<(), CommonError> {
17		let IPCProvider:Arc<dyn IPCProvider> = self.Environment.Require();
18
19		let MaximumAttempts = 3;
20		let mut Attempts = 0;
21		let mut GracefulOk = false;
22		let mut LastError:Option<CommonError> = None;
23
24		while Attempts < MaximumAttempts {
25			match IPCProvider
26				.SendNotificationToSideCar("cocoon-main".to_string(), "$shutdown".to_string(), serde_json::Value::Null)
27				.await
28			{
29				Ok(()) => {
30					tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
31					GracefulOk = true;
32					break;
33				},
34				Err(Error) => {
35					Attempts += 1;
36					LastError = Some(Error.clone());
37					if Attempts < MaximumAttempts {
38						dev_log!(
39							"lifecycle",
40							"warn: [ApplicationRunTime] Cocoon shutdown attempt {} failed: {}. Retrying...",
41							Attempts,
42							Error
43						);
44						tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await;
45					}
46				},
47			}
48		}
49
50		// Mark the Vine gRPC client shutting down BEFORE the SIGKILL so any
51		// background tokio task firing `SendNotification` after this flips
52		// short-circuits to `Ok(())` instead of attempting a TCP connect to
53		// the dead socket and logging a false-positive `Connection refused`.
54		crate::Vine::Client::MarkShutdown::Fn();
55
56		// Atom I6: always reap the child after the graceful attempt. No-op if
57		// the child already exited from $shutdown.
58		crate::ProcessManagement::CocoonManagement::HardKillCocoon().await;
59
60		if GracefulOk {
61			Ok(())
62		} else {
63			Err(LastError.unwrap_or_else(|| {
64				CommonError::Unknown { Description:"Failed to shutdown Cocoon after maximum retries".to_string() }
65			}))
66		}
67	}
68}