Skip to main content

DevelopmentNodeEnvironment_MicrosoftVSCodeDependency_22NodeVersion_Bundle_Clean_Debug_ElectronProfile_EsbuildCompiler_Mountain/Vine/Client/
Shared.rs

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