Skip to main content

camel_api/
in_flight.rs

1//! Context-global in-flight claim (drainclaim), Exchange-carried
2//! extension (claimfamily).
3//!
4//! [`InFlightClaim`] is one accepted-not-completed unit on the
5//! context-global counter (`CamelContext::total_in_flight()`). The type
6//! lives in camel-api — not camel-component-api — so [`crate::Exchange`]
7//! can carry a claim directly (rc-hllkk, rc-qbigm): stash sites behind
8//! `dyn` traits and raw-`Exchange` channels (resequencer buffers,
9//! embedded aggregator buckets) receive resident exchanges as bare
10//! `Exchange` values with no envelope, and their residency stays counted
11//! only if the claim rides the exchange itself.
12
13use std::fmt;
14use std::sync::Arc;
15use std::sync::atomic::{AtomicU64, Ordering};
16
17/// One accepted-not-completed unit on the context-global in-flight
18/// counter.
19///
20/// Attaching a claim increments the counter; dropping it decrements the
21/// counter exactly once (RAII), covering every release path — normal
22/// pipeline completion, dispatch push failure, queued-envelope drop,
23/// pipeline task abort, panic, and readiness failure — with no manual
24/// rollback code. Fanout sites mint one sibling claim per subscriber copy
25/// via [`InFlightClaim::split`], so each copy counts and releases
26/// independently (drainclaim). The type is deliberately NOT `Clone`:
27/// duplicating a claim would double-release on drop; fanout copies mint
28/// siblings via [`InFlightClaim::split`] instead.
29///
30/// Claim lifecycle during a pipeline run (claimfamily): the pipeline
31/// drain site holds the envelope's claim in task scope AND splits a
32/// sibling onto the exchange, so residency inside pipeline-embedded
33/// stash sites (resequencer buffer, aggregator bucket) stays counted
34/// after the pipeline task itself completes. Out-of-band stash emissions
35/// escape with their carried claims; the in-band pipeline result has its
36/// sibling taken back before the reply, so release stays at task end for
37/// exchanges that complete inside the pipeline.
38pub struct InFlightClaim(Arc<AtomicU64>);
39
40impl InFlightClaim {
41    /// Mint a claim against `counter`, incrementing it by one.
42    pub fn attach(counter: &Arc<AtomicU64>) -> Self {
43        counter.fetch_add(1, Ordering::AcqRel);
44        Self(Arc::clone(counter))
45    }
46
47    /// Mint a sibling claim for a fanout copy, incrementing the same
48    /// counter by one. The original claim stays live.
49    pub fn split(&self) -> Self {
50        self.0.fetch_add(1, Ordering::AcqRel);
51        Self(Arc::clone(&self.0))
52    }
53}
54
55impl Drop for InFlightClaim {
56    fn drop(&mut self) {
57        self.0.fetch_sub(1, Ordering::AcqRel);
58    }
59}
60
61impl fmt::Debug for InFlightClaim {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        // The live count is observable through the counter itself
64        // (`total_in_flight()`); the Debug shape deliberately avoids
65        // leaking the Arc pointer.
66        f.write_str("InFlightClaim")
67    }
68}
69
70#[cfg(test)]
71mod tests {
72    use super::*;
73
74    #[test]
75    fn claim_attach_increments_and_drop_decrements() {
76        let counter = Arc::new(AtomicU64::new(0));
77        let claim = InFlightClaim::attach(&counter);
78        assert_eq!(counter.load(Ordering::Acquire), 1);
79        drop(claim);
80        assert_eq!(counter.load(Ordering::Acquire), 0);
81    }
82
83    #[test]
84    fn claim_split_adds_one_sibling() {
85        let counter = Arc::new(AtomicU64::new(0));
86        let original = InFlightClaim::attach(&counter);
87        assert_eq!(counter.load(Ordering::Acquire), 1);
88        let sibling = original.split();
89        assert_eq!(counter.load(Ordering::Acquire), 2);
90        drop(sibling);
91        assert_eq!(counter.load(Ordering::Acquire), 1);
92        drop(original);
93        assert_eq!(counter.load(Ordering::Acquire), 0);
94    }
95
96    #[test]
97    fn claim_debug_does_not_leak_pointer() {
98        let counter = Arc::new(AtomicU64::new(0));
99        let claim = InFlightClaim::attach(&counter);
100        assert_eq!(format!("{claim:?}"), "InFlightClaim");
101    }
102}