1use std::path::{Path, PathBuf};
41use std::sync::Arc;
42use std::sync::atomic::{AtomicU64, Ordering};
43
44use arc_swap::ArcSwap;
45use parking_lot::Mutex;
46
47use crate::shared_broadcast_ring::{BroadcastError, SharedBroadcastRing};
48
49#[derive(Debug)]
52pub enum BroadcastCapacityMorphError {
53 InvalidCapacity,
55 Broadcast(BroadcastError),
58 Io(std::io::Error),
60}
61
62impl From<BroadcastError> for BroadcastCapacityMorphError {
63 fn from(e: BroadcastError) -> Self { Self::Broadcast(e) }
64}
65
66impl From<std::io::Error> for BroadcastCapacityMorphError {
67 fn from(e: std::io::Error) -> Self { Self::Io(e) }
68}
69
70impl std::fmt::Display for BroadcastCapacityMorphError {
71 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72 match self {
73 Self::InvalidCapacity => write!(f, "capacity must be pow2 >= 2"),
74 Self::Broadcast(e) => write!(f, "broadcast ring error during morph: {e:?}"),
75 Self::Io(e) => write!(f, "io error during morph: {e}"),
76 }
77 }
78}
79
80impl std::error::Error for BroadcastCapacityMorphError {}
81
82pub struct CapacityBroadcastRing {
89 state: ArcSwap<BroadcastRingState>,
94 pin_generation: AtomicU64,
97 capacity_atom: AtomicU64,
99 backing_source: BroadcastBackingSource,
101 morph_seq: AtomicU64,
105 n_consumers: AtomicU64,
110 morph_lock: Mutex<()>,
112 warm: Mutex<Option<(usize, Arc<SharedBroadcastRing>)>>,
117 warm_hits: AtomicU64,
119}
120
121unsafe impl Send for CapacityBroadcastRing {}
122unsafe impl Sync for CapacityBroadcastRing {}
123
124struct BroadcastRingState {
130 active: Arc<SharedBroadcastRing>,
131 stale: Vec<Arc<SharedBroadcastRing>>,
132}
133
134enum BroadcastBackingSource {
136 Anon,
137 File(PathBuf),
138 Shm(String),
139}
140
141impl CapacityBroadcastRing {
142 pub fn create_anon(
144 initial_capacity: usize,
145 ) -> Result<Self, BroadcastCapacityMorphError> {
146 if !initial_capacity.is_power_of_two() || initial_capacity < 2 {
147 return Err(BroadcastCapacityMorphError::InvalidCapacity);
148 }
149 let ring = SharedBroadcastRing::create_anon(initial_capacity)?;
150 Ok(Self {
151 state: ArcSwap::from(Arc::new(BroadcastRingState {
152 active: Arc::new(ring),
153 stale: Vec::new(),
154 })),
155 pin_generation: AtomicU64::new(0),
156 capacity_atom: AtomicU64::new(initial_capacity as u64),
157 backing_source: BroadcastBackingSource::Anon,
158 morph_seq: AtomicU64::new(0),
159 n_consumers: AtomicU64::new(0),
160 morph_lock: Mutex::new(()),
161 warm: Mutex::new(None),
162 warm_hits: AtomicU64::new(0),
163 })
164 }
165
166 pub fn create(
169 base_path: impl AsRef<Path>,
170 initial_capacity: usize,
171 ) -> Result<Self, BroadcastCapacityMorphError> {
172 if !initial_capacity.is_power_of_two() || initial_capacity < 2 {
173 return Err(BroadcastCapacityMorphError::InvalidCapacity);
174 }
175 let base = base_path.as_ref().to_path_buf();
176 let path = path_for_capacity_seq(&base, initial_capacity, 0);
177 let ring = SharedBroadcastRing::create(&path, initial_capacity)?;
178 Ok(Self {
179 state: ArcSwap::from(Arc::new(BroadcastRingState {
180 active: Arc::new(ring),
181 stale: Vec::new(),
182 })),
183 pin_generation: AtomicU64::new(0),
184 capacity_atom: AtomicU64::new(initial_capacity as u64),
185 backing_source: BroadcastBackingSource::File(base),
186 morph_seq: AtomicU64::new(1),
187 n_consumers: AtomicU64::new(0),
188 morph_lock: Mutex::new(()),
189 warm: Mutex::new(None),
190 warm_hits: AtomicU64::new(0),
191 })
192 }
193
194 pub fn create_shmfs(
197 name_prefix: &str,
198 initial_capacity: usize,
199 ) -> Result<Self, BroadcastCapacityMorphError> {
200 if !initial_capacity.is_power_of_two() || initial_capacity < 2 {
201 return Err(BroadcastCapacityMorphError::InvalidCapacity);
202 }
203 let name = format!("{name_prefix}_cap_{initial_capacity}_g0");
204 let total = crate::shared_broadcast_ring::broadcast_file_size(initial_capacity);
205 let shm = crate::shm_file::ShmFile::create_or_open_named(&name, total)?;
206 let ring = SharedBroadcastRing::create_from_shm(shm, initial_capacity)?;
207 Ok(Self {
208 state: ArcSwap::from(Arc::new(BroadcastRingState {
209 active: Arc::new(ring),
210 stale: Vec::new(),
211 })),
212 pin_generation: AtomicU64::new(0),
213 capacity_atom: AtomicU64::new(initial_capacity as u64),
214 backing_source: BroadcastBackingSource::Shm(name_prefix.to_owned()),
215 morph_seq: AtomicU64::new(1),
216 n_consumers: AtomicU64::new(0),
217 morph_lock: Mutex::new(()),
218 warm: Mutex::new(None),
219 warm_hits: AtomicU64::new(0),
220 })
221 }
222
223 pub fn current_capacity(&self) -> usize {
225 self.capacity_atom.load(Ordering::Acquire) as usize
226 }
227
228 pub fn pin_generation(&self) -> u64 {
230 self.pin_generation.load(Ordering::Acquire)
231 }
232
233 pub fn register_consumer(&self) -> Result<usize, BroadcastError> {
242 let idx = self.state.load().active.register_consumer()?;
243 self.n_consumers.fetch_add(1, Ordering::AcqRel);
244 Ok(idx)
245 }
246
247 #[inline]
250 pub fn try_push(&self, payload: &[u8]) -> Result<(), BroadcastError> {
251 self.state.load().active.try_push(payload)
252 }
253
254 #[inline]
263 pub fn try_recv(
264 &self,
265 consumer_idx: usize,
266 out: &mut [u8],
267 ) -> Result<usize, BroadcastError> {
268 let state = self.state.load();
280 for ring in &state.stale {
281 loop {
282 match ring.try_recv(consumer_idx, out) {
283 Ok(n) => return Ok(n),
284 Err(_) => {
285 if ring.lag(consumer_idx) == 0 {
286 break;
287 }
288 std::hint::spin_loop();
289 }
290 }
291 }
292 }
293 state.active.try_recv(consumer_idx, out)
294 }
295
296 pub fn morph_capacity_to(
298 &self,
299 new_capacity: usize,
300 ) -> Result<(), BroadcastCapacityMorphError> {
301 let _morph_guard = self.morph_lock.lock();
302
303 if !new_capacity.is_power_of_two() || new_capacity < 2 {
304 return Err(BroadcastCapacityMorphError::InvalidCapacity);
305 }
306
307 let old_state = self.state.load_full();
308 let old = Arc::clone(&old_state.active);
309 let old_capacity = self.capacity_atom.load(Ordering::Acquire) as usize;
310 if old_capacity == new_capacity {
311 return Ok(());
312 }
313
314 let warm_hit = {
318 let mut warm = self.warm.lock();
319 warm.take_if(|(cap, _)| *cap == new_capacity)
320 };
321 let new = match warm_hit {
322 Some((_, ring)) => {
323 self.warm_hits.fetch_add(1, Ordering::Relaxed);
324 ring
325 }
326 None => self.build_backing(new_capacity)?,
327 };
328
329 let n = self.n_consumers.load(Ordering::Acquire) as usize;
334 for _ in 0..n {
335 new.register_consumer()?;
336 }
337
338 self.pin_generation.fetch_add(1, Ordering::AcqRel);
339
340 let mut new_stale: Vec<Arc<SharedBroadcastRing>> = old_state
346 .stale
347 .iter()
348 .filter(|r| !r.is_fully_drained())
349 .cloned()
350 .collect();
351 new_stale.push(old);
352 let new_state = BroadcastRingState { active: new, stale: new_stale };
353 self.state.store(Arc::new(new_state));
354 self.capacity_atom
355 .store(new_capacity as u64, Ordering::Release);
356
357 Ok(())
358 }
359
360 fn build_backing(
364 &self,
365 capacity: usize,
366 ) -> Result<Arc<SharedBroadcastRing>, BroadcastCapacityMorphError> {
367 let seq = self.morph_seq.fetch_add(1, Ordering::AcqRel);
368 let ring = match &self.backing_source {
369 BroadcastBackingSource::Anon => {
370 SharedBroadcastRing::create_anon(capacity)?
371 }
372 BroadcastBackingSource::File(base) => {
373 let path = path_for_capacity_seq(base, capacity, seq);
374 SharedBroadcastRing::create(&path, capacity)?
375 }
376 BroadcastBackingSource::Shm(prefix) => {
377 let name = format!("{prefix}_cap_{capacity}_g{seq}");
378 let total = crate::shared_broadcast_ring::broadcast_file_size(capacity);
379 let shm = crate::shm_file::ShmFile::create_or_open_named(&name, total)?;
380 SharedBroadcastRing::create_from_shm(shm, capacity)?
381 }
382 };
383 Ok(Arc::new(ring))
384 }
385
386 pub fn prewarm(&self, capacity: usize) -> Result<(), BroadcastCapacityMorphError> {
392 if !capacity.is_power_of_two() || capacity < 2 {
393 return Err(BroadcastCapacityMorphError::InvalidCapacity);
394 }
395 if self.warm.lock().as_ref().map(|(c, _)| *c) == Some(capacity) {
396 return Ok(());
397 }
398 let ring = self.build_backing(capacity)?;
399 *self.warm.lock() = Some((capacity, ring));
400 Ok(())
401 }
402
403 pub fn warm_capacity(&self) -> Option<usize> {
405 self.warm.lock().as_ref().map(|(c, _)| *c)
406 }
407
408 pub fn warm_hits(&self) -> u64 {
410 self.warm_hits.load(Ordering::Relaxed)
411 }
412
413 pub fn clear_warm(&self) {
416 *self.warm.lock() = None;
417 }
418
419 pub fn pin_current_capacity(&self) -> PinnedBroadcastCapacity<'_> {
421 let captured_gen = self.pin_generation.load(Ordering::Acquire);
422 let ring = Arc::clone(&self.state.load().active);
423 let capacity = self.capacity_atom.load(Ordering::Acquire) as usize;
424 PinnedBroadcastCapacity {
425 parent: self,
426 pinned_generation: captured_gen,
427 ring,
428 capacity,
429 _not_sync: std::marker::PhantomData,
430 }
431 }
432
433 pub fn ring_handle(&self) -> Arc<SharedBroadcastRing> {
435 Arc::clone(&self.state.load().active)
436 }
437}
438
439pub struct PinnedBroadcastCapacity<'a> {
442 parent: &'a CapacityBroadcastRing,
443 pinned_generation: u64,
444 ring: Arc<SharedBroadcastRing>,
445 capacity: usize,
446 _not_sync: std::marker::PhantomData<std::cell::Cell<()>>,
447}
448
449impl<'a> PinnedBroadcastCapacity<'a> {
450 pub fn is_still_valid(&self) -> bool {
451 self.parent.pin_generation.load(Ordering::Acquire) == self.pinned_generation
452 }
453 pub fn capacity(&self) -> usize { self.capacity }
454 pub fn generation(&self) -> u64 { self.pinned_generation }
455 pub fn ring(&self) -> &Arc<SharedBroadcastRing> { &self.ring }
456}
457
458fn path_for_capacity_seq(base: &Path, capacity: usize, seq: u64) -> PathBuf {
460 let mut s = base.as_os_str().to_owned();
461 s.push(format!(".cap_{capacity}_g{seq}.bin"));
462 PathBuf::from(s)
463}
464
465#[cfg(test)]
466mod tests {
467 use super::*;
468
469 #[test]
470 fn prewarm_hit_consumes_cache_and_broadcast_works() {
471 let ring = CapacityBroadcastRing::create_anon(64).unwrap();
472 let idx = ring.register_consumer().unwrap();
473 ring.try_push(&7u64.to_le_bytes()).unwrap();
474
475 ring.prewarm(256).unwrap();
476 assert_eq!(ring.warm_capacity(), Some(256));
477 ring.morph_capacity_to(256).unwrap();
478 assert_eq!(ring.warm_hits(), 1, "morph must consume the prediction");
479 assert_eq!(ring.warm_capacity(), None, "the slot is one-shot");
480 assert_eq!(ring.current_capacity(), 256);
481
482 ring.try_push(&9u64.to_le_bytes()).unwrap();
485 let mut out = [0u8; 64];
486 let n = ring.try_recv(idx, &mut out).unwrap();
487 assert!(n >= 8);
488 assert_eq!(u64::from_le_bytes(out[..8].try_into().unwrap()), 7);
489 let n = ring.try_recv(idx, &mut out).unwrap();
490 assert!(n >= 8);
491 assert_eq!(u64::from_le_bytes(out[..8].try_into().unwrap()), 9);
492 }
493
494 #[test]
495 fn prewarm_mismatch_stays_cached() {
496 let ring = CapacityBroadcastRing::create_anon(64).unwrap();
497 ring.prewarm(512).unwrap();
498 ring.morph_capacity_to(256).unwrap();
499 assert_eq!(ring.warm_hits(), 0);
500 assert_eq!(ring.warm_capacity(), Some(512));
501 ring.morph_capacity_to(512).unwrap();
502 assert_eq!(ring.warm_hits(), 1);
503 assert_eq!(ring.warm_capacity(), None);
504 }
505
506 #[test]
507 fn prewarm_rejects_non_pow2_and_clear_drops() {
508 let ring = CapacityBroadcastRing::create_anon(64).unwrap();
509 assert!(matches!(
510 ring.prewarm(100),
511 Err(BroadcastCapacityMorphError::InvalidCapacity)
512 ));
513 ring.prewarm(128).unwrap();
514 ring.clear_warm();
515 assert_eq!(ring.warm_capacity(), None);
516 }
517}