1#![allow(clippy::missing_errors_doc)]
40
41use std::future::Future;
42use std::marker::PhantomData;
43use std::path::Path;
44use std::pin::Pin;
45use std::sync::{Arc, OnceLock};
46use std::sync::atomic::{AtomicBool, Ordering};
47use std::task::{Context, Poll, Waker};
48use std::time::{Duration, Instant};
49
50use parking_lot::Mutex;
51use subetha_core::Marshal;
52
53use crate::cross_process_waker::{CrossProcessWaker, WakerError, MAX_WAITERS_DEFAULT};
54use crate::dispatch_deque::DequeVariant;
55use crate::message_transport::TransportError;
56use crate::mmf_dispatcher::{MmfDispatcher, MmfFamily, MmfWorkloadShape};
57use crate::reactor::{spawn_seq_reactor, SeqReactor};
58use crate::shared_deque::SharedDeque;
59use crate::shared_hash_map::{InsertOutcome, MapError, SharedHashMap};
60use crate::shared_ring::{RingError, SharedRing, PAYLOAD_BYTES};
61
62pub(crate) const BLOCKING_HEAL: Duration = Duration::from_millis(1);
66
67#[derive(Debug)]
69pub enum ApiError {
70 Transport(TransportError),
72 Marshal(subetha_core::MarshalError),
74 Io(std::io::Error),
76 Map(MapError),
78 WrongFamily { wanted: &'static str, got: MmfFamily },
80 PayloadTooLarge,
82 Timeout,
85}
86
87impl std::fmt::Display for ApiError {
88 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89 match self {
90 Self::Transport(e) => write!(f, "transport: {e:?}"),
91 Self::Marshal(e) => write!(f, "marshal: {e:?}"),
92 Self::Io(e) => write!(f, "io: {e}"),
93 Self::Map(e) => write!(f, "map: {e:?}"),
94 Self::WrongFamily { wanted, got } => {
95 write!(f, "wrong family: wanted {wanted}, got {got:?}")
96 }
97 Self::PayloadTooLarge => write!(f, "payload too large for transport"),
98 Self::Timeout => write!(f, "blocking op timed out"),
99 }
100 }
101}
102
103impl std::error::Error for ApiError {}
104
105impl From<std::io::Error> for ApiError {
106 fn from(e: std::io::Error) -> Self {
107 Self::Io(e)
108 }
109}
110impl From<subetha_core::MarshalError> for ApiError {
111 fn from(e: subetha_core::MarshalError) -> Self {
112 Self::Marshal(e)
113 }
114}
115impl From<TransportError> for ApiError {
116 fn from(e: TransportError) -> Self {
117 Self::Transport(e)
118 }
119}
120impl From<MapError> for ApiError {
121 fn from(e: MapError) -> Self {
122 Self::Map(e)
123 }
124}
125impl From<crate::shared_ring::RingError> for ApiError {
126 fn from(e: crate::shared_ring::RingError) -> Self {
127 match e {
128 crate::shared_ring::RingError::Full => {
129 ApiError::Transport(TransportError::Full)
130 }
131 crate::shared_ring::RingError::Empty => {
132 ApiError::Transport(TransportError::Empty)
133 }
134 crate::shared_ring::RingError::PayloadTooLarge => {
135 ApiError::Transport(TransportError::PayloadTooLarge)
136 }
137 _ => ApiError::Transport(TransportError::Other),
138 }
139 }
140}
141
142pub struct Channel<T: Marshal> {
150 ring: Arc<SharedRing>,
151 consumer_waker: Arc<CrossProcessWaker>,
153 producer_waker: Arc<CrossProcessWaker>,
155 recv_slot: Arc<Mutex<Option<Waker>>>,
158 send_slot: Arc<Mutex<Option<Waker>>>,
160 recv_reactor: OnceLock<SeqReactor>,
163 send_reactor: OnceLock<SeqReactor>,
164 has_recv_waiter: AtomicBool,
167 has_send_waiter: AtomicBool,
168 family: MmfFamily,
169 _phantom: PhantomData<T>,
170}
171
172fn waker_paths(base: &Path) -> (std::path::PathBuf, std::path::PathBuf) {
173 let mut cw = base.as_os_str().to_owned();
174 cw.push(".cw");
175 let mut pw = base.as_os_str().to_owned();
176 pw.push(".pw");
177 (std::path::PathBuf::from(cw), std::path::PathBuf::from(pw))
178}
179
180fn ring_err(e: RingError) -> ApiError {
181 match e {
182 RingError::Full => ApiError::Transport(TransportError::Full),
183 RingError::Empty => ApiError::Transport(TransportError::Empty),
184 RingError::PayloadTooLarge => ApiError::PayloadTooLarge,
185 RingError::IoError(k) => ApiError::Io(std::io::Error::from(k)),
186 _ => ApiError::Transport(TransportError::Other),
187 }
188}
189
190impl<T: Marshal> Channel<T> {
191 fn assemble(
192 ring: SharedRing,
193 consumer_waker: CrossProcessWaker,
194 producer_waker: CrossProcessWaker,
195 family: MmfFamily,
196 ) -> Self {
197 Self {
198 ring: Arc::new(ring),
199 consumer_waker: Arc::new(consumer_waker),
200 producer_waker: Arc::new(producer_waker),
201 recv_slot: Arc::new(Mutex::new(None)),
202 send_slot: Arc::new(Mutex::new(None)),
203 recv_reactor: OnceLock::new(),
204 send_reactor: OnceLock::new(),
205 has_recv_waiter: AtomicBool::new(false),
206 has_send_waiter: AtomicBool::new(false),
207 family,
208 _phantom: PhantomData,
209 }
210 }
211
212 #[inline]
217 fn ring_push(&self, payload: &[u8]) -> Result<(), RingError> {
218 SharedRing::try_push(&self.ring, payload)
219 }
220
221 #[inline]
222 fn ring_pop(&self, out: &mut [u8]) -> Result<usize, RingError> {
223 SharedRing::try_pop(&self.ring, out)
224 }
225
226 pub fn create(
231 path: impl AsRef<Path>,
232 shape: MmfWorkloadShape,
233 capacity: usize,
234 ) -> Result<Self, ApiError> {
235 let family = MmfDispatcher::pick(shape);
236 if family != MmfFamily::SharedRing {
237 return Err(ApiError::WrongFamily {
238 wanted: "SharedRing",
239 got: family,
240 });
241 }
242 if T::PAYLOAD_BYTES > PAYLOAD_BYTES {
243 return Err(ApiError::PayloadTooLarge);
244 }
245 let (cw, pw) = waker_paths(path.as_ref());
246 let ring = SharedRing::create(path.as_ref(), capacity)?;
247 let consumer_waker = CrossProcessWaker::create(cw, MAX_WAITERS_DEFAULT)
248 .map_err(map_waker)?;
249 let producer_waker = CrossProcessWaker::create(pw, MAX_WAITERS_DEFAULT)
250 .map_err(map_waker)?;
251 Ok(Self::assemble(ring, consumer_waker, producer_waker, family))
252 }
253
254 pub fn open(path: impl AsRef<Path>, capacity: usize) -> Result<Self, ApiError> {
256 if T::PAYLOAD_BYTES > PAYLOAD_BYTES {
257 return Err(ApiError::PayloadTooLarge);
258 }
259 let (cw, pw) = waker_paths(path.as_ref());
260 let ring = SharedRing::open(path.as_ref(), capacity)?;
261 let consumer_waker = CrossProcessWaker::open(cw, MAX_WAITERS_DEFAULT)
262 .map_err(map_waker)?;
263 let producer_waker = CrossProcessWaker::open(pw, MAX_WAITERS_DEFAULT)
264 .map_err(map_waker)?;
265 Ok(Self::assemble(
266 ring, consumer_waker, producer_waker, MmfFamily::SharedRing,
267 ))
268 }
269
270 fn signal_consumer(&self) {
274 if !self.has_recv_waiter.load(Ordering::Relaxed) {
275 return;
276 }
277 if let Some(w) = self.recv_slot.lock().take() {
278 w.wake();
279 }
280 self.consumer_waker.wake_up_to(self.ring.producer_seq());
281 }
282
283 fn signal_producer(&self) {
285 if !self.has_send_waiter.load(Ordering::Relaxed) {
286 return;
287 }
288 if let Some(w) = self.send_slot.lock().take() {
289 w.wake();
290 }
291 self.producer_waker.wake_up_to(self.ring.consumer_seq());
292 }
293
294 fn ensure_recv_reactor(&self) {
295 self.recv_reactor.get_or_init(|| {
296 let ring = Arc::clone(&self.ring);
297 spawn_seq_reactor(
298 Arc::new(move || ring.producer_seq()),
299 Arc::clone(&self.consumer_waker),
300 Arc::clone(&self.recv_slot),
301 )
302 });
303 }
304
305 fn ensure_send_reactor(&self) {
306 self.send_reactor.get_or_init(|| {
307 let ring = Arc::clone(&self.ring);
308 spawn_seq_reactor(
309 Arc::new(move || ring.consumer_seq()),
310 Arc::clone(&self.producer_waker),
311 Arc::clone(&self.send_slot),
312 )
313 });
314 }
315
316 fn marshal_buf(item: &T) -> ([u8; PAYLOAD_BYTES], usize) {
317 let mut buf = [0u8; PAYLOAD_BYTES];
318 item.marshal(&mut buf[..T::PAYLOAD_BYTES]);
319 (buf, T::PAYLOAD_BYTES)
320 }
321
322 fn unmarshal_buf(buf: &[u8], n: usize) -> Result<T, ApiError> {
323 Ok(T::unmarshal(&buf[..n.min(T::PAYLOAD_BYTES.max(1))])?)
324 }
325
326 pub fn send(&self, item: &T) -> Result<(), ApiError> {
328 let (buf, len) = Self::marshal_buf(item);
329 self.ring_push(&buf[..len]).map_err(ring_err)?;
330 self.signal_consumer();
331 Ok(())
332 }
333
334 pub fn recv(&self) -> Result<T, ApiError> {
336 let mut buf = [0u8; PAYLOAD_BYTES];
337 let n = self.ring_pop(&mut buf).map_err(ring_err)?;
338 self.signal_producer();
339 Self::unmarshal_buf(&buf, n)
340 }
341
342 pub fn send_blocking(
345 &self,
346 item: &T,
347 timeout: Option<Duration>,
348 ) -> Result<(), ApiError> {
349 self.has_send_waiter.store(true, Ordering::Relaxed);
350 let (buf, len) = Self::marshal_buf(item);
351 let deadline = timeout.map(|d| Instant::now() + d);
352 loop {
353 match self.ring_push(&buf[..len]) {
354 Ok(()) => {
355 self.signal_consumer();
356 return Ok(());
357 }
358 Err(RingError::Full) => {}
359 Err(e) => return Err(ring_err(e)),
360 }
361 let seen = self.ring.consumer_seq();
362 let token = self.producer_waker.try_park(seen + 1).map_err(map_waker)?;
363 match self.ring_push(&buf[..len]) {
366 Ok(()) => {
367 self.producer_waker.release(token);
368 self.signal_consumer();
369 return Ok(());
370 }
371 Err(RingError::Full) => {}
372 Err(e) => {
373 self.producer_waker.release(token);
374 return Err(ring_err(e));
375 }
376 }
377 match wait_heal(&self.producer_waker, token, deadline) {
378 Ok(()) => continue,
379 Err(e) => {
380 return Err(e);
381 }
382 }
383 }
384 }
385
386 pub fn recv_blocking(&self, timeout: Option<Duration>) -> Result<T, ApiError> {
389 self.has_recv_waiter.store(true, Ordering::Relaxed);
390 let deadline = timeout.map(|d| Instant::now() + d);
391 let mut buf = [0u8; PAYLOAD_BYTES];
392 loop {
393 match self.ring_pop(&mut buf) {
394 Ok(n) => {
395 self.signal_producer();
396 return Self::unmarshal_buf(&buf, n);
397 }
398 Err(RingError::Empty) => {}
399 Err(e) => return Err(ring_err(e)),
400 }
401 let seen = self.ring.producer_seq();
402 let token = self.consumer_waker.try_park(seen + 1).map_err(map_waker)?;
403 match self.ring_pop(&mut buf) {
404 Ok(n) => {
405 self.consumer_waker.release(token);
406 self.signal_producer();
407 return Self::unmarshal_buf(&buf, n);
408 }
409 Err(RingError::Empty) => {}
410 Err(e) => {
411 self.consumer_waker.release(token);
412 return Err(ring_err(e));
413 }
414 }
415 match wait_heal(&self.consumer_waker, token, deadline) {
416 Ok(()) => continue,
417 Err(e) => return Err(e),
418 }
419 }
420 }
421
422 pub fn recv_async(&self) -> RecvFut<'_, T> {
426 self.has_recv_waiter.store(true, Ordering::Relaxed);
427 self.ensure_recv_reactor();
428 RecvFut { chan: self }
429 }
430
431 pub fn send_async(&self, item: &T) -> SendFut<'_, T> {
434 self.has_send_waiter.store(true, Ordering::Relaxed);
435 self.ensure_send_reactor();
436 let (buf, len) = Self::marshal_buf(item);
437 SendFut { chan: self, buf, len }
438 }
439
440 pub fn family(&self) -> MmfFamily {
442 self.family
443 }
444}
445
446pub(crate) fn map_waker(e: WakerError) -> ApiError {
447 match e {
448 WakerError::Timeout => ApiError::Timeout,
449 WakerError::IoError(k) => ApiError::Io(std::io::Error::from(k)),
450 _ => ApiError::Transport(TransportError::Other),
451 }
452}
453
454pub(crate) fn wait_heal(
458 waker: &CrossProcessWaker,
459 token: crate::cross_process_waker::WakerToken,
460 deadline: Option<Instant>,
461) -> Result<(), ApiError> {
462 let wait_for = match deadline {
463 None => BLOCKING_HEAL,
464 Some(d) => {
465 let now = Instant::now();
466 if now >= d {
467 waker.release(token);
468 return Err(ApiError::Timeout);
469 }
470 (d - now).min(BLOCKING_HEAL)
471 }
472 };
473 match waker.wait(token, Some(wait_for)) {
474 Ok(()) | Err(WakerError::Timeout) => Ok(()),
475 Err(e) => Err(map_waker(e)),
476 }
477}
478
479pub struct RecvFut<'a, T: Marshal> {
481 chan: &'a Channel<T>,
482}
483
484impl<'a, T: Marshal> Future for RecvFut<'a, T> {
485 type Output = Result<T, ApiError>;
486
487 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
488 let c = self.chan;
489 let mut buf = [0u8; PAYLOAD_BYTES];
490 if let Ok(n) = c.ring_pop(&mut buf) {
491 c.signal_producer();
492 return Poll::Ready(Channel::<T>::unmarshal_buf(&buf, n));
493 }
494 *c.recv_slot.lock() = Some(cx.waker().clone());
495 if let Ok(n) = c.ring_pop(&mut buf) {
496 c.signal_producer();
497 return Poll::Ready(Channel::<T>::unmarshal_buf(&buf, n));
498 }
499 Poll::Pending
500 }
501}
502
503pub struct SendFut<'a, T: Marshal> {
505 chan: &'a Channel<T>,
506 buf: [u8; PAYLOAD_BYTES],
507 len: usize,
508}
509
510impl<'a, T: Marshal> Future for SendFut<'a, T> {
511 type Output = Result<(), ApiError>;
512
513 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
514 let this = self.get_mut();
515 if this.chan.ring_push(&this.buf[..this.len]).is_ok() {
516 this.chan.signal_consumer();
517 return Poll::Ready(Ok(()));
518 }
519 *this.chan.send_slot.lock() = Some(cx.waker().clone());
520 if this.chan.ring_push(&this.buf[..this.len]).is_ok() {
521 this.chan.signal_consumer();
522 return Poll::Ready(Ok(()));
523 }
524 Poll::Pending
525 }
526}
527
528pub struct WorkStealQueue<T: Marshal + Copy + 'static> {
540 owner: Arc<SharedDeque<T>>,
541 variant: DequeVariant,
542}
543
544impl<T: Marshal + Copy + 'static> WorkStealQueue<T> {
545 pub fn create(
548 path: impl AsRef<Path>,
549 shape: MmfWorkloadShape,
550 capacity: usize,
551 ) -> Result<Self, ApiError> {
552 let family = MmfDispatcher::pick(shape);
553 let variant = match family {
554 MmfFamily::SharedDeque(v) => v,
555 other => {
556 return Err(ApiError::WrongFamily {
557 wanted: "SharedDeque",
558 got: other,
559 });
560 }
561 };
562 let owner = SharedDeque::<T>::create(path.as_ref(), capacity)?;
563 Ok(Self {
564 owner: Arc::new(owner),
565 variant,
566 })
567 }
568
569 pub fn open_as_thief(path: impl AsRef<Path>) -> Result<Self, ApiError> {
571 let thief = SharedDeque::<T>::open_as_thief(path.as_ref())?;
572 Ok(Self {
573 owner: Arc::new(thief),
574 variant: DequeVariant::ChaseLev,
575 })
576 }
577
578 pub fn push(&self, item: &T) -> Result<(), ApiError> {
580 self.owner.push(item)?;
581 Ok(())
582 }
583
584 pub fn pop(&self) -> Option<T> {
586 self.owner.pop()
587 }
588
589 pub fn steal(&self) -> Option<T> {
591 self.owner.steal()
592 }
593
594 pub fn variant(&self) -> DequeVariant {
596 self.variant
597 }
598}
599
600impl From<crate::shared_deque::DequeError> for ApiError {
601 fn from(e: crate::shared_deque::DequeError) -> Self {
602 match e {
603 crate::shared_deque::DequeError::Full => {
604 ApiError::Transport(TransportError::Full)
605 }
606 _ => ApiError::Transport(TransportError::Other),
607 }
608 }
609}
610
611pub struct KvMap<K: Copy + Eq + Send + Sync + 'static, V: Copy + Send + Sync + 'static> {
618 map: Arc<SharedHashMap<K, V>>,
619}
620
621impl<K, V> KvMap<K, V>
622where
623 K: Copy + Eq + std::hash::Hash + Send + Sync + 'static,
624 V: Copy + Send + Sync + 'static,
625{
626 pub fn create(
629 path: impl AsRef<Path>,
630 shape: MmfWorkloadShape,
631 capacity: usize,
632 ) -> Result<Self, ApiError> {
633 let family = MmfDispatcher::pick(shape);
634 if family != MmfFamily::SharedHashMap {
635 return Err(ApiError::WrongFamily {
636 wanted: "SharedHashMap",
637 got: family,
638 });
639 }
640 let map = SharedHashMap::<K, V>::create(path.as_ref(), capacity)?;
641 Ok(Self { map: Arc::new(map) })
642 }
643
644 pub fn insert(&self, key: K, value: V) -> Result<InsertOutcome, ApiError> {
646 let outcome = self.map.insert(key, value)?;
647 Ok(outcome)
648 }
649
650 pub fn get(&self, key: &K) -> Option<V> {
652 self.map.get(key)
653 }
654
655 pub fn len(&self) -> usize {
657 self.map.len()
658 }
659
660 pub fn is_empty(&self) -> bool {
662 self.map.len() == 0
663 }
664}
665
666pub struct AutoIpc {
694 path: std::path::PathBuf,
695 n_producers: usize,
696 n_consumers: usize,
697 batch_size: Option<usize>,
698 wait_idle: bool,
699 capacity: usize,
700 ordering: crate::qos_policy::Ordering,
701 auto_order: Option<f64>,
702}
703
704impl AutoIpc {
705 pub fn new(path: impl Into<std::path::PathBuf>) -> Self {
709 Self {
710 path: path.into(),
711 n_producers: 1,
712 n_consumers: 1,
713 batch_size: None,
714 wait_idle: false,
715 capacity: 64,
716 ordering: crate::qos_policy::Ordering::PerProducer,
717 auto_order: None,
718 }
719 }
720
721 pub fn producers(mut self, n: usize) -> Self {
723 self.n_producers = n.max(1);
724 self
725 }
726
727 pub fn consumers(mut self, n: usize) -> Self {
729 self.n_consumers = n.max(1);
730 self
731 }
732
733 pub fn batch_size(mut self, k: usize) -> Self {
737 self.batch_size = Some(k);
738 self
739 }
740
741 pub fn idle_wait(mut self, on: bool) -> Self {
744 self.wait_idle = on;
745 self
746 }
747
748 pub fn capacity(mut self, n: usize) -> Self {
751 self.capacity = n.max(2).next_power_of_two();
752 self
753 }
754
755 pub fn ordering(mut self, ordering: crate::qos_policy::Ordering) -> Self {
761 self.ordering = ordering;
762 self
763 }
764
765 pub fn auto_order(mut self, threshold: f64) -> Self {
772 self.auto_order = Some(threshold);
773 self
774 }
775
776 pub fn inferred_shape(&self) -> MmfWorkloadShape {
778 if self.ordering == crate::qos_policy::Ordering::GlobalFifo {
781 return MmfWorkloadShape::StreamingMpmc {
782 n_producers: self.n_producers,
783 n_consumers: self.n_consumers,
784 };
785 }
786 if self.batch_size.is_some() || self.wait_idle {
791 MmfWorkloadShape::WorkStealing(
792 crate::dispatch_deque::WorkloadShape {
793 n_thieves: self.n_consumers,
794 batch_size: self.batch_size,
795 wait_idle: self.wait_idle,
796 },
797 )
798 } else if self.n_producers >= 2 || self.n_consumers >= 2 {
799 MmfWorkloadShape::StreamingMpmc {
800 n_producers: self.n_producers,
801 n_consumers: self.n_consumers,
802 }
803 } else {
804 MmfWorkloadShape::StreamingMpmc {
807 n_producers: 1,
808 n_consumers: 1,
809 }
810 }
811 }
812
813 pub fn inferred_family(&self) -> MmfFamily {
815 MmfDispatcher::pick(self.inferred_shape())
816 }
817
818 pub fn build_channel<T: Marshal>(self) -> Result<Channel<T>, ApiError> {
823 let shape = self.inferred_shape();
824 Channel::<T>::create(&self.path, shape, self.capacity)
825 }
826
827 pub fn build_work_steal_queue<T: Marshal + Copy + 'static>(
832 self,
833 ) -> Result<WorkStealQueue<T>, ApiError> {
834 if self.ordering == crate::qos_policy::Ordering::GlobalFifo {
835 return Err(ApiError::WrongFamily {
836 wanted: "SharedRing (GlobalFifo ordering declared)",
837 got: MmfFamily::SharedDeque(
838 crate::dispatch_deque::DequeVariant::ChaseLev,
839 ),
840 });
841 }
842 let shape = MmfWorkloadShape::WorkStealing(
844 crate::dispatch_deque::WorkloadShape {
845 n_thieves: self.n_consumers,
846 batch_size: self.batch_size,
847 wait_idle: self.wait_idle,
848 },
849 );
850 WorkStealQueue::<T>::create(&self.path, shape, self.capacity)
851 }
852
853 pub fn build_adaptive<T: Marshal + Copy + 'static>(
860 self,
861 ) -> Result<crate::AdaptiveIpc<T>, ApiError> {
862 let shape = self.inferred_shape();
863 crate::AdaptiveIpc::<T>::create_with_ordering(
864 &self.path,
865 shape,
866 self.capacity,
867 self.n_consumers,
868 self.ordering,
869 self.auto_order,
870 )
871 }
872
873 pub fn build_kv_map<K, V>(self) -> Result<KvMap<K, V>, ApiError>
877 where
878 K: Copy + Eq + std::hash::Hash + Send + Sync + 'static,
879 V: Copy + Send + Sync + 'static,
880 {
881 let shape = MmfWorkloadShape::KeyValueLookup {
882 n_readers: self.n_consumers,
883 n_writers: self.n_producers,
884 };
885 KvMap::<K, V>::create(&self.path, shape, self.capacity)
886 }
887}
888
889#[cfg(test)]
890mod tests {
891 use super::*;
892 use crate::dispatch_deque::WorkloadShape;
893
894 fn tmp(name: &str) -> std::path::PathBuf {
895 let mut p = std::env::temp_dir();
896 let pid = std::process::id();
897 let nonce = std::time::SystemTime::now()
898 .duration_since(std::time::UNIX_EPOCH)
899 .map(|d| d.as_nanos())
900 .unwrap_or(0);
901 p.push(format!("subetha_api_{pid}_{nonce}_{name}.bin"));
902 p
903 }
904
905 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
907 struct U32Item(u32);
908
909 unsafe impl Marshal for U32Item {
910 const PAYLOAD_BYTES: usize = 4;
911 fn marshal(&self, dst: &mut [u8]) {
912 dst[..4].copy_from_slice(&self.0.to_le_bytes());
913 }
914 fn unmarshal(src: &[u8]) -> Result<Self, subetha_core::MarshalError> {
915 if src.len() < 4 {
916 return Err(subetha_core::MarshalError::ShortBuffer {
917 expected: 4,
918 got: src.len(),
919 });
920 }
921 Ok(U32Item(u32::from_le_bytes(src[..4].try_into().unwrap())))
922 }
923 }
924
925 #[test]
926 fn channel_round_trips_via_streaming_shape() {
927 let path = tmp("channel");
928 let shape = MmfWorkloadShape::StreamingMpmc {
929 n_producers: 1,
930 n_consumers: 1,
931 };
932 let chan: Channel<U32Item> = Channel::create(&path, shape, 64).expect("create");
933 assert_eq!(chan.family(), MmfFamily::SharedRing);
934 chan.send(&U32Item(42)).expect("send");
935 let v = chan.recv().expect("recv");
936 assert_eq!(v, U32Item(42));
937 std::fs::remove_file(&path).ok();
938 }
939
940 #[test]
941 fn channel_rejects_wrong_family() {
942 let path = tmp("channel_wrong_family");
943 let bad_shape = MmfWorkloadShape::KeyValueLookup {
944 n_readers: 1,
945 n_writers: 1,
946 };
947 let result = Channel::<U32Item>::create(&path, bad_shape, 64);
948 match result {
949 Err(ApiError::WrongFamily {
950 wanted: "SharedRing",
951 got: MmfFamily::SharedHashMap,
952 }) => {}
953 Err(other) => panic!("expected WrongFamily, got {other:?}"),
954 Ok(_) => panic!("expected error, got Ok"),
955 }
956 std::fs::remove_file(&path).ok();
957 }
958
959 #[test]
960 fn work_steal_queue_round_trips_via_request_reply_shape() {
961 let path = tmp("wsq");
962 let shape = MmfWorkloadShape::WorkStealing(WorkloadShape::request_reply());
963 let q: WorkStealQueue<u64> = WorkStealQueue::create(&path, shape, 64).expect("create");
964 assert_eq!(q.variant(), DequeVariant::ChaseLev);
966 q.push(&100).expect("push");
967 q.push(&200).expect("push");
968 assert_eq!(q.pop(), Some(200));
970 assert_eq!(q.steal(), Some(100));
972 std::fs::remove_file(&path).ok();
973 }
974
975 #[test]
976 fn kv_map_round_trips_via_key_value_shape() {
977 let path = tmp("kv");
978 let shape = MmfWorkloadShape::KeyValueLookup {
979 n_readers: 1,
980 n_writers: 1,
981 };
982 let map: KvMap<u32, u32> = KvMap::create(&path, shape, 64).expect("create");
983 for k in 0..10u32 {
984 map.insert(k, k * k).expect("insert");
985 }
986 for k in 0..10u32 {
987 assert_eq!(map.get(&k), Some(k * k));
988 }
989 assert_eq!(map.len(), 10);
990 std::fs::remove_file(&path).ok();
991 }
992
993 #[test]
994 fn auto_ipc_default_infers_streaming_one_to_one() {
995 let auto = AutoIpc::new("/tmp/test-default.bin");
996 let shape = auto.inferred_shape();
997 assert!(matches!(shape, MmfWorkloadShape::StreamingMpmc { .. }));
998 assert_eq!(auto.inferred_family(), MmfFamily::SharedRing);
999 }
1000
1001 #[test]
1002 fn auto_ipc_multi_producer_infers_streaming_mpmc() {
1003 let auto = AutoIpc::new("/tmp/test-mp.bin").producers(4).consumers(4);
1004 assert_eq!(auto.inferred_family(), MmfFamily::SharedRing);
1005 }
1006
1007 #[test]
1008 fn auto_ipc_batch_hint_flips_to_work_stealing() {
1009 let auto = AutoIpc::new("/tmp/test-batch.bin").batch_size(64);
1010 let shape = auto.inferred_shape();
1011 assert!(matches!(shape, MmfWorkloadShape::WorkStealing(_)));
1012 assert_eq!(
1014 auto.inferred_family(),
1015 MmfFamily::SharedDeque(DequeVariant::Khl)
1016 );
1017 }
1018
1019 #[test]
1020 fn auto_ipc_multi_consumer_plus_batch_infers_urd() {
1021 let auto = AutoIpc::new("/tmp/test-mt.bin")
1022 .consumers(4)
1023 .batch_size(64);
1024 assert_eq!(
1025 auto.inferred_family(),
1026 MmfFamily::SharedDeque(DequeVariant::Urd)
1027 );
1028 }
1029
1030 #[test]
1031 fn auto_ipc_idle_wait_routes_to_urd() {
1032 let auto = AutoIpc::new("/tmp/test-idle.bin").idle_wait(true);
1033 assert_eq!(
1034 auto.inferred_family(),
1035 MmfFamily::SharedDeque(DequeVariant::Urd)
1036 );
1037 }
1038
1039 #[test]
1040 fn auto_ipc_build_channel_end_to_end_round_trip() {
1041 let path = tmp("auto_ch");
1042 let auto = AutoIpc::new(&path).capacity(64);
1043 let chan: Channel<U32Item> = auto.build_channel().expect("build");
1044 chan.send(&U32Item(123)).expect("send");
1045 let v = chan.recv().expect("recv");
1046 assert_eq!(v, U32Item(123));
1047 std::fs::remove_file(&path).ok();
1048 }
1049
1050 #[test]
1051 fn auto_ipc_rounds_a_non_pow2_capacity_up_for_every_terminal() {
1052 let ch_path = tmp("auto_cap_ch");
1057 let chan: Channel<U32Item> = AutoIpc::new(&ch_path)
1058 .capacity(100)
1059 .build_channel()
1060 .expect("100 rounds to 128");
1061 chan.send(&U32Item(5)).expect("send");
1062 assert_eq!(chan.recv().expect("recv"), U32Item(5));
1063 std::fs::remove_file(&ch_path).ok();
1064
1065 let q_path = tmp("auto_cap_wsq");
1066 let q: WorkStealQueue<u64> = AutoIpc::new(&q_path)
1067 .batch_size(8)
1068 .capacity(100)
1069 .build_work_steal_queue()
1070 .expect("100 rounds to 128");
1071 q.push(&1).expect("push");
1072 assert_eq!(q.pop(), Some(1));
1073 std::fs::remove_file(&q_path).ok();
1074
1075 let z_path = tmp("auto_cap_zero");
1078 let zero: Channel<U32Item> = AutoIpc::new(&z_path)
1079 .capacity(0)
1080 .build_channel()
1081 .expect("0 clamps to 2");
1082 zero.send(&U32Item(9)).expect("send");
1083 assert_eq!(zero.recv().expect("recv"), U32Item(9));
1084 std::fs::remove_file(&z_path).ok();
1085 }
1086
1087 #[test]
1088 fn auto_ipc_build_work_steal_queue_with_batch_hint() {
1089 let path = tmp("auto_wsq");
1090 let q: WorkStealQueue<u64> = AutoIpc::new(&path)
1091 .batch_size(8)
1092 .capacity(64)
1093 .build_work_steal_queue()
1094 .expect("build");
1095 q.push(&10).expect("push");
1096 q.push(&20).expect("push");
1097 assert_eq!(q.pop(), Some(20));
1098 assert_eq!(q.steal(), Some(10));
1099 std::fs::remove_file(&path).ok();
1100 }
1101
1102 #[test]
1103 fn auto_ipc_build_kv_map() {
1104 let path = tmp("auto_kv");
1105 let map: KvMap<u32, u32> = AutoIpc::new(&path)
1106 .capacity(64)
1107 .build_kv_map()
1108 .expect("build");
1109 map.insert(7, 49).expect("insert");
1110 assert_eq!(map.get(&7), Some(49));
1111 std::fs::remove_file(&path).ok();
1112 }
1113
1114 #[test]
1115 fn auto_ipc_global_fifo_forces_streaming_inference() {
1116 let auto = AutoIpc::new("/tmp/test-fifo.bin")
1120 .producers(4)
1121 .batch_size(64)
1122 .ordering(crate::qos_policy::Ordering::GlobalFifo);
1123 assert!(matches!(
1124 auto.inferred_shape(),
1125 MmfWorkloadShape::StreamingMpmc { .. }
1126 ));
1127 assert_eq!(auto.inferred_family(), MmfFamily::SharedRing);
1128 }
1129
1130 #[test]
1131 fn auto_ipc_global_fifo_rejects_work_steal_queue() {
1132 let path = tmp("fifo_wsq");
1133 let result = AutoIpc::new(&path)
1134 .batch_size(8)
1135 .ordering(crate::qos_policy::Ordering::GlobalFifo)
1136 .build_work_steal_queue::<u64>();
1137 assert!(matches!(result, Err(ApiError::WrongFamily { .. })),
1138 "GlobalFifo + work-stealing must be rejected, got Ok or wrong error");
1139 std::fs::remove_file(&path).ok();
1140 }
1141
1142 #[test]
1143 fn auto_ipc_build_adaptive_with_ordering_round_trips() {
1144 let path = tmp("auto_adaptive");
1145 let ipc = AutoIpc::new(&path)
1146 .capacity(64)
1147 .ordering(crate::qos_policy::Ordering::GlobalFifo)
1148 .build_adaptive::<u64>()
1149 .expect("build");
1150 assert!(ipc.ring_handle().is_stamped(),
1151 "build_adaptive must construct the stamped ring");
1152 assert_eq!(ipc.ordering(), crate::qos_policy::Ordering::GlobalFifo);
1153 ipc.send(&31337).expect("send");
1154 assert_eq!(ipc.recv().expect("recv"), 31337);
1155 }
1156
1157 #[test]
1158 fn auto_ipc_auto_order_threshold_reaches_adaptive_endpoint() {
1159 let path = tmp("auto_threshold");
1160 let ipc = AutoIpc::new(&path)
1161 .capacity(64)
1162 .auto_order(5.0)
1163 .build_adaptive::<u64>()
1164 .expect("build");
1165 assert!(ipc.ring_handle().is_stamped(),
1166 "auto_order requires the stamped ring and build_adaptive must provide it");
1167 assert_eq!(ipc.ordering(), crate::qos_policy::Ordering::PerProducer,
1168 "auto_order alone must not pre-arm the merge");
1169 }
1170
1171 #[test]
1172 fn kv_map_rejects_streaming_shape() {
1173 let path = tmp("kv_wrong");
1174 let bad_shape = MmfWorkloadShape::StreamingMpmc {
1175 n_producers: 1,
1176 n_consumers: 1,
1177 };
1178 let result = KvMap::<u32, u32>::create(&path, bad_shape, 64);
1179 match result {
1180 Err(ApiError::WrongFamily {
1181 wanted: "SharedHashMap",
1182 got: MmfFamily::SharedRing,
1183 }) => {}
1184 Err(other) => panic!("expected WrongFamily, got {other:?}"),
1185 Ok(_) => panic!("expected error, got Ok"),
1186 }
1187 std::fs::remove_file(&path).ok();
1188 }
1189}