query_runtime/observability/recorder/
owner.rs1#[cfg(test)]
4use std::sync::atomic::{AtomicBool, Ordering};
5use std::{
6 cell::RefCell,
7 fmt,
8 sync::{Arc, Condvar, Mutex, MutexGuard},
9};
10
11#[cfg(any(test, feature = "test-hooks"))]
12use super::{AttemptReportProvider, RuntimeObservationFooter};
13use super::{
14 AttemptReportSealProvider, DeferredRuntimeObservationSeal, LiveRuntimeObservationSnapshot, QueryBoundaryKind,
15 QueryCleanupSealDelivery, QueryId, RuntimeObservationClock, RuntimeObservationConfig, RuntimeObservationHandle,
16 RuntimeObservationPayload, RuntimeObservationSealObserver, RuntimeObservationSession, RuntimeTerminalOutcome,
17 SealedRuntimeObservationSnapshot, TraceId,
18};
19
20#[derive(Clone)]
26pub struct RuntimeObservationOwner {
27 session: Arc<RuntimeObservationSession>,
28 lifecycle: Arc<RuntimeObservationOwnerLifecycle>,
29 #[cfg(any(test, feature = "test-hooks"))]
33 attempt_report_provider: Option<AttemptReportProvider>,
34}
35
36#[derive(Debug)]
37pub(super) struct RuntimeObservationOwnerLifecycle {
38 state: Mutex<RuntimeObservationOwnerLifecycleState>,
39 finished: Condvar,
40}
41
42#[cfg(test)]
48#[derive(Debug)]
49pub(crate) struct RuntimeObservationDeliveryGate {
50 entered: AtomicBool,
51 gate: std::sync::Barrier,
52}
53
54#[cfg(test)]
55impl RuntimeObservationDeliveryGate {
56 pub(crate) fn new() -> Self {
57 Self { entered: AtomicBool::new(false), gate: std::sync::Barrier::new(2) }
58 }
59
60 pub(crate) fn entered(&self) -> bool {
61 self.entered.load(Ordering::Acquire)
62 }
63
64 pub(crate) fn open(&self) {
65 self.gate.wait();
66 }
67
68 pub(super) fn hold(&self) {
70 self.entered.store(true, Ordering::Release);
71 self.gate.wait();
72 }
73}
74
75#[derive(Debug)]
76pub(super) enum RuntimeObservationOwnerLifecycleState {
77 Open {
78 cleanup_started: bool,
79 pending_outcome: Option<RuntimeTerminalOutcome>,
80 },
81 Finishing {
82 late_observers: Vec<RuntimeObservationSealObserver>,
83 },
84 Delivering {
85 snapshot: Option<SealedRuntimeObservationSnapshot>,
86 late_observers: Vec<RuntimeObservationSealObserver>,
87 },
88 Sealed(Option<SealedRuntimeObservationSnapshot>),
89}
90
91thread_local! {
92 static RUNTIME_OBSERVATION_DELIVERY_STACK: RefCell<Vec<Option<*const RuntimeObservationOwnerLifecycle>>> =
93 const { RefCell::new(Vec::new()) };
94}
95
96pub(crate) fn in_runtime_observation_delivery() -> bool {
101 RUNTIME_OBSERVATION_DELIVERY_STACK.with(|stack| !stack.borrow().is_empty())
102}
103
104fn in_owner_observation_delivery(lifecycle: &Arc<RuntimeObservationOwnerLifecycle>) -> bool {
105 let lifecycle = Arc::as_ptr(lifecycle);
106 RUNTIME_OBSERVATION_DELIVERY_STACK
107 .with(|stack| stack.borrow().iter().any(|active| active.is_some_and(|active| active == lifecycle)))
108}
109
110pub(super) struct RuntimeObservationDeliveryGuard {
111 lifecycle: Option<*const RuntimeObservationOwnerLifecycle>,
112}
113
114impl RuntimeObservationDeliveryGuard {
115 pub(super) fn enter(lifecycle: Option<&Arc<RuntimeObservationOwnerLifecycle>>) -> Self {
116 let lifecycle = lifecycle.map(Arc::as_ptr);
117 RUNTIME_OBSERVATION_DELIVERY_STACK.with(|stack| stack.borrow_mut().push(lifecycle));
118 Self { lifecycle }
119 }
120}
121
122impl Drop for RuntimeObservationDeliveryGuard {
123 fn drop(&mut self) {
124 RUNTIME_OBSERVATION_DELIVERY_STACK.with(|stack| {
125 let active = stack.borrow_mut().pop();
126 debug_assert_eq!(active, Some(self.lifecycle));
127 });
128 }
129}
130
131impl Default for RuntimeObservationOwnerLifecycle {
132 fn default() -> Self {
133 Self {
134 state: Mutex::new(RuntimeObservationOwnerLifecycleState::Open {
135 cleanup_started: false,
136 pending_outcome: None,
137 }),
138 finished: Condvar::new(),
139 }
140 }
141}
142
143impl fmt::Debug for RuntimeObservationOwner {
144 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
145 formatter
146 .debug_struct("RuntimeObservationOwner")
147 .field("query_id", &self.query_id().map(QueryId::as_str))
148 .field("enabled", &self.handle().is_enabled())
149 .finish_non_exhaustive()
150 }
151}
152
153impl RuntimeObservationOwner {
154 pub fn new(query_id: impl Into<QueryId>, trace_id: Option<TraceId>, config: RuntimeObservationConfig) -> Self {
156 Self::from_session(RuntimeObservationSession::new(query_id, trace_id, config))
157 }
158
159 pub fn with_clock(
161 query_id: impl Into<QueryId>,
162 trace_id: Option<TraceId>,
163 config: RuntimeObservationConfig,
164 clock: Arc<dyn RuntimeObservationClock>,
165 ) -> Self {
166 Self::from_session(RuntimeObservationSession::with_clock(query_id, trace_id, config, clock))
167 }
168
169 pub fn from_session(session: RuntimeObservationSession) -> Self {
171 let owner = Self {
172 session: Arc::new(session),
173 lifecycle: Arc::new(RuntimeObservationOwnerLifecycle::default()),
174 #[cfg(any(test, feature = "test-hooks"))]
175 attempt_report_provider: None,
176 };
177 owner.handle().record(|| RuntimeObservationPayload::QueryBoundary { boundary: QueryBoundaryKind::Started });
178 owner
179 }
180
181 #[cfg(any(test, feature = "test-hooks"))]
183 #[doc(hidden)]
184 pub fn with_attempt_report_provider(mut self, provider: AttemptReportProvider) -> Self {
185 self.attempt_report_provider = Some(provider);
186 self
187 }
188
189 #[cfg(any(test, feature = "test-hooks"))]
190 fn fixed_report_provider(&self) -> Option<AttemptReportSealProvider> {
191 self.attempt_report_provider.as_ref().map(|provider| {
192 let provider = Arc::clone(provider);
193 Arc::new(move |footer: &RuntimeObservationFooter, _: &_| {
194 let mut report = provider();
195 report.set_observation_completeness(footer);
196 Arc::new(report)
197 }) as AttemptReportSealProvider
198 })
199 }
200
201 pub fn disabled() -> Self {
203 Self::from_session(RuntimeObservationSession::disabled())
204 }
205
206 pub fn handle(&self) -> RuntimeObservationHandle {
208 self.session.handle()
209 }
210
211 pub fn query_id(&self) -> Option<&QueryId> {
213 self.session.query_id()
214 }
215
216 pub fn trace_id(&self) -> Option<&TraceId> {
218 self.session.trace_id()
219 }
220
221 pub fn config(&self) -> RuntimeObservationConfig {
223 self.session.config()
224 }
225
226 pub fn seal(&self, outcome: RuntimeTerminalOutcome) -> Option<SealedRuntimeObservationSnapshot> {
228 let outcome = loop {
229 let mut lifecycle = self.lock_lifecycle();
230 match &mut *lifecycle {
231 RuntimeObservationOwnerLifecycleState::Open { .. } => {
232 *lifecycle = RuntimeObservationOwnerLifecycleState::Finishing { late_observers: Vec::new() };
233 break outcome;
234 },
235 RuntimeObservationOwnerLifecycleState::Finishing { .. } => {
236 lifecycle = match self.lifecycle.finished.wait(lifecycle) {
237 Ok(lifecycle) => lifecycle,
238 Err(poisoned) => poisoned.into_inner(),
239 };
240 if let RuntimeObservationOwnerLifecycleState::Sealed(snapshot) = &*lifecycle {
241 return snapshot.clone();
242 }
243 },
244 RuntimeObservationOwnerLifecycleState::Delivering { snapshot, .. } => {
245 if in_owner_observation_delivery(&self.lifecycle) {
246 return snapshot.clone();
247 }
248 lifecycle = match self.lifecycle.finished.wait(lifecycle) {
249 Ok(lifecycle) => lifecycle,
250 Err(poisoned) => poisoned.into_inner(),
251 };
252 if let RuntimeObservationOwnerLifecycleState::Sealed(snapshot) = &*lifecycle {
253 return snapshot.clone();
254 }
255 },
256 RuntimeObservationOwnerLifecycleState::Sealed(snapshot) => return snapshot.clone(),
257 }
258 };
259 self.finish_owner_terminal(Some(outcome), None, |_| {})
260 }
261
262 pub(crate) fn record_terminal_outcome(&self, outcome: RuntimeTerminalOutcome) {
267 let mut lifecycle = self.lock_lifecycle();
268 if let RuntimeObservationOwnerLifecycleState::Open { pending_outcome, .. } = &mut *lifecycle {
269 *pending_outcome = Some(match pending_outcome.take() {
270 None | Some(RuntimeTerminalOutcome::Succeeded) => outcome,
271 Some(existing) => existing,
272 });
273 }
274 }
275
276 pub(crate) fn begin_query_cleanup(&self) {
278 let should_record = {
279 let mut lifecycle = self.lock_lifecycle();
280 match &mut *lifecycle {
281 RuntimeObservationOwnerLifecycleState::Open { cleanup_started, .. } if !*cleanup_started => {
282 *cleanup_started = true;
283 true
284 },
285 RuntimeObservationOwnerLifecycleState::Open { .. }
286 | RuntimeObservationOwnerLifecycleState::Finishing { .. }
287 | RuntimeObservationOwnerLifecycleState::Delivering { .. }
288 | RuntimeObservationOwnerLifecycleState::Sealed(_) => false,
289 }
290 };
291 if should_record {
292 self.handle()
293 .record(|| RuntimeObservationPayload::QueryBoundary { boundary: QueryBoundaryKind::CleanupStarted });
294 }
295 }
296
297 pub(crate) fn finish_query_cleanup(
300 &self,
301 fallback: RuntimeTerminalOutcome,
302 ) -> Option<SealedRuntimeObservationSnapshot> {
303 self.seal_query_cleanup(fallback).deliver()
304 }
305
306 pub(crate) fn seal_query_cleanup(&self, fallback: RuntimeTerminalOutcome) -> QueryCleanupSealDelivery {
311 self.seal_query_cleanup_with(CleanupSeal::Recorded { fallback: Some(fallback) })
312 }
313
314 fn seal_query_cleanup_with(&self, seal: CleanupSeal) -> QueryCleanupSealDelivery {
315 self.begin_query_cleanup();
316 let outcome = loop {
317 let mut lifecycle = self.lock_lifecycle();
318 match &mut *lifecycle {
319 RuntimeObservationOwnerLifecycleState::Open { pending_outcome, .. } => {
320 let pending_outcome = pending_outcome.take();
321 *lifecycle = RuntimeObservationOwnerLifecycleState::Finishing { late_observers: Vec::new() };
322 break match seal {
323 CleanupSeal::Recorded { fallback } => pending_outcome.or(fallback),
324 };
325 },
326 RuntimeObservationOwnerLifecycleState::Finishing { .. } => {
327 lifecycle = match self.lifecycle.finished.wait(lifecycle) {
328 Ok(lifecycle) => lifecycle,
329 Err(poisoned) => poisoned.into_inner(),
330 };
331 if let RuntimeObservationOwnerLifecycleState::Sealed(snapshot) = &*lifecycle {
332 return QueryCleanupSealDelivery::already_sealed(snapshot.clone());
333 }
334 },
335 RuntimeObservationOwnerLifecycleState::Delivering { snapshot, .. } => {
336 if in_owner_observation_delivery(&self.lifecycle) {
337 return QueryCleanupSealDelivery::already_sealed(snapshot.clone());
338 }
339 lifecycle = match self.lifecycle.finished.wait(lifecycle) {
340 Ok(lifecycle) => lifecycle,
341 Err(poisoned) => poisoned.into_inner(),
342 };
343 if let RuntimeObservationOwnerLifecycleState::Sealed(snapshot) = &*lifecycle {
344 return QueryCleanupSealDelivery::already_sealed(snapshot.clone());
345 }
346 },
347 RuntimeObservationOwnerLifecycleState::Sealed(snapshot) => {
348 return QueryCleanupSealDelivery::already_sealed(snapshot.clone());
349 },
350 }
351 };
352 let deferred = self.seal_owner_terminal(outcome, None, |outcome| {
353 self.handle()
354 .record(|| RuntimeObservationPayload::QueryBoundary { boundary: QueryBoundaryKind::CleanupFinished });
355 if let Some(outcome) = outcome {
356 let boundary = query_terminal_boundary(outcome);
357 self.handle().record(|| RuntimeObservationPayload::QueryBoundary { boundary });
358 }
359 });
360 QueryCleanupSealDelivery {
361 owner: Some(self.clone()),
362 deferred: Some(deferred),
363 already_sealed: None,
364 #[cfg(test)]
365 delivery_gate: None,
366 }
367 }
368
369 pub fn finish_query(&self, outcome: Option<RuntimeTerminalOutcome>) -> Option<SealedRuntimeObservationSnapshot> {
376 match outcome {
377 Some(outcome) => {
378 self.record_terminal_outcome(outcome.clone());
379 self.finish_query_cleanup(outcome)
380 },
381 None => self.seal_query_cleanup_with(CleanupSeal::Recorded { fallback: None }).deliver(),
382 }
383 }
384
385 pub fn live_snapshot(&self) -> Option<LiveRuntimeObservationSnapshot> {
387 self.session.live_snapshot()
388 }
389
390 pub fn sealed_snapshot(&self) -> Option<SealedRuntimeObservationSnapshot> {
392 self.session.sealed_snapshot()
393 }
394
395 pub fn register_seal_observer<F>(&self, observer: F)
403 where F: Fn(SealedRuntimeObservationSnapshot) + Send + Sync + 'static {
404 let mut observer = Some(RuntimeObservationSealObserver { notify: Box::new(observer) });
405 let immediate = {
406 let mut lifecycle = self.lock_lifecycle();
407 match &mut *lifecycle {
408 RuntimeObservationOwnerLifecycleState::Open { .. } => {
409 self.handle().register_seal_observer(observer.take().expect("observer exists"))
410 },
411 RuntimeObservationOwnerLifecycleState::Finishing { late_observers }
412 | RuntimeObservationOwnerLifecycleState::Delivering { late_observers, .. } => {
413 late_observers.push(observer.take().expect("observer exists"));
414 None
415 },
416 RuntimeObservationOwnerLifecycleState::Sealed(snapshot) => {
417 snapshot.clone().map(|snapshot| (observer.take().expect("observer exists"), snapshot))
418 },
419 }
420 };
421 if let Some((observer, snapshot)) = immediate {
422 observer.invoke(snapshot);
423 }
424 }
425
426 fn finish_owner_terminal<F>(
427 &self,
428 outcome: Option<RuntimeTerminalOutcome>,
429 attempt_report: Option<AttemptReportSealProvider>,
430 before_seal: F,
431 ) -> Option<SealedRuntimeObservationSnapshot>
432 where
433 F: FnOnce(Option<&RuntimeTerminalOutcome>),
434 {
435 let deferred = self.seal_owner_terminal(outcome, attempt_report, before_seal);
436 self.complete_observer_delivery(deferred)
437 }
438
439 fn seal_owner_terminal<F>(
440 &self,
441 outcome: Option<RuntimeTerminalOutcome>,
442 attempt_report: Option<AttemptReportSealProvider>,
443 before_seal: F,
444 ) -> DeferredRuntimeObservationSeal
445 where
446 F: FnOnce(Option<&RuntimeTerminalOutcome>),
447 {
448 let finishing = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
449 before_seal(outcome.as_ref());
450 #[cfg(any(test, feature = "test-hooks"))]
451 let attempt_report = attempt_report.or_else(|| self.fixed_report_provider());
452 let attempt_report_provider = outcome.as_ref().and(attempt_report);
453 self.session.seal_deferred(outcome, attempt_report_provider.as_ref())
454 }));
455 match finishing {
456 Ok(deferred) => deferred,
457 Err(panic) => {
458 self.handle().mark_degraded();
459 let deferred = self.session.seal_deferred(Some(RuntimeTerminalOutcome::Panicked), None);
460 let _ = self.complete_observer_delivery(deferred);
461 std::panic::resume_unwind(panic);
462 },
463 }
464 }
465
466 pub(super) fn complete_observer_delivery(
467 &self,
468 deferred: DeferredRuntimeObservationSeal,
469 ) -> Option<SealedRuntimeObservationSnapshot> {
470 let snapshot = deferred.snapshot.clone();
471 let mut observers = deferred.observers;
472 {
473 let mut lifecycle = self.lock_lifecycle();
474 let late_observers = match &mut *lifecycle {
475 RuntimeObservationOwnerLifecycleState::Finishing { late_observers } => std::mem::take(late_observers),
476 _ => unreachable!("terminal owner must publish before observer delivery"),
477 };
478 if snapshot.is_some() {
479 observers.extend(late_observers);
480 }
481 *lifecycle = RuntimeObservationOwnerLifecycleState::Delivering {
482 snapshot: snapshot.clone(),
483 late_observers: Vec::new(),
484 };
485 }
486 let _delivery = RuntimeObservationDeliveryGuard::enter(Some(&self.lifecycle));
487 loop {
488 if let Some(snapshot) = &snapshot {
489 for observer in observers.drain(..) {
490 observer.invoke(snapshot.clone());
491 }
492 }
493 let mut lifecycle = self.lock_lifecycle();
494 let late_observers = match &mut *lifecycle {
495 RuntimeObservationOwnerLifecycleState::Delivering { late_observers, .. } => {
496 std::mem::take(late_observers)
497 },
498 _ => unreachable!("observer delivery must retain terminal owner state"),
499 };
500 if late_observers.is_empty() || snapshot.is_none() {
501 *lifecycle = RuntimeObservationOwnerLifecycleState::Sealed(snapshot.clone());
502 self.lifecycle.finished.notify_all();
503 return snapshot;
504 }
505 observers = late_observers;
506 }
507 }
508
509 pub(super) fn lock_lifecycle(&self) -> MutexGuard<'_, RuntimeObservationOwnerLifecycleState> {
510 match self.lifecycle.state.lock() {
511 Ok(lifecycle) => lifecycle,
512 Err(poisoned) => poisoned.into_inner(),
513 }
514 }
515}
516
517impl Default for RuntimeObservationOwner {
518 fn default() -> Self {
519 Self::disabled()
520 }
521}
522
523enum CleanupSeal {
525 Recorded { fallback: Option<RuntimeTerminalOutcome> },
528}
529
530fn query_terminal_boundary(outcome: &RuntimeTerminalOutcome) -> QueryBoundaryKind {
531 match outcome {
532 RuntimeTerminalOutcome::Succeeded => QueryBoundaryKind::Succeeded,
533 RuntimeTerminalOutcome::Failed { .. } => QueryBoundaryKind::Failed,
534 RuntimeTerminalOutcome::Cancelled => QueryBoundaryKind::Cancelled,
535 RuntimeTerminalOutcome::Dropped => QueryBoundaryKind::Dropped,
536 RuntimeTerminalOutcome::Panicked => QueryBoundaryKind::Panicked,
537 }
538}