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