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 {
754 self.capacity = n.max(2).next_power_of_two();
755 self
756 }
757
758 pub fn ordering(mut self, ordering: crate::qos_policy::Ordering) -> Self {
764 self.ordering = ordering;
765 self
766 }
767
768 pub fn auto_order(mut self, threshold: f64) -> Self {
775 self.auto_order = Some(threshold);
776 self
777 }
778
779 pub fn inferred_shape(&self) -> MmfWorkloadShape {
781 if self.ordering == crate::qos_policy::Ordering::GlobalFifo {
784 return MmfWorkloadShape::StreamingMpmc {
785 n_producers: self.n_producers,
786 n_consumers: self.n_consumers,
787 };
788 }
789 if self.batch_size.is_some() || self.wait_idle {
794 MmfWorkloadShape::WorkStealing(
795 crate::dispatch_deque::WorkloadShape {
796 n_thieves: self.n_consumers,
797 batch_size: self.batch_size,
798 wait_idle: self.wait_idle,
799 },
800 )
801 } else if self.n_producers >= 2 || self.n_consumers >= 2 {
802 MmfWorkloadShape::StreamingMpmc {
803 n_producers: self.n_producers,
804 n_consumers: self.n_consumers,
805 }
806 } else {
807 MmfWorkloadShape::StreamingMpmc {
810 n_producers: 1,
811 n_consumers: 1,
812 }
813 }
814 }
815
816 pub fn inferred_family(&self) -> MmfFamily {
818 MmfDispatcher::pick(self.inferred_shape())
819 }
820
821 pub fn build_channel<T: Marshal>(self) -> Result<Channel<T>, ApiError> {
826 let shape = self.inferred_shape();
827 Channel::<T>::create(&self.path, shape, self.capacity)
828 }
829
830 pub fn build_work_steal_queue<T: Marshal + Copy + 'static>(
835 self,
836 ) -> Result<WorkStealQueue<T>, ApiError> {
837 if self.ordering == crate::qos_policy::Ordering::GlobalFifo {
838 return Err(ApiError::WrongFamily {
839 wanted: "SharedRing (GlobalFifo ordering declared)",
840 got: MmfFamily::SharedDeque(
841 crate::dispatch_deque::DequeVariant::ChaseLev,
842 ),
843 });
844 }
845 let shape = MmfWorkloadShape::WorkStealing(
847 crate::dispatch_deque::WorkloadShape {
848 n_thieves: self.n_consumers,
849 batch_size: self.batch_size,
850 wait_idle: self.wait_idle,
851 },
852 );
853 WorkStealQueue::<T>::create(&self.path, shape, self.capacity)
854 }
855
856 pub fn build_adaptive<T: Marshal + Copy + 'static>(
863 self,
864 ) -> Result<crate::AdaptiveIpc<T>, ApiError> {
865 let shape = self.inferred_shape();
866 crate::AdaptiveIpc::<T>::create_with_ordering(
867 &self.path,
868 shape,
869 self.capacity,
870 self.n_consumers,
871 self.ordering,
872 self.auto_order,
873 )
874 }
875
876 pub fn build_kv_map<K, V>(self) -> Result<KvMap<K, V>, ApiError>
880 where
881 K: Copy + Eq + std::hash::Hash + Send + Sync + 'static,
882 V: Copy + Send + Sync + 'static,
883 {
884 let shape = MmfWorkloadShape::KeyValueLookup {
885 n_readers: self.n_consumers,
886 n_writers: self.n_producers,
887 };
888 KvMap::<K, V>::create(&self.path, shape, self.capacity)
889 }
890}
891
892#[cfg(test)]
893mod tests {
894 use super::*;
895 use crate::dispatch_deque::WorkloadShape;
896
897 fn tmp(name: &str) -> std::path::PathBuf {
898 let mut p = std::env::temp_dir();
899 let pid = std::process::id();
900 let nonce = std::time::SystemTime::now()
901 .duration_since(std::time::UNIX_EPOCH)
902 .map(|d| d.as_nanos())
903 .unwrap_or(0);
904 p.push(format!("subetha_api_{pid}_{nonce}_{name}.bin"));
905 p
906 }
907
908 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
910 struct U32Item(u32);
911
912 unsafe impl Marshal for U32Item {
913 const PAYLOAD_BYTES: usize = 4;
914 fn marshal(&self, dst: &mut [u8]) {
915 dst[..4].copy_from_slice(&self.0.to_le_bytes());
916 }
917 fn unmarshal(src: &[u8]) -> Result<Self, subetha_core::MarshalError> {
918 if src.len() < 4 {
919 return Err(subetha_core::MarshalError::ShortBuffer {
920 expected: 4,
921 got: src.len(),
922 });
923 }
924 Ok(U32Item(u32::from_le_bytes(src[..4].try_into().unwrap())))
925 }
926 }
927
928 #[test]
929 fn channel_round_trips_via_streaming_shape() {
930 let path = tmp("channel");
931 let shape = MmfWorkloadShape::StreamingMpmc {
932 n_producers: 1,
933 n_consumers: 1,
934 };
935 let chan: Channel<U32Item> = Channel::create(&path, shape, 64).expect("create");
936 assert_eq!(chan.family(), MmfFamily::SharedRing);
937 chan.send(&U32Item(42)).expect("send");
938 let v = chan.recv().expect("recv");
939 assert_eq!(v, U32Item(42));
940 std::fs::remove_file(&path).ok();
941 }
942
943 #[test]
944 fn channel_rejects_wrong_family() {
945 let path = tmp("channel_wrong_family");
946 let bad_shape = MmfWorkloadShape::KeyValueLookup {
947 n_readers: 1,
948 n_writers: 1,
949 };
950 let result = Channel::<U32Item>::create(&path, bad_shape, 64);
951 match result {
952 Err(ApiError::WrongFamily {
953 wanted: "SharedRing",
954 got: MmfFamily::SharedHashMap,
955 }) => {}
956 Err(other) => panic!("expected WrongFamily, got {other:?}"),
957 Ok(_) => panic!("expected error, got Ok"),
958 }
959 std::fs::remove_file(&path).ok();
960 }
961
962 #[test]
963 fn work_steal_queue_round_trips_via_request_reply_shape() {
964 let path = tmp("wsq");
965 let shape = MmfWorkloadShape::WorkStealing(WorkloadShape::request_reply());
966 let q: WorkStealQueue<u64> = WorkStealQueue::create(&path, shape, 64).expect("create");
967 assert_eq!(q.variant(), DequeVariant::ChaseLev);
969 q.push(&100).expect("push");
970 q.push(&200).expect("push");
971 assert_eq!(q.pop(), Some(200));
973 assert_eq!(q.steal(), Some(100));
975 std::fs::remove_file(&path).ok();
976 }
977
978 #[test]
979 fn kv_map_round_trips_via_key_value_shape() {
980 let path = tmp("kv");
981 let shape = MmfWorkloadShape::KeyValueLookup {
982 n_readers: 1,
983 n_writers: 1,
984 };
985 let map: KvMap<u32, u32> = KvMap::create(&path, shape, 64).expect("create");
986 for k in 0..10u32 {
987 map.insert(k, k * k).expect("insert");
988 }
989 for k in 0..10u32 {
990 assert_eq!(map.get(&k), Some(k * k));
991 }
992 assert_eq!(map.len(), 10);
993 std::fs::remove_file(&path).ok();
994 }
995
996 #[test]
997 fn auto_ipc_default_infers_streaming_one_to_one() {
998 let auto = AutoIpc::new("/tmp/test-default.bin");
999 let shape = auto.inferred_shape();
1000 assert!(matches!(shape, MmfWorkloadShape::StreamingMpmc { .. }));
1001 assert_eq!(auto.inferred_family(), MmfFamily::SharedRing);
1002 }
1003
1004 #[test]
1005 fn auto_ipc_multi_producer_infers_streaming_mpmc() {
1006 let auto = AutoIpc::new("/tmp/test-mp.bin").producers(4).consumers(4);
1007 assert_eq!(auto.inferred_family(), MmfFamily::SharedRing);
1008 }
1009
1010 #[test]
1011 fn auto_ipc_batch_hint_flips_to_work_stealing() {
1012 let auto = AutoIpc::new("/tmp/test-batch.bin").batch_size(64);
1013 let shape = auto.inferred_shape();
1014 assert!(matches!(shape, MmfWorkloadShape::WorkStealing(_)));
1015 assert_eq!(
1017 auto.inferred_family(),
1018 MmfFamily::SharedDeque(DequeVariant::Khl)
1019 );
1020 }
1021
1022 #[test]
1023 fn auto_ipc_multi_consumer_plus_batch_infers_urd() {
1024 let auto = AutoIpc::new("/tmp/test-mt.bin")
1025 .consumers(4)
1026 .batch_size(64);
1027 assert_eq!(
1028 auto.inferred_family(),
1029 MmfFamily::SharedDeque(DequeVariant::Urd)
1030 );
1031 }
1032
1033 #[test]
1034 fn auto_ipc_idle_wait_routes_to_urd() {
1035 let auto = AutoIpc::new("/tmp/test-idle.bin").idle_wait(true);
1036 assert_eq!(
1037 auto.inferred_family(),
1038 MmfFamily::SharedDeque(DequeVariant::Urd)
1039 );
1040 }
1041
1042 #[test]
1043 fn auto_ipc_build_channel_end_to_end_round_trip() {
1044 let path = tmp("auto_ch");
1045 let auto = AutoIpc::new(&path).capacity(64);
1046 let chan: Channel<U32Item> = auto.build_channel().expect("build");
1047 chan.send(&U32Item(123)).expect("send");
1048 let v = chan.recv().expect("recv");
1049 assert_eq!(v, U32Item(123));
1050 std::fs::remove_file(&path).ok();
1051 }
1052
1053 #[test]
1054 fn auto_ipc_rounds_a_non_pow2_capacity_up_for_every_terminal() {
1055 let ch_path = tmp("auto_cap_ch");
1060 let chan: Channel<U32Item> = AutoIpc::new(&ch_path)
1061 .capacity(100)
1062 .build_channel()
1063 .expect("100 rounds to 128");
1064 chan.send(&U32Item(5)).expect("send");
1065 assert_eq!(chan.recv().expect("recv"), U32Item(5));
1066 std::fs::remove_file(&ch_path).ok();
1067
1068 let q_path = tmp("auto_cap_wsq");
1069 let q: WorkStealQueue<u64> = AutoIpc::new(&q_path)
1070 .batch_size(8)
1071 .capacity(100)
1072 .build_work_steal_queue()
1073 .expect("100 rounds to 128");
1074 q.push(&1).expect("push");
1075 assert_eq!(q.pop(), Some(1));
1076 std::fs::remove_file(&q_path).ok();
1077
1078 let z_path = tmp("auto_cap_zero");
1081 let zero: Channel<U32Item> = AutoIpc::new(&z_path)
1082 .capacity(0)
1083 .build_channel()
1084 .expect("0 clamps to 2");
1085 zero.send(&U32Item(9)).expect("send");
1086 assert_eq!(zero.recv().expect("recv"), U32Item(9));
1087 std::fs::remove_file(&z_path).ok();
1088 }
1089
1090 #[test]
1091 fn auto_ipc_build_work_steal_queue_with_batch_hint() {
1092 let path = tmp("auto_wsq");
1093 let q: WorkStealQueue<u64> = AutoIpc::new(&path)
1094 .batch_size(8)
1095 .capacity(64)
1096 .build_work_steal_queue()
1097 .expect("build");
1098 q.push(&10).expect("push");
1099 q.push(&20).expect("push");
1100 assert_eq!(q.pop(), Some(20));
1101 assert_eq!(q.steal(), Some(10));
1102 std::fs::remove_file(&path).ok();
1103 }
1104
1105 #[test]
1106 fn auto_ipc_build_kv_map() {
1107 let path = tmp("auto_kv");
1108 let map: KvMap<u32, u32> = AutoIpc::new(&path)
1109 .capacity(64)
1110 .build_kv_map()
1111 .expect("build");
1112 map.insert(7, 49).expect("insert");
1113 assert_eq!(map.get(&7), Some(49));
1114 std::fs::remove_file(&path).ok();
1115 }
1116
1117 #[test]
1118 fn auto_ipc_global_fifo_forces_streaming_inference() {
1119 let auto = AutoIpc::new("/tmp/test-fifo.bin")
1123 .producers(4)
1124 .batch_size(64)
1125 .ordering(crate::qos_policy::Ordering::GlobalFifo);
1126 assert!(matches!(
1127 auto.inferred_shape(),
1128 MmfWorkloadShape::StreamingMpmc { .. }
1129 ));
1130 assert_eq!(auto.inferred_family(), MmfFamily::SharedRing);
1131 }
1132
1133 #[test]
1134 fn auto_ipc_global_fifo_rejects_work_steal_queue() {
1135 let path = tmp("fifo_wsq");
1136 let result = AutoIpc::new(&path)
1137 .batch_size(8)
1138 .ordering(crate::qos_policy::Ordering::GlobalFifo)
1139 .build_work_steal_queue::<u64>();
1140 assert!(matches!(result, Err(ApiError::WrongFamily { .. })),
1141 "GlobalFifo + work-stealing must be rejected, got Ok or wrong error");
1142 std::fs::remove_file(&path).ok();
1143 }
1144
1145 #[test]
1146 fn auto_ipc_build_adaptive_with_ordering_round_trips() {
1147 let path = tmp("auto_adaptive");
1148 let ipc = AutoIpc::new(&path)
1149 .capacity(64)
1150 .ordering(crate::qos_policy::Ordering::GlobalFifo)
1151 .build_adaptive::<u64>()
1152 .expect("build");
1153 assert!(ipc.ring_handle().is_stamped(),
1154 "build_adaptive must construct the stamped ring");
1155 assert_eq!(ipc.ordering(), crate::qos_policy::Ordering::GlobalFifo);
1156 ipc.send(&31337).expect("send");
1157 assert_eq!(ipc.recv().expect("recv"), 31337);
1158 }
1159
1160 #[test]
1161 fn auto_ipc_auto_order_threshold_reaches_adaptive_endpoint() {
1162 let path = tmp("auto_threshold");
1163 let ipc = AutoIpc::new(&path)
1164 .capacity(64)
1165 .auto_order(5.0)
1166 .build_adaptive::<u64>()
1167 .expect("build");
1168 assert!(ipc.ring_handle().is_stamped(),
1169 "auto_order requires the stamped ring and build_adaptive must provide it");
1170 assert_eq!(ipc.ordering(), crate::qos_policy::Ordering::PerProducer,
1171 "auto_order alone must not pre-arm the merge");
1172 }
1173
1174 #[test]
1175 fn kv_map_rejects_streaming_shape() {
1176 let path = tmp("kv_wrong");
1177 let bad_shape = MmfWorkloadShape::StreamingMpmc {
1178 n_producers: 1,
1179 n_consumers: 1,
1180 };
1181 let result = KvMap::<u32, u32>::create(&path, bad_shape, 64);
1182 match result {
1183 Err(ApiError::WrongFamily {
1184 wanted: "SharedHashMap",
1185 got: MmfFamily::SharedRing,
1186 }) => {}
1187 Err(other) => panic!("expected WrongFamily, got {other:?}"),
1188 Ok(_) => panic!("expected error, got Ok"),
1189 }
1190 std::fs::remove_file(&path).ok();
1191 }
1192}