1use super::*;
2
3#[cfg(unix)]
10pub(super) async fn connect_verified(socket_path: &Path) -> std::io::Result<UnixStream> {
11 let stream = UnixStream::connect(socket_path).await?;
12 let own_euid = unsafe { libc::geteuid() } as u32;
14 let peer = crate::daemon::peer_uid(&stream)?;
15 if !crate::daemon::uid_is_permitted(peer, own_euid) {
16 return Err(std::io::Error::new(
17 std::io::ErrorKind::PermissionDenied,
18 format!(
19 "events socket {} answered by uid {peer}, not this process's uid {own_euid}; \
20 refusing to exchange event frames with an unowned daemon",
21 socket_path.display()
22 ),
23 ));
24 }
25 Ok(stream)
26}
27
28#[cfg(unix)]
32#[derive(Debug, Clone, Copy, Serialize)]
33pub struct EventsForwardingMetrics {
34 pub forwarded_batches: u64,
35 pub forwarded_events: u64,
36 pub dropped_batches: u64,
37 pub dropped_events: u64,
38 pub queued_bytes: usize,
40}
41
42#[cfg(unix)]
43#[derive(Debug, Default)]
44pub(super) struct ForwardingCounters {
45 pub(super) forwarded_batches: AtomicU64,
46 pub(super) forwarded_events: AtomicU64,
47 pub(super) dropped_batches: AtomicU64,
48 pub(super) dropped_events: AtomicU64,
49 pub(super) queued_bytes: AtomicUsize,
50}
51
52#[cfg(unix)]
55#[derive(Debug)]
56pub(super) struct AppendByteReservation {
57 pub(super) counters: Arc<ForwardingCounters>,
58 pub(super) bytes: usize,
59}
60
61#[cfg(unix)]
62impl Drop for AppendByteReservation {
63 fn drop(&mut self) {
64 self.counters
65 .queued_bytes
66 .fetch_sub(self.bytes, Ordering::AcqRel);
67 }
68}
69
70#[cfg(unix)]
71#[derive(Debug)]
72pub(super) struct QueuedAppend {
73 pub(super) request: EventsRequest,
74 pub(super) count: u64,
75 pub(super) _reservation: AppendByteReservation,
76}
77
78#[cfg(all(test, unix))]
79impl QueuedAppend {
80 pub(super) fn unmetered(
81 namespace: &str,
82 events: Vec<Event>,
83 counters: Arc<ForwardingCounters>,
84 ) -> Self {
85 let count = events.len() as u64;
86 Self {
87 request: EventsRequest::AppendEvents {
88 protocol_version: EVENTS_PROTOCOL_VERSION,
89 namespace: namespace.to_string(),
90 events,
91 },
92 count,
93 _reservation: AppendByteReservation { counters, bytes: 0 },
94 }
95 }
96}
97
98#[cfg(unix)]
101#[derive(Default)]
102pub(super) struct BoundedFrameCounter {
103 pub(super) bytes: usize,
104 pub(super) exceeded: bool,
105}
106
107#[cfg(unix)]
108impl std::io::Write for BoundedFrameCounter {
109 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
110 let next = self.bytes.saturating_add(buf.len());
111 if next > crate::daemon::MAX_FRAME_BYTES {
112 self.exceeded = true;
113 return Err(std::io::Error::new(
114 std::io::ErrorKind::InvalidData,
115 "events append request exceeds IPC frame cap",
116 ));
117 }
118 self.bytes = next;
119 Ok(buf.len())
120 }
121
122 fn flush(&mut self) -> std::io::Result<()> {
123 Ok(())
124 }
125}
126
127#[cfg(unix)]
128pub(super) fn reserve_append_bytes(
129 counters: &Arc<ForwardingCounters>,
130 bytes: usize,
131 budget: usize,
132) -> Option<AppendByteReservation> {
133 let mut used = counters.queued_bytes.load(Ordering::Acquire);
134 loop {
135 let next = used.checked_add(bytes)?;
136 if next > budget {
137 return None;
138 }
139 match counters.queued_bytes.compare_exchange_weak(
140 used,
141 next,
142 Ordering::AcqRel,
143 Ordering::Acquire,
144 ) {
145 Ok(_) => {
146 return Some(AppendByteReservation {
147 counters: Arc::clone(counters),
148 bytes,
149 });
150 }
151 Err(observed) => used = observed,
152 }
153 }
154}
155
156#[cfg(unix)]
164pub struct EventsSplitClient {
165 pub(super) socket_path: PathBuf,
166 pub(super) append_tx: tokio::sync::mpsc::Sender<QueuedAppend>,
167 pub(super) append_queue_byte_budget: usize,
168 pub(super) counters: Arc<ForwardingCounters>,
169 pub(super) outage_logged: Arc<AtomicBool>,
172 pub(super) preflight_store: Arc<dyn EventStore>,
174}
175
176#[cfg(unix)]
177impl std::fmt::Debug for EventsSplitClient {
178 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
179 f.debug_struct("EventsSplitClient")
180 .field("socket_path", &self.socket_path)
181 .finish_non_exhaustive()
182 }
183}
184
185#[cfg(unix)]
186impl EventsSplitClient {
187 pub fn new(socket_path: PathBuf) -> crate::error::RuntimeResult<Arc<Self>> {
189 Self::new_with_queue_depth(socket_path, DEFAULT_APPEND_QUEUE_BATCHES)
190 }
191
192 pub fn new_with_queue_depth(
195 socket_path: PathBuf,
196 queue_depth: usize,
197 ) -> crate::error::RuntimeResult<Arc<Self>> {
198 Self::new_with_queue_depth_and_delivery_timeout(socket_path, queue_depth, REQUEST_TIMEOUT)
199 }
200
201 pub(super) fn new_with_queue_depth_and_delivery_timeout(
205 socket_path: PathBuf,
206 queue_depth: usize,
207 delivery_timeout: Duration,
208 ) -> crate::error::RuntimeResult<Arc<Self>> {
209 Self::new_with_limits_and_delivery_timeout(
210 socket_path,
211 queue_depth,
212 DEFAULT_APPEND_QUEUE_BYTES,
213 delivery_timeout,
214 )
215 }
216
217 pub(super) fn new_with_limits_and_delivery_timeout(
218 socket_path: PathBuf,
219 queue_depth: usize,
220 byte_budget: usize,
221 delivery_timeout: Duration,
222 ) -> crate::error::RuntimeResult<Arc<Self>> {
223 validate_events_socket_path(&socket_path)?;
224 let preflight_backend = StorageBackend::memory()?;
225 let preflight_store = preflight_backend.events()?;
226 let (append_tx, append_rx) = tokio::sync::mpsc::channel::<QueuedAppend>(queue_depth.max(1));
230 let counters = Arc::new(ForwardingCounters::default());
231 let outage_logged = Arc::new(AtomicBool::new(false));
232
233 let client = Arc::new(Self {
234 socket_path: socket_path.clone(),
235 append_tx,
236 append_queue_byte_budget: byte_budget,
237 counters: Arc::clone(&counters),
238 outage_logged: Arc::clone(&outage_logged),
239 preflight_store,
240 });
241
242 crate::daemon::spawn_named_tracked_task(
243 "events_forwarder",
244 run_forwarder(
245 socket_path,
246 append_rx,
247 counters,
248 outage_logged,
249 delivery_timeout,
250 crate::daemon::daemon_shutdown_token(),
251 ),
252 );
253 Ok(client)
254 }
255
256 pub fn metrics(&self) -> EventsForwardingMetrics {
258 EventsForwardingMetrics {
259 forwarded_batches: self.counters.forwarded_batches.load(Ordering::Relaxed),
260 forwarded_events: self.counters.forwarded_events.load(Ordering::Relaxed),
261 dropped_batches: self.counters.dropped_batches.load(Ordering::Relaxed),
262 dropped_events: self.counters.dropped_events.load(Ordering::Relaxed),
263 queued_bytes: self.counters.queued_bytes.load(Ordering::Acquire),
264 }
265 }
266
267 pub(super) fn enqueue(&self, namespace: &str, events: Vec<Event>) {
270 let count = events.len() as u64;
271 let request = EventsRequest::AppendEvents {
272 protocol_version: EVENTS_PROTOCOL_VERSION,
273 namespace: namespace.to_string(),
274 events,
275 };
276 let mut frame_size = BoundedFrameCounter::default();
277 if let Err(error) = serde_json::to_writer(&mut frame_size, &request) {
278 self.count_append_drop(
279 count,
280 if frame_size.exceeded {
281 "frame cap"
282 } else {
283 "serialization"
284 },
285 );
286 tracing::warn!(error = %error, "events append batch could not fit a wire frame");
287 return;
288 }
289 let Some(reservation) = reserve_append_bytes(
290 &self.counters,
291 frame_size.bytes,
292 self.append_queue_byte_budget,
293 ) else {
294 self.count_append_drop(count, "queue byte budget");
295 return;
296 };
297 let batch = QueuedAppend {
298 request,
299 count,
300 _reservation: reservation,
301 };
302 match self.append_tx.try_send(batch) {
303 Ok(()) => {}
304 Err(_) => {
305 self.count_append_drop(count, "queue batch limit");
307 }
308 }
309 }
310
311 pub(super) fn count_append_drop(&self, count: u64, reason: &'static str) {
312 self.counters
313 .dropped_batches
314 .fetch_add(1, Ordering::Relaxed);
315 self.counters
316 .dropped_events
317 .fetch_add(count, Ordering::Relaxed);
318 if !self.outage_logged.swap(true, Ordering::Relaxed) {
319 tracing::warn!(
320 dropped_events = count,
321 reason,
322 "events append queue rejected loss-tolerant batch"
323 );
324 }
325 }
326
327 pub(super) async fn round_trip(
332 &self,
333 request: &EventsRequest,
334 ) -> StorageResult<EventsResponse> {
335 let op = "events daemon round-trip";
336 let payload = serialize_events_request(request)?;
337 let attempt = events_round_trip_attempt(&self.socket_path, &payload);
338 let bytes = match tokio::time::timeout(REQUEST_TIMEOUT, attempt).await {
339 Ok(Ok(bytes)) => bytes,
340 Ok(Err(("read", error))) => {
341 return Err(StorageError::Serialization {
342 capability: khive_storage::StorageCapability::Events,
343 message: format!(
344 "events daemon closed or broke the response after connection at {}: {error}",
345 self.socket_path.display()
346 ),
347 });
348 }
349 Ok(Err((stage, error))) => {
350 return Err(StorageError::Pool {
351 operation: op.into(),
352 message: format!(
353 "events daemon {stage} failed at {}: {error}",
354 self.socket_path.display()
355 ),
356 });
357 }
358 Err(_elapsed) => {
359 return Err(StorageError::Timeout {
360 operation: op.into(),
361 });
362 }
363 };
364
365 deserialize_events_response(&bytes)
366 }
367
368 pub(super) async fn round_trip_count(
369 &self,
370 request: &EventsRequest,
371 budget: &CountReadBudget,
372 ) -> StorageResult<EventsResponse> {
373 budget.scope(async {
374 let payload = serialize_events_request(request)?;
375 let attempt = events_round_trip_attempt(&self.socket_path, &payload);
376 let result = khive_storage::await_request_read_phase("events daemon count round-trip", attempt)
377 .await.map_err(|error| budget.attribute(error))?;
378 let bytes = match result {
379 Ok(bytes) => bytes,
380 Err(("read", error)) => return Err(StorageError::Serialization {
381 capability: khive_storage::StorageCapability::Events,
382 message: format!("events daemon closed or broke the response after connection at {}: {error}", self.socket_path.display()),
383 }),
384 Err((stage, error)) => return Err(StorageError::Pool {
385 operation: "events daemon count round-trip".into(),
386 message: format!("events daemon {stage} failed at {}: {error}", self.socket_path.display()),
387 }),
388 };
389 deserialize_events_response(&bytes)
390 }).await
391 }
392}
393
394#[cfg(unix)]
395pub(super) fn serialize_events_request(request: &EventsRequest) -> StorageResult<Vec<u8>> {
396 serde_json::to_vec(request).map_err(|error| StorageError::Serialization {
397 capability: khive_storage::StorageCapability::Events,
398 message: format!("events request serialization failed: {error}"),
399 })
400}
401
402#[cfg(unix)]
403pub(super) fn deserialize_events_response(bytes: &[u8]) -> StorageResult<EventsResponse> {
404 serde_json::from_slice(bytes).map_err(|error| StorageError::Serialization {
405 capability: khive_storage::StorageCapability::Events,
406 message: format!("events response deserialization failed: {error}"),
407 })
408}
409
410#[cfg(unix)]
411pub(super) async fn events_round_trip_attempt(
412 socket_path: &Path,
413 payload: &[u8],
414) -> Result<Vec<u8>, (&'static str, std::io::Error)> {
415 let mut stream = connect_verified(socket_path)
416 .await
417 .map_err(|error| ("connect", error))?;
418 write_frame(&mut stream, payload)
419 .await
420 .map_err(|error| ("write", error))?;
421 read_frame(&mut stream)
422 .await
423 .map_err(|error| ("read", error))
424}
425
426#[cfg(unix)]
433pub(super) fn drain_dropped_queue(
434 rx: &mut tokio::sync::mpsc::Receiver<QueuedAppend>,
435 counters: &ForwardingCounters,
436) {
437 let mut dropped_batches = 0u64;
438 let mut dropped_events = 0u64;
439 while let Ok(batch) = rx.try_recv() {
440 dropped_batches += 1;
441 dropped_events += batch.count;
442 }
444 if dropped_batches > 0 {
445 counters
446 .dropped_batches
447 .fetch_add(dropped_batches, Ordering::Relaxed);
448 counters
449 .dropped_events
450 .fetch_add(dropped_events, Ordering::Relaxed);
451 tracing::warn!(
452 dropped_batches,
453 dropped_events,
454 "events forwarder shutting down; dropping queued loss-tolerant batches"
455 );
456 }
457}
458
459#[cfg(unix)]
464pub(super) async fn run_forwarder(
465 socket_path: PathBuf,
466 mut rx: tokio::sync::mpsc::Receiver<QueuedAppend>,
467 counters: Arc<ForwardingCounters>,
468 outage_logged: Arc<AtomicBool>,
469 delivery_timeout: Duration,
470 shutdown: tokio_util::sync::CancellationToken,
471) {
472 let mut conn: Option<UnixStream> = None;
480 loop {
481 let batch = tokio::select! {
482 _ = shutdown.cancelled() => {
483 drain_dropped_queue(&mut rx, &counters);
484 break;
485 },
486 received = rx.recv() => match received {
487 Some(batch) => batch,
488 None => break,
489 },
490 };
491 let count = batch.count;
492 let payload = match serde_json::to_vec(&batch.request) {
493 Ok(payload) => payload,
494 Err(error) => {
495 counters.dropped_batches.fetch_add(1, Ordering::Relaxed);
496 counters.dropped_events.fetch_add(count, Ordering::Relaxed);
497 tracing::error!(error = %error, "events forwarder serialization failed; batch dropped");
498 continue;
499 }
500 };
501
502 let delivered = tokio::select! {
510 _ = shutdown.cancelled() => {
511 counters.dropped_batches.fetch_add(1, Ordering::Relaxed);
512 counters.dropped_events.fetch_add(count, Ordering::Relaxed);
513 tracing::warn!(
514 dropped_events = count,
515 "events forwarder shutting down mid-delivery; in-flight batch dropped"
516 );
517 drain_dropped_queue(&mut rx, &counters);
518 break;
519 },
520 outcome = tokio::time::timeout(
521 delivery_timeout,
522 deliver_batch(&socket_path, &mut conn, &payload),
523 ) => match outcome {
524 Ok(delivered) => delivered,
525 Err(_elapsed) => {
526 conn = None;
527 false
528 }
529 },
530 };
531 if delivered {
532 counters.forwarded_batches.fetch_add(1, Ordering::Relaxed);
533 counters
534 .forwarded_events
535 .fetch_add(count, Ordering::Relaxed);
536 if outage_logged.swap(false, Ordering::Relaxed) {
537 tracing::info!("events daemon reachable again; forwarding resumed");
538 }
539 } else {
540 counters.dropped_batches.fetch_add(1, Ordering::Relaxed);
541 counters.dropped_events.fetch_add(count, Ordering::Relaxed);
542 if !outage_logged.swap(true, Ordering::Relaxed) {
543 tracing::warn!(
544 socket = %socket_path.display(),
545 "events daemon unreachable; dropping loss-tolerant events until it returns"
546 );
547 }
548 tokio::select! {
549 _ = shutdown.cancelled() => {
550 drain_dropped_queue(&mut rx, &counters);
551 break;
552 },
553 _ = tokio::time::sleep(FORWARDER_BACKOFF) => {}
554 }
555 }
556 }
557}
558
559#[cfg(unix)]
564pub(super) async fn deliver_batch(
565 socket_path: &Path,
566 conn: &mut Option<UnixStream>,
567 payload: &[u8],
568) -> bool {
569 for _attempt in 0..2u8 {
570 if conn.is_none() {
571 match connect_verified(socket_path).await {
572 Ok(stream) => *conn = Some(stream),
573 Err(_) => return false,
574 }
575 }
576 let stream = conn.as_mut().expect("connection populated above");
577 let ok = async {
578 write_frame(stream, payload).await?;
579 let bytes = read_frame(stream).await?;
580 std::io::Result::Ok(bytes)
581 }
582 .await;
583 match ok {
584 Ok(bytes) => {
585 return !matches!(
586 serde_json::from_slice::<EventsResponse>(&bytes),
587 Ok(EventsResponse::Error { .. }) | Err(_)
588 );
589 }
590 Err(_) => {
591 *conn = None;
594 }
595 }
596 }
597 false
598}