#[cfg(test)]
use std::sync::Mutex;
use std::sync::{
Arc,
atomic::{AtomicBool, Ordering},
};
use query_planning::correlation::{QueryId, TraceId};
#[cfg(test)]
use crate::observability::RuntimeObservationDeliveryGate;
use crate::observability::{
AttemptReportSealProvider, QueryBoundaryKind, QueryCleanupSealDelivery, RuntimeObservationClock,
RuntimeObservationConfig, RuntimeObservationHandle, RuntimeObservationPayload, RuntimeObservationSession,
RuntimeTerminalOutcome, SealedRuntimeObservationSnapshot,
};
#[derive(Debug)]
pub(crate) struct AttemptObservation {
session: RuntimeObservationSession,
cleanup_started: AtomicBool,
#[cfg(test)]
delivery_gate: Mutex<Option<Arc<RuntimeObservationDeliveryGate>>>,
}
impl AttemptObservation {
pub(super) fn new(
query_id: QueryId,
trace_id: Option<TraceId>,
config: RuntimeObservationConfig,
clock: Arc<dyn RuntimeObservationClock>,
) -> Self {
let session = RuntimeObservationSession::with_clock(query_id, trace_id, config, clock);
session.handle().record(|| RuntimeObservationPayload::QueryBoundary { boundary: QueryBoundaryKind::Started });
Self {
session,
cleanup_started: AtomicBool::new(false),
#[cfg(test)]
delivery_gate: Mutex::new(None),
}
}
pub(crate) fn handle(&self) -> RuntimeObservationHandle {
self.session.handle()
}
pub(crate) fn begin_cleanup(&self) {
if !self.cleanup_started.swap(true, Ordering::AcqRel) {
self.handle()
.record(|| RuntimeObservationPayload::QueryBoundary { boundary: QueryBoundaryKind::CleanupStarted });
}
}
pub(crate) fn seal(
&self,
outcome: RuntimeTerminalOutcome,
report: &AttemptReportSealProvider,
) -> QueryCleanupSealDelivery {
self.begin_cleanup();
let handle = self.handle();
handle.record(|| RuntimeObservationPayload::QueryBoundary { boundary: QueryBoundaryKind::CleanupFinished });
let boundary = terminal_boundary(&outcome);
handle.record(|| RuntimeObservationPayload::QueryBoundary { boundary });
let delivery = self.session.seal_with_report(outcome, report);
#[cfg(test)]
let delivery = delivery
.with_delivery_gate(self.delivery_gate.lock().unwrap_or_else(std::sync::PoisonError::into_inner).take());
delivery
}
pub(crate) fn register_seal_observer<F>(&self, observer: F)
where F: Fn(SealedRuntimeObservationSnapshot) + Send + Sync + 'static {
self.session.register_seal_observer(observer);
}
#[cfg(test)]
pub(crate) fn sealed_snapshot(&self) -> Option<SealedRuntimeObservationSnapshot> {
self.session.sealed_snapshot()
}
#[cfg(test)]
pub(crate) fn arm_delivery_gate(&self) -> Arc<RuntimeObservationDeliveryGate> {
let gate = Arc::new(RuntimeObservationDeliveryGate::new());
let mut slot = self.delivery_gate.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
assert!(slot.is_none(), "an attempt observation can arm only one delivery gate");
*slot = Some(Arc::clone(&gate));
gate
}
}
fn terminal_boundary(outcome: &RuntimeTerminalOutcome) -> QueryBoundaryKind {
match outcome {
RuntimeTerminalOutcome::Succeeded => QueryBoundaryKind::Succeeded,
RuntimeTerminalOutcome::Failed { .. } => QueryBoundaryKind::Failed,
RuntimeTerminalOutcome::Cancelled => QueryBoundaryKind::Cancelled,
RuntimeTerminalOutcome::Dropped => QueryBoundaryKind::Dropped,
RuntimeTerminalOutcome::Panicked => QueryBoundaryKind::Panicked,
}
}