1use std::sync::atomic::{AtomicU32, AtomicU64, Ordering as AtomOrd};
29use std::time::Duration;
30
31use crate::locale_adaptive_ring::Locale;
32
33#[repr(u32)]
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum Durability {
38 Volatile = 0,
40 Transient = 1,
44 Persistent = 2,
47}
48
49impl Durability {
50 pub fn recommended_locale(self) -> Locale {
52 match self {
53 Self::Volatile => Locale::Anon,
54 Self::Transient => Locale::ShmFs,
55 Self::Persistent => Locale::File,
56 }
57 }
58
59 fn from_u32(tag: u32) -> Self {
60 match tag {
61 0 => Self::Volatile,
62 1 => Self::Transient,
63 2 => Self::Persistent,
64 _ => panic!("QosPolicy.durability corrupted: {tag}"),
65 }
66 }
67}
68
69#[repr(u32)]
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub enum Reliability {
74 BestEffort = 0,
78 Reliable = 1,
81}
82
83impl Reliability {
84 fn from_u32(tag: u32) -> Self {
85 match tag {
86 0 => Self::BestEffort,
87 1 => Self::Reliable,
88 _ => panic!("QosPolicy.reliability corrupted: {tag}"),
89 }
90 }
91}
92
93#[derive(Debug, Clone, Copy, PartialEq, Eq)]
96pub enum History {
97 KeepLastN(u32),
100 KeepAll,
103}
104
105#[repr(u32)]
118#[derive(Debug, Clone, Copy, PartialEq, Eq)]
119pub enum Ordering {
120 PerProducer = 0,
124 GlobalFifo = 1,
126}
127
128impl Ordering {
129 fn from_u32(tag: u32) -> Self {
130 match tag {
131 0 => Self::PerProducer,
132 1 => Self::GlobalFifo,
133 _ => panic!("QosPolicy.ordering corrupted: {tag}"),
134 }
135 }
136}
137
138impl History {
139 pub fn recommended_capacity(self) -> usize {
143 match self {
144 Self::KeepLastN(n) => (n as usize).next_power_of_two().max(16),
145 Self::KeepAll => 1024,
148 }
149 }
150
151 fn encode(self) -> u64 {
152 match self {
153 Self::KeepLastN(n) => (1u64 << 32) | u64::from(n),
154 Self::KeepAll => 0,
155 }
156 }
157
158 fn decode(raw: u64) -> Self {
159 if raw == 0 {
160 Self::KeepAll
161 } else {
162 let n = (raw & 0xFFFF_FFFF) as u32;
163 Self::KeepLastN(n)
164 }
165 }
166}
167
168pub struct QosPolicy {
172 durability_atom: AtomicU32,
173 reliability_atom: AtomicU32,
174 history_atom: AtomicU64,
175 max_latency_nanos: AtomicU64,
176 ordering_atom: AtomicU32,
177}
178
179impl QosPolicy {
180 pub fn new(
186 durability: Durability,
187 reliability: Reliability,
188 history: History,
189 max_latency: Duration,
190 ) -> Self {
191 Self {
192 durability_atom: AtomicU32::new(durability as u32),
193 reliability_atom: AtomicU32::new(reliability as u32),
194 history_atom: AtomicU64::new(history.encode()),
195 max_latency_nanos: AtomicU64::new(
196 max_latency.as_nanos().min(u64::MAX as u128) as u64,
197 ),
198 ordering_atom: AtomicU32::new(Ordering::PerProducer as u32),
199 }
200 }
201
202 pub fn streaming_default() -> Self {
205 Self::new(
206 Durability::Volatile,
207 Reliability::BestEffort,
208 History::KeepLastN(1024),
209 Duration::from_millis(100),
210 )
211 }
212
213 pub fn reliable_pubsub_default() -> Self {
217 Self::new(
218 Durability::Transient,
219 Reliability::Reliable,
220 History::KeepAll,
221 Duration::from_secs(1),
222 )
223 }
224
225 pub fn persistent_log_default() -> Self {
228 Self::new(
229 Durability::Persistent,
230 Reliability::Reliable,
231 History::KeepAll,
232 Duration::from_secs(5),
233 )
234 }
235
236 pub fn durability(&self) -> Durability {
238 Durability::from_u32(self.durability_atom.load(AtomOrd::Acquire))
239 }
240
241 pub fn reliability(&self) -> Reliability {
243 Reliability::from_u32(self.reliability_atom.load(AtomOrd::Acquire))
244 }
245
246 pub fn history(&self) -> History {
248 History::decode(self.history_atom.load(AtomOrd::Acquire))
249 }
250
251 pub fn max_latency(&self) -> Duration {
253 Duration::from_nanos(self.max_latency_nanos.load(AtomOrd::Acquire))
254 }
255
256 pub fn ordering(&self) -> Ordering {
258 Ordering::from_u32(self.ordering_atom.load(AtomOrd::Acquire))
259 }
260
261 pub fn set_durability(&self, durability: Durability) {
264 self.durability_atom.store(durability as u32, AtomOrd::Release);
265 }
266
267 pub fn set_reliability(&self, reliability: Reliability) {
269 self.reliability_atom.store(reliability as u32, AtomOrd::Release);
270 }
271
272 pub fn set_history(&self, history: History) {
274 self.history_atom.store(history.encode(), AtomOrd::Release);
275 }
276
277 pub fn set_max_latency(&self, max_latency: Duration) {
279 self.max_latency_nanos.store(
280 max_latency.as_nanos().min(u64::MAX as u128) as u64,
281 AtomOrd::Release,
282 );
283 }
284
285 pub fn set_ordering(&self, ordering: Ordering) {
290 self.ordering_atom.store(ordering as u32, AtomOrd::Release);
291 }
292
293 pub fn snapshot(&self) -> QosSnapshot {
298 QosSnapshot {
299 durability: self.durability(),
300 reliability: self.reliability(),
301 history: self.history(),
302 max_latency: self.max_latency(),
303 ordering: self.ordering(),
304 }
305 }
306}
307
308impl Default for QosPolicy {
309 fn default() -> Self { Self::streaming_default() }
310}
311
312#[derive(Debug, Clone, Copy)]
316pub struct QosSnapshot {
317 pub durability: Durability,
318 pub reliability: Reliability,
319 pub history: History,
320 pub max_latency: Duration,
321 pub ordering: Ordering,
322}
323
324impl QosSnapshot {
325 pub fn recommends_locale_change(self, current: Locale) -> Option<Locale> {
328 let recommended = self.durability.recommended_locale();
329 if recommended == current {
330 None
331 } else {
332 Some(recommended)
333 }
334 }
335
336 pub fn recommends_ordering_change(self, current: Ordering) -> Option<Ordering> {
342 if self.ordering == current {
343 None
344 } else {
345 Some(self.ordering)
346 }
347 }
348}
349
350#[cfg(test)]
351mod tests {
352 use super::*;
353
354 #[test]
355 fn defaults_load_with_sensible_values() {
356 let qos = QosPolicy::default();
357 assert_eq!(qos.durability(), Durability::Volatile);
358 assert_eq!(qos.reliability(), Reliability::BestEffort);
359 assert!(matches!(qos.history(), History::KeepLastN(_)));
360 assert_eq!(qos.max_latency(), Duration::from_millis(100));
361 }
362
363 #[test]
364 fn setters_update_atomic_fields() {
365 let qos = QosPolicy::default();
366 qos.set_durability(Durability::Persistent);
367 qos.set_reliability(Reliability::Reliable);
368 qos.set_history(History::KeepAll);
369 qos.set_max_latency(Duration::from_secs(10));
370
371 let snap = qos.snapshot();
372 assert_eq!(snap.durability, Durability::Persistent);
373 assert_eq!(snap.reliability, Reliability::Reliable);
374 assert!(matches!(snap.history, History::KeepAll));
375 assert_eq!(snap.max_latency, Duration::from_secs(10));
376 }
377
378 #[test]
379 fn durability_maps_to_locale() {
380 assert_eq!(Durability::Volatile.recommended_locale(), Locale::Anon);
381 assert_eq!(Durability::Transient.recommended_locale(), Locale::ShmFs);
382 assert_eq!(Durability::Persistent.recommended_locale(), Locale::File);
383 }
384
385 #[test]
386 fn history_recommended_capacity_powers_of_two() {
387 assert_eq!(History::KeepLastN(100).recommended_capacity(), 128);
388 assert_eq!(History::KeepLastN(1).recommended_capacity(), 16);
389 assert_eq!(History::KeepLastN(2048).recommended_capacity(), 2048);
390 assert_eq!(History::KeepAll.recommended_capacity(), 1024);
391 }
392
393 #[test]
394 fn snapshot_recommends_locale_change_only_when_different() {
395 let qos = QosPolicy::default(); let snap = qos.snapshot();
397 assert_eq!(snap.recommends_locale_change(Locale::Anon), None);
398 assert_eq!(snap.recommends_locale_change(Locale::File), Some(Locale::Anon));
399 assert_eq!(snap.recommends_locale_change(Locale::ShmFs), Some(Locale::Anon));
400 }
401
402 #[test]
403 fn ordering_knob_round_trips() {
404 let qos = QosPolicy::default();
405 assert_eq!(qos.ordering(), Ordering::PerProducer,
406 "ordering must default to the composed shapes' native guarantee");
407 qos.set_ordering(Ordering::GlobalFifo);
408 assert_eq!(qos.ordering(), Ordering::GlobalFifo);
409 assert_eq!(qos.snapshot().ordering, Ordering::GlobalFifo);
410 qos.set_ordering(Ordering::PerProducer);
411 assert_eq!(qos.snapshot().ordering, Ordering::PerProducer);
412 }
413
414 #[test]
415 fn snapshot_recommends_ordering_change_only_when_different() {
416 let qos = QosPolicy::default();
417 let snap = qos.snapshot();
418 assert_eq!(snap.recommends_ordering_change(Ordering::PerProducer), None);
419 assert_eq!(snap.recommends_ordering_change(Ordering::GlobalFifo),
420 Some(Ordering::PerProducer));
421
422 qos.set_ordering(Ordering::GlobalFifo);
423 let snap = qos.snapshot();
424 assert_eq!(snap.recommends_ordering_change(Ordering::PerProducer),
425 Some(Ordering::GlobalFifo));
426 assert_eq!(snap.recommends_ordering_change(Ordering::GlobalFifo), None);
427 }
428
429 #[test]
430 fn presets_match_expected_combos() {
431 let streaming = QosPolicy::streaming_default();
432 assert_eq!(streaming.durability(), Durability::Volatile);
433 assert_eq!(streaming.reliability(), Reliability::BestEffort);
434
435 let pubsub = QosPolicy::reliable_pubsub_default();
436 assert_eq!(pubsub.durability(), Durability::Transient);
437 assert_eq!(pubsub.reliability(), Reliability::Reliable);
438 assert!(matches!(pubsub.history(), History::KeepAll));
439
440 let log = QosPolicy::persistent_log_default();
441 assert_eq!(log.durability(), Durability::Persistent);
442 assert_eq!(log.reliability(), Reliability::Reliable);
443 }
444}