Skip to main content

DevelopmentNodeEnvironment_MicrosoftVSCodeDependency_22NodeVersion_Bundle_Clean_Debug_ElectronProfile_EsbuildCompiler_Mountain/Vine/Client/
Shared.rs

1#![allow(non_snake_case)]
2
3//! Module-private state for the Vine client: connection pool, per-
4//! connection metadata, the broadcast fan-out, the shutdown flag, plus
5//! the constants and message-size validator that every entry-point shares.
6
7use std::{
8	collections::HashMap,
9	sync::{
10		Arc,
11		OnceLock,
12		atomic::{AtomicBool, Ordering},
13	},
14	time::Instant,
15};
16
17use lazy_static::lazy_static;
18use parking_lot::Mutex;
19use tokio::sync::Notify;
20
21use crate::Vine::{Client::NotificationFrame, Error::VineError, Generated::cocoon_service_client::CocoonServiceClient};
22
23/// Cocoon gRPC client over a tonic transport channel.
24pub type CocoonClient = CocoonServiceClient<tonic::transport::Channel>;
25
26/// Default timeout for RPC calls.
27pub const DEFAULT_TIMEOUT_MS:u64 = 5000;
28
29/// Maximum number of retry attempts for failed connections.
30pub const MAX_RETRY_ATTEMPTS:usize = 3;
31
32/// Base delay between retry attempts.
33pub const RETRY_BASE_DELAY_MS:u64 = 100;
34
35/// Maximum message size for validation (4 MB to match the tonic default).
36pub const MAX_MESSAGE_SIZE_BYTES:usize = 4 * 1024 * 1024;
37
38/// Health-check interval.
39pub const HEALTH_CHECK_INTERVAL_MS:u64 = 30000;
40
41/// Connection timeout (currently unused - kept for the streaming variant).
42#[allow(dead_code)]
43pub const CONNECTION_TIMEOUT_MS:u64 = 10000;
44
45/// Notification broadcast capacity (drop-oldest when full). 4096 covers
46/// the worst-case storms (sky://diagnostics/changed at 50-200/s during
47/// rust-analyzer cargo-check) with margin.
48pub const NOTIFICATION_BROADCAST_CAPACITY:usize = 4096;
49
50/// Connection metadata tracking health and last activity.
51pub struct ConnectionMetadata {
52	pub LastActivity:Instant,
53
54	pub FailureCount:usize,
55
56	pub IsHealthy:bool,
57}
58
59lazy_static! {
60	pub static ref SIDECAR_CLIENTS: Arc<Mutex<HashMap<String, CocoonClient>>> = Arc::new(Mutex::new(HashMap::new()));
61	pub static ref CONNECTION_METADATA: Arc<Mutex<HashMap<String, ConnectionMetadata>>> =
62		Arc::new(Mutex::new(HashMap::new()));
63	pub static ref NOTIFICATION_BROADCAST: tokio::sync::broadcast::Sender<NotificationFrame::Struct> = {
64		let (Sender, _) = tokio::sync::broadcast::channel(NOTIFICATION_BROADCAST_CAPACITY);
65
66		Sender
67	};
68}
69
70/// Per-sidecar connection-ready notifiers. Keyed by the sidecar identifier
71/// (e.g. `"cocoon-main"`). Callers that need the connection before issuing an
72/// RPC can `await` the `Notify` instead of polling `IsClientConnected`.
73/// The `Notify` is created lazily on first `GetConnectionNotify` call and
74/// fired (`notify_waiters`) once in `ConnectToSideCar` after a successful
75/// handshake. Subsequent calls see a pre-fired notifier and wake immediately.
76static CONNECTION_NOTIFIERS:OnceLock<Arc<parking_lot::RwLock<HashMap<String, Arc<Notify>>>>> = OnceLock::new();
77
78pub fn GetConnectionNotify(SideCarIdentifier:&str) -> Arc<Notify> {
79	let Map = CONNECTION_NOTIFIERS.get_or_init(|| Arc::new(parking_lot::RwLock::new(HashMap::new())));
80
81	{
82		let Read = Map.read();
83
84		if let Some(Notify) = Read.get(SideCarIdentifier) {
85			return Notify.clone();
86		}
87	}
88
89	let mut Write = Map.write();
90
91	Write
92		.entry(SideCarIdentifier.to_string())
93		.or_insert_with(|| Arc::new(Notify::new()))
94		.clone()
95}
96
97pub fn FireConnectionNotify(SideCarIdentifier:&str) {
98	if let Some(Map) = CONNECTION_NOTIFIERS.get() {
99		if let Some(Notifier) = Map.read().get(SideCarIdentifier) {
100			Notifier.notify_waiters();
101		}
102	}
103}
104
105/// Process-wide shutdown flag. Set to `true` once Mountain has issued
106/// `$shutdown` (or SIGKILL'd) Cocoon. After that point all
107/// `SendNotification` / `SendRequest` calls short-circuit.
108pub static SHUTDOWN_FLAG:AtomicBool = AtomicBool::new(false);
109
110pub fn ShutdownFlagStore(Value:bool) { SHUTDOWN_FLAG.store(Value, Ordering::Relaxed); }
111
112pub fn ShutdownFlagLoad() -> bool { SHUTDOWN_FLAG.load(Ordering::Relaxed) }
113
114/// Increment the failure counter and mark the connection unhealthy.
115pub fn RecordSideCarFailure(SideCarIdentifier:&str) {
116	let mut Metadata = CONNECTION_METADATA.lock();
117
118	if let Some(Connection) = Metadata.get_mut(SideCarIdentifier) {
119		Connection.FailureCount += 1;
120
121		Connection.IsHealthy = false;
122	}
123}
124
125/// Refresh the last-activity timestamp and reset the failure counter.
126pub fn UpdateSideCarActivity(SideCarIdentifier:&str) {
127	let mut Metadata = CONNECTION_METADATA.lock();
128
129	if let Some(Connection) = Metadata.get_mut(SideCarIdentifier) {
130		Connection.LastActivity = Instant::now();
131
132		Connection.FailureCount = 0;
133
134		Connection.IsHealthy = true;
135	}
136}
137
138/// Reject messages above `MAX_MESSAGE_SIZE_BYTES` to bound the worst-case
139/// gRPC frame. Mirrors tonic's own check so we don't pay the codec round-
140/// trip for an oversize payload.
141pub fn ValidateMessageSize(Data:&[u8]) -> Result<(), VineError> {
142	if Data.len() > MAX_MESSAGE_SIZE_BYTES {
143		Err(VineError::MessageTooLarge { ActualSize:Data.len(), MaxSize:MAX_MESSAGE_SIZE_BYTES })
144	} else {
145		Ok(())
146	}
147}