Skip to main content

xrpc/transport/
shared_memory.rs

1use arc_swap::ArcSwap;
2use async_trait::async_trait;
3use bytes::Bytes;
4use parking_lot::Mutex;
5use raw_sync::Timeout;
6use raw_sync::events::{Event, EventImpl, EventInit, EventState};
7use shared_memory::{Shmem, ShmemConf};
8use std::sync::Arc;
9#[cfg(test)]
10use std::sync::atomic::AtomicUsize;
11use std::sync::atomic::{AtomicBool, AtomicU8, AtomicU64, Ordering};
12use std::time::{Duration, Instant};
13use tokio::sync::Mutex as AsyncMutex;
14
15use crate::error::{TransportError, TransportResult};
16use crate::transport::utils::spawn_weak_loop;
17use crate::transport::{FrameTransport, TransportStats};
18
19const SHM_MAGIC: u64 = 0x5852_5043_5348_4D32;
20const SHM_VERSION: u64 = 2;
21const SHM_INITIALIZING: u64 = 0;
22const SHM_READY: u64 = 1;
23const ATTACHMENT_FREE: u64 = 0;
24const ATTACHMENT_CLAIMED: u64 = 1;
25const MIN_BUFFER_SIZE: usize = 4096;
26const MAX_BUFFER_SIZE: usize = 1024 * 1024 * 1024;
27const DEFAULT_BUFFER_SIZE: usize = 1024 * 1024;
28const SHUTDOWN_POLL_INTERVAL: Duration = Duration::from_millis(25);
29const WRITE_COMMIT_CLOSE_TIMEOUT: Duration = Duration::from_millis(100);
30const WRITE_COMMIT_CLOSE_POLL_INTERVAL: Duration = Duration::from_millis(1);
31const OPERATION_PENDING: u8 = 0;
32const OPERATION_CANCELLED: u8 = 1;
33const OPERATION_COMMITTED: u8 = 2;
34const WRITE_COMMIT_OPEN: u64 = 0;
35const WRITE_COMMIT_ACTIVE: u64 = 1;
36const WRITE_COMMIT_CLOSING: u64 = 2;
37const WRITE_COMMIT_CLOSED: u64 = 3;
38
39struct BlockingCancellation {
40    state: Arc<AtomicU8>,
41}
42
43impl BlockingCancellation {
44    fn new() -> Self {
45        Self {
46            state: Arc::new(AtomicU8::new(OPERATION_PENDING)),
47        }
48    }
49
50    fn state(&self) -> Arc<AtomicU8> {
51        Arc::clone(&self.state)
52    }
53
54    fn is_cancelled(state: &AtomicU8) -> bool {
55        state.load(Ordering::Acquire) == OPERATION_CANCELLED
56    }
57
58    fn commit(state: &AtomicU8) -> TransportResult<()> {
59        match state.compare_exchange(
60            OPERATION_PENDING,
61            OPERATION_COMMITTED,
62            Ordering::AcqRel,
63            Ordering::Acquire,
64        ) {
65            Ok(_) => Ok(()),
66            Err(OPERATION_CANCELLED) => Err(TransportError::ConnectionClosed),
67            Err(_) => Err(TransportError::InvalidBufferState(
68                "Blocking operation committed more than once".to_string(),
69            )),
70        }
71    }
72}
73
74impl Drop for BlockingCancellation {
75    fn drop(&mut self) {
76        let _ = self.state.compare_exchange(
77            OPERATION_PENDING,
78            OPERATION_CANCELLED,
79            Ordering::AcqRel,
80            Ordering::Acquire,
81        );
82    }
83}
84
85#[cfg(test)]
86struct ActiveBlockingOperation {
87    counter: Arc<AtomicUsize>,
88}
89
90#[cfg(test)]
91impl ActiveBlockingOperation {
92    fn new(counter: Arc<AtomicUsize>) -> Self {
93        counter.fetch_add(1, Ordering::AcqRel);
94        Self { counter }
95    }
96}
97
98#[cfg(test)]
99impl Drop for ActiveBlockingOperation {
100    fn drop(&mut self) {
101        self.counter.fetch_sub(1, Ordering::AcqRel);
102    }
103}
104
105#[cfg(test)]
106struct CommitTestHook {
107    reached: std::sync::mpsc::SyncSender<()>,
108    resume: Mutex<std::sync::mpsc::Receiver<()>>,
109}
110
111#[cfg(test)]
112impl CommitTestHook {
113    fn pause(&self) {
114        self.reached
115            .send(())
116            .expect("commit test observer was dropped");
117        self.resume
118            .lock()
119            .recv()
120            .expect("commit test resume sender was dropped");
121    }
122}
123
124#[derive(Clone, Copy)]
125struct SharedMemoryLayout {
126    control_size: usize,
127    event_size: usize,
128    read_signal_offset: usize,
129    data_offset: usize,
130    total_size: usize,
131}
132
133impl SharedMemoryLayout {
134    fn checked(capacity: usize, control_size: usize, event_size: usize) -> Result<Self, String> {
135        let read_signal_offset = control_size
136            .checked_add(event_size)
137            .ok_or_else(|| "write event offset overflow".to_string())?;
138        let data_offset = read_signal_offset
139            .checked_add(event_size)
140            .ok_or_else(|| "read event offset overflow".to_string())?;
141        let total_size = data_offset
142            .checked_add(capacity)
143            .ok_or_else(|| "shared memory size overflow".to_string())?;
144        Ok(Self {
145            control_size,
146            event_size,
147            read_signal_offset,
148            data_offset,
149            total_size,
150        })
151    }
152}
153
154#[repr(C)]
155struct SharedMemoryControlBlock {
156    magic: AtomicU64,
157    version: AtomicU64,
158    readiness: AtomicU64,
159    control_size: AtomicU64,
160    event_size: AtomicU64,
161    total_size: AtomicU64,
162    capacity: AtomicU64,
163    attachment_state: AtomicU64,
164    write_pos: AtomicU64,
165    read_pos: AtomicU64,
166    write_commit_state: AtomicU64,
167    error_count: AtomicU64,
168    last_heartbeat: AtomicU64,
169}
170
171impl SharedMemoryControlBlock {
172    fn new(capacity: usize, layout: SharedMemoryLayout) -> Self {
173        Self {
174            magic: AtomicU64::new(SHM_MAGIC),
175            version: AtomicU64::new(SHM_VERSION),
176            readiness: AtomicU64::new(SHM_INITIALIZING),
177            control_size: AtomicU64::new(layout.control_size as u64),
178            event_size: AtomicU64::new(layout.event_size as u64),
179            total_size: AtomicU64::new(layout.total_size as u64),
180            capacity: AtomicU64::new(capacity as u64),
181            attachment_state: AtomicU64::new(ATTACHMENT_FREE),
182            write_pos: AtomicU64::new(0),
183            read_pos: AtomicU64::new(0),
184            write_commit_state: AtomicU64::new(WRITE_COMMIT_OPEN),
185            error_count: AtomicU64::new(0),
186            last_heartbeat: AtomicU64::new(0),
187        }
188    }
189
190    fn is_valid(&self) -> bool {
191        self.readiness.load(Ordering::Acquire) == SHM_READY
192            && self.magic.load(Ordering::Acquire) == SHM_MAGIC
193            && self.version.load(Ordering::Acquire) == SHM_VERSION
194    }
195
196    fn publish_ready(&self) {
197        self.update_heartbeat();
198        self.readiness.store(SHM_READY, Ordering::Release);
199    }
200
201    fn claim_attachment(&self, name: &str) -> TransportResult<()> {
202        match self.attachment_state.compare_exchange(
203            ATTACHMENT_FREE,
204            ATTACHMENT_CLAIMED,
205            Ordering::AcqRel,
206            Ordering::Acquire,
207        ) {
208            Ok(_) => Ok(()),
209            Err(ATTACHMENT_CLAIMED) => Err(TransportError::ConnectionFailed {
210                name: name.to_string(),
211                attempts: 1,
212                reason: "shared memory ring already has a client attachment".to_string(),
213            }),
214            Err(state) => Err(TransportError::InvalidBufferState(format!(
215                "Unknown shared memory attachment state in '{}': {}",
216                name, state
217            ))),
218        }
219    }
220
221    fn release_attachment(&self) {
222        if self
223            .attachment_state
224            .compare_exchange(
225                ATTACHMENT_CLAIMED,
226                ATTACHMENT_FREE,
227                Ordering::AcqRel,
228                Ordering::Acquire,
229            )
230            .is_err()
231        {
232            self.record_error();
233        }
234    }
235
236    fn available_write(&self) -> usize {
237        let w = self.write_pos.load(Ordering::Acquire) as usize;
238        let r = self.read_pos.load(Ordering::Acquire) as usize;
239        let cap = self.capacity.load(Ordering::Acquire) as usize;
240        let w_norm = w % cap;
241        let r_norm = r % cap;
242        if w_norm >= r_norm {
243            cap - (w_norm - r_norm) - 1
244        } else {
245            (r_norm - w_norm) - 1
246        }
247    }
248
249    fn available_read(&self) -> usize {
250        let w = self.write_pos.load(Ordering::Acquire) as usize;
251        let r = self.read_pos.load(Ordering::Acquire) as usize;
252        let cap = self.capacity.load(Ordering::Acquire) as usize;
253        let w_norm = w % cap;
254        let r_norm = r % cap;
255        if w_norm >= r_norm {
256            w_norm - r_norm
257        } else {
258            cap - (r_norm - w_norm)
259        }
260    }
261
262    fn is_connected(&self) -> bool {
263        matches!(
264            self.write_commit_state.load(Ordering::Acquire),
265            WRITE_COMMIT_OPEN | WRITE_COMMIT_ACTIVE
266        )
267    }
268
269    fn may_receive_more(&self) -> bool {
270        self.write_commit_state.load(Ordering::Acquire) != WRITE_COMMIT_CLOSED
271    }
272
273    fn begin_write_commit(&self) -> TransportResult<SharedWriteCommit<'_>> {
274        match self.write_commit_state.compare_exchange(
275            WRITE_COMMIT_OPEN,
276            WRITE_COMMIT_ACTIVE,
277            Ordering::AcqRel,
278            Ordering::Acquire,
279        ) {
280            Ok(_) => Ok(SharedWriteCommit { control: self }),
281            Err(WRITE_COMMIT_CLOSING) | Err(WRITE_COMMIT_CLOSED) => {
282                Err(TransportError::ConnectionClosed)
283            }
284            Err(WRITE_COMMIT_ACTIVE) => Err(TransportError::InvalidBufferState(
285                "Concurrent shared memory write commit".to_string(),
286            )),
287            Err(state) => Err(TransportError::InvalidBufferState(format!(
288                "Unknown shared memory write commit state: {}",
289                state
290            ))),
291        }
292    }
293
294    fn close_write_commits(&self) -> TransportResult<()> {
295        let started = Instant::now();
296        loop {
297            let state = self.write_commit_state.load(Ordering::Acquire);
298            match state {
299                WRITE_COMMIT_OPEN => {
300                    if self
301                        .write_commit_state
302                        .compare_exchange(
303                            WRITE_COMMIT_OPEN,
304                            WRITE_COMMIT_CLOSED,
305                            Ordering::AcqRel,
306                            Ordering::Acquire,
307                        )
308                        .is_ok()
309                    {
310                        return Ok(());
311                    }
312                }
313                WRITE_COMMIT_ACTIVE => {
314                    let _ = self.write_commit_state.compare_exchange(
315                        WRITE_COMMIT_ACTIVE,
316                        WRITE_COMMIT_CLOSING,
317                        Ordering::AcqRel,
318                        Ordering::Acquire,
319                    );
320                }
321                WRITE_COMMIT_CLOSING => {
322                    if started.elapsed() >= WRITE_COMMIT_CLOSE_TIMEOUT
323                        && self
324                            .write_commit_state
325                            .compare_exchange(
326                                WRITE_COMMIT_CLOSING,
327                                WRITE_COMMIT_CLOSED,
328                                Ordering::AcqRel,
329                                Ordering::Acquire,
330                            )
331                            .is_ok()
332                    {
333                        self.record_error();
334                        return Err(TransportError::InvalidBufferState(format!(
335                            "Forced recovery of an abandoned shared memory write commit after {} ms",
336                            WRITE_COMMIT_CLOSE_TIMEOUT.as_millis()
337                        )));
338                    }
339                    std::thread::sleep(WRITE_COMMIT_CLOSE_POLL_INTERVAL);
340                }
341                WRITE_COMMIT_CLOSED => return Ok(()),
342                _ => {
343                    self.record_error();
344                    self.write_commit_state
345                        .store(WRITE_COMMIT_CLOSED, Ordering::Release);
346                    return Err(TransportError::InvalidBufferState(format!(
347                        "Forced recovery of unknown shared memory write commit state: {}",
348                        state
349                    )));
350                }
351            }
352        }
353    }
354
355    fn record_error(&self) {
356        self.error_count.fetch_add(1, Ordering::Relaxed);
357    }
358
359    fn update_heartbeat(&self) {
360        let timestamp = std::time::SystemTime::now()
361            .duration_since(std::time::UNIX_EPOCH)
362            .unwrap()
363            .as_secs();
364        self.last_heartbeat.store(timestamp, Ordering::Release);
365    }
366
367    fn tick_heartbeat(&self) {
368        // Just update the timestamp without signaling
369        self.update_heartbeat();
370    }
371
372    fn is_healthy(&self, timeout_secs: u64) -> bool {
373        let now = std::time::SystemTime::now()
374            .duration_since(std::time::UNIX_EPOCH)
375            .unwrap()
376            .as_secs();
377        let last = self.last_heartbeat.load(Ordering::Acquire);
378
379        if last == 0 {
380            return true; // Not yet initialized
381        }
382
383        now - last < timeout_secs
384    }
385}
386
387struct SharedMemoryAttachment<'a> {
388    control: &'a SharedMemoryControlBlock,
389    armed: bool,
390}
391
392impl SharedMemoryAttachment<'_> {
393    fn disarm(&mut self) {
394        self.armed = false;
395    }
396}
397
398impl Drop for SharedMemoryAttachment<'_> {
399    fn drop(&mut self) {
400        if self.armed {
401            self.control.release_attachment();
402        }
403    }
404}
405
406struct SharedWriteCommit<'a> {
407    control: &'a SharedMemoryControlBlock,
408}
409
410impl Drop for SharedWriteCommit<'_> {
411    fn drop(&mut self) {
412        loop {
413            let state = self.control.write_commit_state.load(Ordering::Acquire);
414            let next = match state {
415                WRITE_COMMIT_ACTIVE => WRITE_COMMIT_OPEN,
416                WRITE_COMMIT_CLOSING => WRITE_COMMIT_CLOSED,
417                _ => {
418                    self.control.record_error();
419                    return;
420                }
421            };
422            if self
423                .control
424                .write_commit_state
425                .compare_exchange(state, next, Ordering::AcqRel, Ordering::Acquire)
426                .is_ok()
427            {
428                return;
429            }
430        }
431    }
432}
433
434#[derive(Debug)]
435struct PreparedWrite {
436    write_pos: usize,
437    total_len: usize,
438}
439
440#[derive(Debug)]
441struct PreparedRead {
442    read_pos: usize,
443    frame_len: usize,
444    payload: Bytes,
445}
446
447struct SharedMemoryRingBuffer {
448    #[allow(dead_code)]
449    shmem: Shmem,
450    control: *mut SharedMemoryControlBlock,
451    data: *mut u8,
452    capacity: usize,
453    is_owner: bool,
454    attachment_claimed: bool,
455    #[allow(dead_code)]
456    name: String,
457    // raw-sync events for true IPC signaling
458    // write_signal: signaled when space is available (writer waits on this)
459    write_signal: Box<dyn EventImpl>,
460    // read_signal: signaled when data is available (reader waits on this)
461    read_signal: Box<dyn EventImpl>,
462    #[cfg(test)]
463    commit_test_hook: Mutex<Option<Arc<CommitTestHook>>>,
464}
465
466impl std::fmt::Debug for SharedMemoryRingBuffer {
467    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
468        f.debug_struct("SharedMemoryRingBuffer")
469            .field("capacity", &self.capacity)
470            .field("is_owner", &self.is_owner)
471            .finish()
472    }
473}
474
475unsafe impl Send for SharedMemoryRingBuffer {}
476unsafe impl Sync for SharedMemoryRingBuffer {}
477
478impl SharedMemoryRingBuffer {
479    fn is_event_wait_timeout(reason: &str, elapsed: Duration, requested: Duration) -> bool {
480        if reason.contains("timed out") || reason.ends_with("0x102") {
481            return true;
482        }
483
484        #[cfg(unix)]
485        {
486            // raw_sync 0.1.x collapses every non-zero pthread_cond_timedwait result
487            // to this generic message. Treat it as a timeout only when the native
488            // wait actually consumed the requested interval; immediate failures
489            // (for example EINVAL) must remain visible as transport errors.
490            let known_unix_timeout = reason == "Failed waiting for signal"
491                || reason.ends_with(": 110")
492                || reason.ends_with(": 60");
493            // pthread_cond_timedwait and Instant can use different platform clocks;
494            // allow a small scheduling/clock-boundary margin without accepting an
495            // immediate generic failure as a timeout.
496            let tolerance = Duration::from_millis(5).min(requested / 2);
497            !requested.is_zero()
498                && known_unix_timeout
499                && elapsed.saturating_add(tolerance) >= requested
500        }
501
502        #[cfg(not(unix))]
503        false
504    }
505
506    fn create(name: &str, capacity: usize) -> TransportResult<Self> {
507        if !(MIN_BUFFER_SIZE..=MAX_BUFFER_SIZE).contains(&capacity) {
508            return Err(TransportError::InvalidBufferState(format!(
509                "Invalid buffer size: {}",
510                capacity
511            )));
512        }
513
514        let control_size = std::mem::size_of::<SharedMemoryControlBlock>();
515        let event_size = Event::size_of(None);
516        let layout = SharedMemoryLayout::checked(capacity, control_size, event_size)
517            .map_err(TransportError::InvalidBufferState)?;
518        let shmem = ShmemConf::new()
519            .size(layout.total_size)
520            .os_id(name)
521            .create()
522            .map_err(|e| TransportError::SharedMemoryCreation {
523                name: name.to_string(),
524                reason: e.to_string(),
525            })?;
526
527        if shmem.len() != layout.total_size {
528            return Err(TransportError::SharedMemoryCreation {
529                name: name.to_string(),
530                reason: format!(
531                    "Mapping length mismatch: expected {}, got {}",
532                    layout.total_size,
533                    shmem.len()
534                ),
535            });
536        }
537
538        let base_ptr = shmem.as_ptr();
539        let control = base_ptr as *mut SharedMemoryControlBlock;
540        let write_signal_ptr = unsafe { base_ptr.add(layout.control_size) };
541        let read_signal_ptr = unsafe { base_ptr.add(layout.read_signal_offset) };
542        let data = unsafe { base_ptr.add(layout.data_offset) };
543
544        unsafe {
545            std::ptr::write(control, SharedMemoryControlBlock::new(capacity, layout));
546            let ctrl = &*control;
547            let (write_signal, _) = Event::new(write_signal_ptr, true).map_err(|e| {
548                TransportError::SharedMemoryCreation {
549                    name: name.to_string(),
550                    reason: format!("Failed to create write event: {}", e),
551                }
552            })?;
553            let (read_signal, _) = Event::new(read_signal_ptr, true).map_err(|e| {
554                TransportError::SharedMemoryCreation {
555                    name: name.to_string(),
556                    reason: format!("Failed to create read event: {}", e),
557                }
558            })?;
559            write_signal.set(EventState::Signaled).map_err(|e| {
560                TransportError::SharedMemoryCreation {
561                    name: name.to_string(),
562                    reason: format!("Failed to set write event: {}", e),
563                }
564            })?;
565            ctrl.publish_ready();
566
567            Ok(Self {
568                shmem,
569                control,
570                data,
571                capacity,
572                is_owner: true,
573                attachment_claimed: false,
574                name: name.to_string(),
575                write_signal,
576                read_signal,
577                #[cfg(test)]
578                commit_test_hook: Mutex::new(None),
579            })
580        }
581    }
582
583    fn connect(name: &str) -> TransportResult<Self> {
584        let shmem =
585            ShmemConf::new()
586                .os_id(name)
587                .open()
588                .map_err(|e| TransportError::ConnectionFailed {
589                    name: name.to_string(),
590                    attempts: 1,
591                    reason: e.to_string(),
592                })?;
593        let expected_control_size = std::mem::size_of::<SharedMemoryControlBlock>();
594        if shmem.len() < expected_control_size {
595            return Err(TransportError::InvalidBufferState(format!(
596                "Shared memory mapping '{}' is too small for the versioned header: {} < {}",
597                name,
598                shmem.len(),
599                expected_control_size
600            )));
601        }
602
603        let base_ptr = shmem.as_ptr();
604        let control = base_ptr as *mut SharedMemoryControlBlock;
605        let ctrl = unsafe { &*control };
606        if ctrl.magic.load(Ordering::Acquire) != SHM_MAGIC {
607            return Err(TransportError::InvalidBufferState(format!(
608                "Shared memory mapping '{}' has incompatible magic",
609                name
610            )));
611        }
612        if ctrl.version.load(Ordering::Acquire) != SHM_VERSION {
613            return Err(TransportError::InvalidBufferState(format!(
614                "Shared memory mapping '{}' has incompatible version",
615                name
616            )));
617        }
618        if ctrl.readiness.load(Ordering::Acquire) != SHM_READY {
619            return Err(TransportError::InvalidBufferState(format!(
620                "Shared memory mapping '{}' is not ready",
621                name
622            )));
623        }
624
625        let control_size =
626            usize::try_from(ctrl.control_size.load(Ordering::Acquire)).map_err(|_| {
627                TransportError::InvalidBufferState(format!(
628                    "Shared memory mapping '{}' has an invalid control size",
629                    name
630                ))
631            })?;
632        let event_size =
633            usize::try_from(ctrl.event_size.load(Ordering::Acquire)).map_err(|_| {
634                TransportError::InvalidBufferState(format!(
635                    "Shared memory mapping '{}' has an invalid event size",
636                    name
637                ))
638            })?;
639        let total_size =
640            usize::try_from(ctrl.total_size.load(Ordering::Acquire)).map_err(|_| {
641                TransportError::InvalidBufferState(format!(
642                    "Shared memory mapping '{}' has an invalid total size",
643                    name
644                ))
645            })?;
646        let capacity = usize::try_from(ctrl.capacity.load(Ordering::Acquire)).map_err(|_| {
647            TransportError::InvalidBufferState(format!(
648                "Shared memory mapping '{}' has an invalid capacity",
649                name
650            ))
651        })?;
652        let expected_event_size = Event::size_of(None);
653        if control_size != expected_control_size || event_size != expected_event_size {
654            return Err(TransportError::InvalidBufferState(format!(
655                "Shared memory mapping '{}' has an incompatible control or event layout",
656                name
657            )));
658        }
659        if !(MIN_BUFFER_SIZE..=MAX_BUFFER_SIZE).contains(&capacity) {
660            return Err(TransportError::InvalidBufferState(format!(
661                "Shared memory mapping '{}' has an invalid capacity: {}",
662                name, capacity
663            )));
664        }
665        let layout =
666            SharedMemoryLayout::checked(capacity, control_size, event_size).map_err(|e| {
667                TransportError::InvalidBufferState(format!(
668                    "Shared memory mapping '{}' has an invalid layout: {}",
669                    name, e
670                ))
671            })?;
672        if total_size != layout.total_size || shmem.len() != layout.total_size {
673            return Err(TransportError::InvalidBufferState(format!(
674                "Shared memory mapping '{}' length mismatch: header {}, computed {}, mapped {}",
675                name,
676                total_size,
677                layout.total_size,
678                shmem.len()
679            )));
680        }
681        if !ctrl.is_connected() {
682            return Err(TransportError::ConnectionClosed);
683        }
684
685        ctrl.claim_attachment(name)?;
686        let mut attachment = SharedMemoryAttachment {
687            control: ctrl,
688            armed: true,
689        };
690        let write_signal_ptr = unsafe { base_ptr.add(layout.control_size) };
691        let read_signal_ptr = unsafe { base_ptr.add(layout.read_signal_offset) };
692        let data = unsafe { base_ptr.add(layout.data_offset) };
693
694        unsafe {
695            let (write_signal, _) = Event::from_existing(write_signal_ptr).map_err(|e| {
696                TransportError::ConnectionFailed {
697                    name: name.to_string(),
698                    attempts: 1,
699                    reason: format!("Failed to open write event: {}", e),
700                }
701            })?;
702            let (read_signal, _) = Event::from_existing(read_signal_ptr).map_err(|e| {
703                TransportError::ConnectionFailed {
704                    name: name.to_string(),
705                    attempts: 1,
706                    reason: format!("Failed to open read event: {}", e),
707                }
708            })?;
709            if !ctrl.is_valid() {
710                return Err(TransportError::InvalidBufferState(format!(
711                    "Shared memory mapping '{}' changed while attaching",
712                    name
713                )));
714            }
715            if !ctrl.is_connected() {
716                return Err(TransportError::ConnectionClosed);
717            }
718            attachment.disarm();
719
720            Ok(Self {
721                shmem,
722                control,
723                data,
724                capacity,
725                is_owner: false,
726                attachment_claimed: true,
727                name: name.to_string(),
728                write_signal,
729                read_signal,
730                #[cfg(test)]
731                commit_test_hook: Mutex::new(None),
732            })
733        }
734    }
735
736    fn control(&self) -> &SharedMemoryControlBlock {
737        unsafe { &*self.control }
738    }
739
740    fn is_connected(&self) -> bool {
741        self.control().is_connected()
742    }
743
744    #[cfg(test)]
745    fn install_commit_test_hook(
746        &self,
747    ) -> (
748        std::sync::mpsc::Receiver<()>,
749        std::sync::mpsc::SyncSender<()>,
750    ) {
751        let (reached_tx, reached_rx) = std::sync::mpsc::sync_channel(1);
752        let (resume_tx, resume_rx) = std::sync::mpsc::sync_channel(1);
753        *self.commit_test_hook.lock() = Some(Arc::new(CommitTestHook {
754            reached: reached_tx,
755            resume: Mutex::new(resume_rx),
756        }));
757        (reached_rx, resume_tx)
758    }
759
760    #[cfg(test)]
761    fn pause_before_commit(&self) {
762        let hook = self.commit_test_hook.lock().take();
763        if let Some(hook) = hook {
764            hook.pause();
765        }
766    }
767
768    fn ensure_control_valid(&self) -> TransportResult<()> {
769        let control = self.control();
770        if !control.is_valid() {
771            control.record_error();
772            return Err(TransportError::InvalidBufferState(
773                "Bad control".to_string(),
774            ));
775        }
776        Ok(())
777    }
778
779    fn ensure_local_operation_open(
780        &self,
781        terminal: &AtomicBool,
782        operation: &AtomicU8,
783    ) -> TransportResult<()> {
784        if terminal.load(Ordering::Acquire) || BlockingCancellation::is_cancelled(operation) {
785            Err(TransportError::ConnectionClosed)
786        } else {
787            Ok(())
788        }
789    }
790
791    fn ensure_write_open(
792        &self,
793        terminal: &AtomicBool,
794        operation: &AtomicU8,
795    ) -> TransportResult<()> {
796        self.ensure_local_operation_open(terminal, operation)?;
797        self.ensure_control_valid()?;
798        if self.is_connected() {
799            Ok(())
800        } else {
801            Err(TransportError::ConnectionClosed)
802        }
803    }
804
805    fn ensure_read_commit(
806        &self,
807        terminal: &AtomicBool,
808        operation: &AtomicU8,
809        frame_len: usize,
810    ) -> TransportResult<()> {
811        self.ensure_local_operation_open(terminal, operation)?;
812        self.ensure_control_valid()?;
813
814        if self.control().available_read() >= frame_len {
815            return Ok(());
816        }
817        if !self.is_connected() {
818            return Err(TransportError::ConnectionClosed);
819        }
820
821        self.control().record_error();
822        Err(TransportError::InvalidBufferState(
823            "Frame changed before receive commit".to_string(),
824        ))
825    }
826
827    fn wait_for_event(
828        &self,
829        event: &dyn EventImpl,
830        duration: Duration,
831        operation: &str,
832    ) -> TransportResult<()> {
833        let mut retried_immediate_unix_failure = false;
834        loop {
835            let started = Instant::now();
836            match event.wait(Timeout::Val(duration)) {
837                Ok(()) => return Ok(()),
838                Err(error) => {
839                    let reason = error.to_string();
840                    let is_timeout =
841                        Self::is_event_wait_timeout(&reason, started.elapsed(), duration);
842                    if is_timeout {
843                        return Ok(());
844                    }
845
846                    #[cfg(unix)]
847                    if !retried_immediate_unix_failure
848                        && !duration.is_zero()
849                        && reason == "Failed waiting for signal"
850                    {
851                        // raw_sync 0.1.x does not normalize tv_nsec after adding the
852                        // timeout to CLOCK_REALTIME. A wait that crosses a second
853                        // boundary can therefore fail immediately with EINVAL, which
854                        // raw_sync collapses into this generic message. Retry once
855                        // after crossing that boundary; a repeated failure remains a
856                        // real InvalidBufferState instead of being hidden as timeout.
857                        retried_immediate_unix_failure = true;
858                        let now = std::time::SystemTime::now()
859                            .duration_since(std::time::UNIX_EPOCH)
860                            .unwrap_or_default();
861                        let until_boundary =
862                            Duration::from_nanos(1_000_000_000_u64 - u64::from(now.subsec_nanos()));
863                        if until_boundary <= duration {
864                            std::thread::sleep(
865                                until_boundary
866                                    .saturating_add(Duration::from_micros(100))
867                                    .min(duration),
868                            );
869                        } else {
870                            std::thread::yield_now();
871                        }
872                        continue;
873                    }
874
875                    self.control().record_error();
876                    return Err(TransportError::InvalidBufferState(format!(
877                        "Failed to wait for {} event in '{}': {}",
878                        operation, self.name, reason
879                    )));
880                }
881            }
882        }
883    }
884
885    fn signal_event(&self, event: &dyn EventImpl) {
886        if event.set(EventState::Signaled).is_err() {
887            self.control().record_error();
888        }
889    }
890
891    fn disconnect(&self) -> TransportResult<()> {
892        let control = self.control();
893        let mut failures = Vec::new();
894        if let Err(error) = control.close_write_commits() {
895            failures.push(format!("write commit: {}", error));
896        }
897        if let Err(error) = self.write_signal.set(EventState::Signaled) {
898            control.record_error();
899            failures.push(format!("write event: {}", error));
900        }
901        if let Err(error) = self.read_signal.set(EventState::Signaled) {
902            control.record_error();
903            failures.push(format!("read event: {}", error));
904        }
905
906        if failures.is_empty() {
907            Ok(())
908        } else {
909            Err(TransportError::InvalidBufferState(format!(
910                "Failed to signal shutdown for '{}': {}",
911                self.name,
912                failures.join(", ")
913            )))
914        }
915    }
916
917    fn prepare_write(
918        &self,
919        data: &[u8],
920        timeout: Duration,
921        terminal: &AtomicBool,
922        operation: &AtomicU8,
923    ) -> TransportResult<PreparedWrite> {
924        let control = self.control();
925        self.ensure_write_open(terminal, operation)?;
926
927        let msg_len = data.len();
928        let total_len = msg_len.checked_add(4).ok_or_else(|| {
929            control.record_error();
930            TransportError::MessageTooLarge {
931                size: msg_len,
932                max: self.capacity - 5,
933            }
934        })?;
935
936        if total_len >= self.capacity {
937            control.record_error();
938            return Err(TransportError::MessageTooLarge {
939                size: msg_len,
940                max: self.capacity - 5,
941            });
942        }
943
944        let start = Instant::now();
945        loop {
946            self.ensure_write_open(terminal, operation)?;
947            if control.available_write() >= total_len {
948                break;
949            }
950            if start.elapsed() >= timeout {
951                self.ensure_write_open(terminal, operation)?;
952                control.record_error();
953                return Err(TransportError::Timeout {
954                    duration_ms: timeout.as_millis() as u64,
955                    operation: "waiting for buffer space".into(),
956                });
957            }
958
959            let remaining = timeout.saturating_sub(start.elapsed());
960            let wait_duration = remaining.min(SHUTDOWN_POLL_INTERVAL);
961            if let Err(error) =
962                self.wait_for_event(self.write_signal.as_ref(), wait_duration, "writable buffer")
963            {
964                self.ensure_write_open(terminal, operation)?;
965                return Err(error);
966            }
967        }
968
969        self.ensure_write_open(terminal, operation)?;
970        let write_pos = control.write_pos.load(Ordering::Acquire) as usize;
971        let len_bytes = (msg_len as u32).to_le_bytes();
972        self.raw_write(write_pos, &len_bytes);
973        self.raw_write(write_pos + 4, data);
974        #[cfg(test)]
975        self.pause_before_commit();
976
977        Ok(PreparedWrite {
978            write_pos,
979            total_len,
980        })
981    }
982
983    fn commit_write(
984        &self,
985        prepared: PreparedWrite,
986        terminal: &AtomicBool,
987        operation: &AtomicU8,
988        state_lock: &Mutex<()>,
989    ) -> TransportResult<()> {
990        let control = self.control();
991        let state_guard = state_lock.lock();
992        self.ensure_write_open(terminal, operation)?;
993        let write_commit = control.begin_write_commit()?;
994        BlockingCancellation::commit(operation)?;
995        control.write_pos.store(
996            (prepared.write_pos + prepared.total_len) as u64,
997            Ordering::Release,
998        );
999        drop(write_commit);
1000        drop(state_guard);
1001
1002        control.update_heartbeat();
1003        self.signal_event(self.read_signal.as_ref());
1004
1005        Ok(())
1006    }
1007
1008    // Helper for circular buffer write
1009    fn raw_write(&self, offset: usize, src: &[u8]) {
1010        let offset = offset % self.capacity;
1011        let len = src.len();
1012        let first_chunk = std::cmp::min(len, self.capacity - offset);
1013
1014        unsafe {
1015            // First part
1016            std::ptr::copy_nonoverlapping(src.as_ptr(), self.data.add(offset), first_chunk);
1017
1018            // Second part (wrap around) if needed
1019            if first_chunk < len {
1020                std::ptr::copy_nonoverlapping(
1021                    src.as_ptr().add(first_chunk),
1022                    self.data,
1023                    len - first_chunk,
1024                );
1025            }
1026        }
1027    }
1028
1029    fn prepare_read(
1030        &self,
1031        timeout: Duration,
1032        terminal: &AtomicBool,
1033        operation: &AtomicU8,
1034    ) -> TransportResult<PreparedRead> {
1035        let control = self.control();
1036        self.ensure_local_operation_open(terminal, operation)?;
1037        self.ensure_control_valid()?;
1038
1039        let start = Instant::now();
1040        loop {
1041            self.ensure_local_operation_open(terminal, operation)?;
1042            self.ensure_control_valid()?;
1043            if control.available_read() >= 4 {
1044                break;
1045            }
1046            if !control.may_receive_more() {
1047                return Err(TransportError::ConnectionClosed);
1048            }
1049            if start.elapsed() >= timeout {
1050                self.ensure_local_operation_open(terminal, operation)?;
1051                control.record_error();
1052                return Err(TransportError::Timeout {
1053                    duration_ms: timeout.as_millis() as u64,
1054                    operation: "waiting for data".into(),
1055                });
1056            }
1057
1058            let remaining = timeout.saturating_sub(start.elapsed());
1059            let wait_duration = remaining.min(SHUTDOWN_POLL_INTERVAL);
1060            if let Err(error) =
1061                self.wait_for_event(self.read_signal.as_ref(), wait_duration, "readable data")
1062            {
1063                self.ensure_local_operation_open(terminal, operation)?;
1064                if !control.may_receive_more() {
1065                    return Err(TransportError::ConnectionClosed);
1066                }
1067                return Err(error);
1068            }
1069        }
1070
1071        self.ensure_local_operation_open(terminal, operation)?;
1072        self.ensure_control_valid()?;
1073        let read_pos = control.read_pos.load(Ordering::Acquire) as usize;
1074        let mut len_bytes = [0u8; 4];
1075        self.raw_read(read_pos, &mut len_bytes);
1076        let msg_len = u32::from_le_bytes(len_bytes) as usize;
1077
1078        if msg_len > self.capacity - 5 {
1079            control.record_error();
1080            return Err(TransportError::InvalidBufferState(
1081                "Bad message length".to_string(),
1082            ));
1083        }
1084
1085        let frame_len = 4 + msg_len;
1086        loop {
1087            self.ensure_local_operation_open(terminal, operation)?;
1088            self.ensure_control_valid()?;
1089            if control.available_read() >= frame_len {
1090                break;
1091            }
1092            if !control.may_receive_more() {
1093                return Err(TransportError::ConnectionClosed);
1094            }
1095            if start.elapsed() >= timeout {
1096                self.ensure_local_operation_open(terminal, operation)?;
1097                control.record_error();
1098                return Err(TransportError::Timeout {
1099                    duration_ms: timeout.as_millis() as u64,
1100                    operation: "waiting for full message".into(),
1101                });
1102            }
1103
1104            let remaining = timeout.saturating_sub(start.elapsed());
1105            let wait_duration = remaining.min(SHUTDOWN_POLL_INTERVAL);
1106            if let Err(error) =
1107                self.wait_for_event(self.read_signal.as_ref(), wait_duration, "complete frame")
1108            {
1109                self.ensure_local_operation_open(terminal, operation)?;
1110                if !control.may_receive_more() {
1111                    return Err(TransportError::ConnectionClosed);
1112                }
1113                return Err(error);
1114            }
1115        }
1116
1117        self.ensure_local_operation_open(terminal, operation)?;
1118        let mut buffer = vec![0u8; msg_len];
1119        self.raw_read(read_pos + 4, &mut buffer);
1120        #[cfg(test)]
1121        self.pause_before_commit();
1122
1123        Ok(PreparedRead {
1124            read_pos,
1125            frame_len,
1126            payload: Bytes::from(buffer),
1127        })
1128    }
1129
1130    fn commit_read(
1131        &self,
1132        prepared: &PreparedRead,
1133        terminal: &AtomicBool,
1134        operation: &AtomicU8,
1135        state_lock: &Mutex<()>,
1136    ) -> TransportResult<()> {
1137        let control = self.control();
1138        let state_guard = state_lock.lock();
1139        self.ensure_read_commit(terminal, operation, prepared.frame_len)?;
1140        BlockingCancellation::commit(operation)?;
1141        control.read_pos.store(
1142            (prepared.read_pos + prepared.frame_len) as u64,
1143            Ordering::Release,
1144        );
1145        drop(state_guard);
1146
1147        control.update_heartbeat();
1148        self.signal_event(self.write_signal.as_ref());
1149
1150        Ok(())
1151    }
1152
1153    // Helper for circular buffer read
1154    fn raw_read(&self, offset: usize, dst: &mut [u8]) {
1155        let offset = offset % self.capacity;
1156        let len = dst.len();
1157        let first_chunk = std::cmp::min(len, self.capacity - offset);
1158
1159        unsafe {
1160            // First part
1161            std::ptr::copy_nonoverlapping(self.data.add(offset), dst.as_mut_ptr(), first_chunk);
1162
1163            // Second part (wrap around) if needed
1164            if first_chunk < len {
1165                std::ptr::copy_nonoverlapping(
1166                    self.data,
1167                    dst.as_mut_ptr().add(first_chunk),
1168                    len - first_chunk,
1169                );
1170            }
1171        }
1172    }
1173
1174    fn tick_heartbeat(&self) {
1175        if self.is_connected() {
1176            self.control().tick_heartbeat();
1177        }
1178    }
1179}
1180
1181impl Drop for SharedMemoryRingBuffer {
1182    fn drop(&mut self) {
1183        if self.is_owner {
1184            if self.disconnect().is_err() {
1185                self.control().record_error();
1186            }
1187        } else if self.attachment_claimed {
1188            self.control().release_attachment();
1189            self.attachment_claimed = false;
1190        }
1191    }
1192}
1193
1194/// Retry policy for reconnection attempts.
1195#[derive(Clone, Copy, Debug, PartialEq)]
1196pub enum RetryPolicy {
1197    Fixed {
1198        delay_ms: u64,
1199    },
1200    Linear {
1201        base_delay_ms: u64,
1202    },
1203    Exponential {
1204        base_delay_ms: u64,
1205        max_delay_ms: u64,
1206    },
1207}
1208
1209impl RetryPolicy {
1210    pub fn delay(&self, attempt: usize) -> Duration {
1211        match self {
1212            RetryPolicy::Fixed { delay_ms } => Duration::from_millis(*delay_ms),
1213            RetryPolicy::Linear { base_delay_ms } => {
1214                Duration::from_millis(base_delay_ms * (attempt as u64 + 1))
1215            }
1216            RetryPolicy::Exponential {
1217                base_delay_ms,
1218                max_delay_ms,
1219            } => {
1220                let delay = base_delay_ms * 2u64.pow(attempt as u32);
1221                Duration::from_millis(delay.min(*max_delay_ms))
1222            }
1223        }
1224    }
1225}
1226
1227impl Default for RetryPolicy {
1228    fn default() -> Self {
1229        RetryPolicy::Linear { base_delay_ms: 100 }
1230    }
1231}
1232
1233/// Configuration for shared memory frame transport.
1234#[derive(Clone, Debug)]
1235pub struct SharedMemoryConfig {
1236    pub buffer_size: usize,
1237    pub read_timeout: Option<Duration>,
1238    pub write_timeout: Option<Duration>,
1239    pub max_retry_attempts: usize,
1240    pub retry_policy: RetryPolicy,
1241    pub auto_reconnect: bool,
1242}
1243
1244impl Default for SharedMemoryConfig {
1245    fn default() -> Self {
1246        Self {
1247            buffer_size: DEFAULT_BUFFER_SIZE,
1248            read_timeout: Some(Duration::from_secs(5)),
1249            write_timeout: Some(Duration::from_secs(5)),
1250            max_retry_attempts: 3,
1251            retry_policy: RetryPolicy::default(),
1252            auto_reconnect: true,
1253        }
1254    }
1255}
1256
1257impl SharedMemoryConfig {
1258    pub fn new() -> Self {
1259        Self::default()
1260    }
1261
1262    pub fn with_buffer_size(mut self, size: usize) -> Self {
1263        self.buffer_size = size;
1264        self
1265    }
1266
1267    pub fn with_read_timeout(mut self, timeout: Duration) -> Self {
1268        self.read_timeout = Some(timeout);
1269        self
1270    }
1271
1272    pub fn with_write_timeout(mut self, timeout: Duration) -> Self {
1273        self.write_timeout = Some(timeout);
1274        self
1275    }
1276
1277    pub fn with_retry_policy(mut self, policy: RetryPolicy) -> Self {
1278        self.retry_policy = policy;
1279        self
1280    }
1281
1282    pub fn with_max_retries(mut self, max: usize) -> Self {
1283        self.max_retry_attempts = max;
1284        self
1285    }
1286
1287    pub fn with_auto_reconnect(mut self, enabled: bool) -> Self {
1288        self.auto_reconnect = enabled;
1289        self
1290    }
1291}
1292
1293#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1294enum SharedMemoryRole {
1295    Client,
1296    Server,
1297}
1298
1299/// Shared memory frame transport with ring buffer (Layer 1).
1300#[derive(Debug)]
1301pub struct SharedMemoryFrameTransport {
1302    send_buffer: ArcSwap<SharedMemoryRingBuffer>,
1303    recv_buffer: ArcSwap<SharedMemoryRingBuffer>,
1304    config: SharedMemoryConfig,
1305    stats: Arc<Mutex<TransportStats>>,
1306    name: String,
1307    error_count: Arc<Mutex<usize>>,
1308    connected: Arc<AtomicBool>,
1309    terminal: Arc<AtomicBool>,
1310    role: SharedMemoryRole,
1311    reconnect_name: String,
1312    send_lock: Arc<AsyncMutex<()>>,
1313    recv_lock: Arc<AsyncMutex<()>>,
1314    reconnect_lock: AsyncMutex<()>,
1315    state_lock: Arc<Mutex<()>>,
1316    #[cfg(test)]
1317    active_blocking_sends: Arc<AtomicUsize>,
1318    #[cfg(test)]
1319    active_blocking_receives: Arc<AtomicUsize>,
1320}
1321
1322impl SharedMemoryFrameTransport {
1323    fn start_send_heartbeat(buffer: &Arc<SharedMemoryRingBuffer>) {
1324        buffer.tick_heartbeat();
1325        spawn_weak_loop(Arc::downgrade(buffer), Duration::from_secs(5), |buffer| {
1326            buffer.tick_heartbeat();
1327        });
1328    }
1329
1330    /// Create a server-side transport.
1331    pub fn create_server(
1332        name: impl Into<String>,
1333        config: SharedMemoryConfig,
1334    ) -> TransportResult<Self> {
1335        let name = name.into();
1336
1337        let send_name = format!("{}_s2c", name);
1338        let recv_name = format!("{}_c2s", name);
1339
1340        let send_buffer = Arc::new(SharedMemoryRingBuffer::create(
1341            &send_name,
1342            config.buffer_size,
1343        )?);
1344        let recv_buffer = Arc::new(SharedMemoryRingBuffer::create(
1345            &recv_name,
1346            config.buffer_size,
1347        )?);
1348
1349        Self::start_send_heartbeat(&send_buffer);
1350
1351        Ok(Self {
1352            send_buffer: ArcSwap::new(send_buffer),
1353            recv_buffer: ArcSwap::new(recv_buffer),
1354            config,
1355            stats: Arc::new(Mutex::new(TransportStats::default())),
1356            name: format!("{}-server", name),
1357            error_count: Arc::new(Mutex::new(0)),
1358            connected: Arc::new(AtomicBool::new(true)),
1359            terminal: Arc::new(AtomicBool::new(false)),
1360            role: SharedMemoryRole::Server,
1361            reconnect_name: name.clone(),
1362            send_lock: Arc::new(AsyncMutex::new(())),
1363            recv_lock: Arc::new(AsyncMutex::new(())),
1364            reconnect_lock: AsyncMutex::new(()),
1365            state_lock: Arc::new(Mutex::new(())),
1366            #[cfg(test)]
1367            active_blocking_sends: Arc::new(AtomicUsize::new(0)),
1368            #[cfg(test)]
1369            active_blocking_receives: Arc::new(AtomicUsize::new(0)),
1370        })
1371    }
1372
1373    /// Connect as a client with custom config.
1374    pub fn connect_client_with_config(
1375        name: impl Into<String>,
1376        config: SharedMemoryConfig,
1377    ) -> TransportResult<Self> {
1378        let name = name.into();
1379        let send_name = format!("{}_c2s", name);
1380        let recv_name = format!("{}_s2c", name);
1381        let mut last_error = None;
1382
1383        for attempt in 0..config.max_retry_attempts {
1384            if attempt > 0 {
1385                std::thread::sleep(config.retry_policy.delay(attempt - 1));
1386            }
1387
1388            match (
1389                SharedMemoryRingBuffer::connect(&send_name),
1390                SharedMemoryRingBuffer::connect(&recv_name),
1391            ) {
1392                (Ok(send_buffer), Ok(recv_buffer)) => {
1393                    let send_arc = Arc::new(send_buffer);
1394                    let recv_arc = Arc::new(recv_buffer);
1395
1396                    Self::start_send_heartbeat(&send_arc);
1397
1398                    return Ok(Self {
1399                        send_buffer: ArcSwap::new(send_arc),
1400                        recv_buffer: ArcSwap::new(recv_arc),
1401                        config: config.clone(),
1402                        stats: Arc::new(Mutex::new(TransportStats::default())),
1403                        name: format!("{}-client", name),
1404                        error_count: Arc::new(Mutex::new(0)),
1405                        connected: Arc::new(AtomicBool::new(true)),
1406                        terminal: Arc::new(AtomicBool::new(false)),
1407                        role: SharedMemoryRole::Client,
1408                        reconnect_name: name.clone(),
1409                        send_lock: Arc::new(AsyncMutex::new(())),
1410                        recv_lock: Arc::new(AsyncMutex::new(())),
1411                        reconnect_lock: AsyncMutex::new(()),
1412                        state_lock: Arc::new(Mutex::new(())),
1413                        #[cfg(test)]
1414                        active_blocking_sends: Arc::new(AtomicUsize::new(0)),
1415                        #[cfg(test)]
1416                        active_blocking_receives: Arc::new(AtomicUsize::new(0)),
1417                    });
1418                }
1419                (Err(error), _) | (_, Err(error)) => {
1420                    if matches!(&error, TransportError::ConnectionClosed) {
1421                        return Err(error);
1422                    }
1423                    last_error = Some(error);
1424                }
1425            }
1426        }
1427
1428        Err(
1429            last_error.unwrap_or_else(|| TransportError::ConnectionFailed {
1430                name: name.clone(),
1431                attempts: config.max_retry_attempts,
1432                reason: "max retries exceeded".into(),
1433            }),
1434        )
1435    }
1436
1437    /// Connect as a client with default config.
1438    pub fn connect_client(name: impl Into<String>) -> TransportResult<Self> {
1439        Self::connect_client_with_config(name, SharedMemoryConfig::default())
1440    }
1441
1442    fn ensure_local_open(&self) -> TransportResult<()> {
1443        if self.terminal.load(Ordering::Acquire) {
1444            Err(TransportError::ConnectionClosed)
1445        } else {
1446            Ok(())
1447        }
1448    }
1449
1450    fn buffers_connected(&self) -> bool {
1451        let send_buffer = self.send_buffer.load_full();
1452        let recv_buffer = self.recv_buffer.load_full();
1453        send_buffer.is_connected() && recv_buffer.is_connected()
1454    }
1455
1456    fn connection_is_open(&self) -> bool {
1457        !self.terminal.load(Ordering::Acquire)
1458            && self.connected.load(Ordering::Acquire)
1459            && self.buffers_connected()
1460    }
1461
1462    fn is_non_partial_timeout(error: &TransportError) -> bool {
1463        matches!(
1464            error,
1465            TransportError::Timeout { operation, .. }
1466                if operation == "waiting for buffer space" || operation == "waiting for data"
1467        )
1468    }
1469
1470    async fn wait_for_retry_delay(&self, duration: Duration) -> TransportResult<()> {
1471        let started = Instant::now();
1472        while started.elapsed() < duration {
1473            self.ensure_local_open()?;
1474            let remaining = duration.saturating_sub(started.elapsed());
1475            tokio::time::sleep(remaining.min(SHUTDOWN_POLL_INTERVAL)).await;
1476        }
1477        self.ensure_local_open()
1478    }
1479
1480    async fn try_reconnect(&self) -> TransportResult<()> {
1481        self.ensure_local_open()?;
1482        let _reconnect_guard = self.reconnect_lock.lock().await;
1483        self.ensure_local_open()?;
1484
1485        if self.buffers_connected() {
1486            self.connected.store(true, Ordering::Release);
1487            return Ok(());
1488        }
1489        if !self.config.auto_reconnect {
1490            return Err(TransportError::NotConnected);
1491        }
1492        if self.role == SharedMemoryRole::Server {
1493            return Err(TransportError::ConnectionFailed {
1494                name: self.reconnect_name.clone(),
1495                attempts: 0,
1496                reason: "server-side shared memory reconnect is not supported".into(),
1497            });
1498        }
1499
1500        let send_name = format!("{}_c2s", self.reconnect_name);
1501        let recv_name = format!("{}_s2c", self.reconnect_name);
1502        let mut last_error = None;
1503
1504        for attempt in 0..self.config.max_retry_attempts {
1505            if attempt > 0 {
1506                self.wait_for_retry_delay(self.config.retry_policy.delay(attempt - 1))
1507                    .await?;
1508            }
1509            self.ensure_local_open()?;
1510
1511            match (
1512                SharedMemoryRingBuffer::connect(&send_name),
1513                SharedMemoryRingBuffer::connect(&recv_name),
1514            ) {
1515                (Ok(send_buffer), Ok(recv_buffer)) => {
1516                    let send_buffer = Arc::new(send_buffer);
1517                    let recv_buffer = Arc::new(recv_buffer);
1518                    let state_guard = self.state_lock.lock();
1519                    self.ensure_local_open()?;
1520                    self.send_buffer.store(Arc::clone(&send_buffer));
1521                    self.recv_buffer.store(recv_buffer);
1522                    *self.error_count.lock() = 0;
1523                    self.connected.store(true, Ordering::Release);
1524                    drop(state_guard);
1525                    Self::start_send_heartbeat(&send_buffer);
1526                    return Ok(());
1527                }
1528                (Err(error), _) | (_, Err(error)) => {
1529                    self.ensure_local_open()?;
1530                    if matches!(&error, TransportError::ConnectionClosed) {
1531                        return Err(error);
1532                    }
1533                    last_error = Some(error);
1534                }
1535            }
1536        }
1537
1538        Err(
1539            last_error.unwrap_or_else(|| TransportError::ConnectionFailed {
1540                name: self.reconnect_name.clone(),
1541                attempts: self.config.max_retry_attempts,
1542                reason: "reconnect failed".into(),
1543            }),
1544        )
1545    }
1546
1547    fn shutdown(&self) -> TransportResult<()> {
1548        let _state_guard = self.state_lock.lock();
1549        self.terminal.store(true, Ordering::Release);
1550        self.connected.store(false, Ordering::Release);
1551        let send_buffer = self.send_buffer.load_full();
1552        let recv_buffer = self.recv_buffer.load_full();
1553        let mut failures = Vec::new();
1554
1555        if let Err(error) = send_buffer.disconnect() {
1556            failures.push(error.to_string());
1557        }
1558        if let Err(error) = recv_buffer.disconnect() {
1559            failures.push(error.to_string());
1560        }
1561
1562        if failures.is_empty() {
1563            Ok(())
1564        } else {
1565            Err(TransportError::InvalidBufferState(format!(
1566                "Failed to close '{}': {}",
1567                self.name,
1568                failures.join(", ")
1569            )))
1570        }
1571    }
1572
1573    pub fn shm_is_healthy(&self) -> bool {
1574        if !self.connection_is_open() {
1575            return false;
1576        }
1577
1578        let recv_buffer = self.recv_buffer.load_full();
1579        recv_buffer.control().is_healthy(30) && *self.error_count.lock() < 10
1580    }
1581
1582    pub fn shm_stats(&self) -> TransportStats {
1583        self.stats.lock().clone()
1584    }
1585
1586    #[cfg(test)]
1587    fn active_blocking_send_count(&self) -> usize {
1588        self.active_blocking_sends.load(Ordering::Acquire)
1589    }
1590
1591    #[cfg(test)]
1592    fn active_blocking_receive_count(&self) -> usize {
1593        self.active_blocking_receives.load(Ordering::Acquire)
1594    }
1595}
1596
1597impl Drop for SharedMemoryFrameTransport {
1598    fn drop(&mut self) {
1599        if self.shutdown().is_err() {
1600            *self.error_count.lock() += 1;
1601        }
1602    }
1603}
1604
1605#[async_trait]
1606impl FrameTransport for SharedMemoryFrameTransport {
1607    fn is_healthy(&self) -> bool {
1608        self.shm_is_healthy()
1609    }
1610
1611    async fn send_frame(&self, data: &[u8]) -> TransportResult<()> {
1612        let result: TransportResult<()> = async {
1613            let cancellation = BlockingCancellation::new();
1614            let send_guard = Arc::new(Arc::clone(&self.send_lock).lock_owned().await);
1615            self.ensure_local_open()?;
1616            if !self.connected.load(Ordering::Acquire) {
1617                self.try_reconnect().await?;
1618            }
1619
1620            let mut last_error = None;
1621            let mut only_non_partial_timeouts = true;
1622
1623            for attempt in 0..self.config.max_retry_attempts {
1624                if attempt > 0 {
1625                    self.wait_for_retry_delay(self.config.retry_policy.delay(attempt - 1))
1626                        .await?;
1627                }
1628                self.ensure_local_open()?;
1629
1630                let operation = cancellation.state();
1631                let result = tokio::task::spawn_blocking({
1632                    let buffer = self.send_buffer.load_full();
1633                    let terminal = Arc::clone(&self.terminal);
1634                    let worker_operation = Arc::clone(&operation);
1635                    #[cfg(test)]
1636                    let active_blocking_sends = Arc::clone(&self.active_blocking_sends);
1637                    let operation_guard = Arc::clone(&send_guard);
1638                    let data = data.to_vec();
1639                    let timeout = self.config.write_timeout.unwrap_or(Duration::from_secs(30));
1640                    move || {
1641                        #[cfg(test)]
1642                        let _active_operation = ActiveBlockingOperation::new(active_blocking_sends);
1643                        let result = buffer.prepare_write(
1644                            &data,
1645                            timeout,
1646                            terminal.as_ref(),
1647                            worker_operation.as_ref(),
1648                        );
1649                        drop(operation_guard);
1650                        result.map(|prepared| (buffer, prepared))
1651                    }
1652                })
1653                .await;
1654
1655                let operation_result = match result {
1656                    Ok(Ok((buffer, prepared))) => buffer.commit_write(
1657                        prepared,
1658                        self.terminal.as_ref(),
1659                        operation.as_ref(),
1660                        self.state_lock.as_ref(),
1661                    ),
1662                    Ok(Err(error)) => Err(error),
1663                    Err(error) => {
1664                        last_error = Some(TransportError::SendFailed {
1665                            attempts: attempt + 1,
1666                            reason: error.to_string(),
1667                        });
1668                        only_non_partial_timeouts = false;
1669                        *self.error_count.lock() += 1;
1670                        continue;
1671                    }
1672                };
1673
1674                match operation_result {
1675                    Ok(()) => {
1676                        let mut stats = self.stats.lock();
1677                        stats.messages_sent += 1;
1678                        stats.bytes_sent += data.len() as u64;
1679                        return Ok(());
1680                    }
1681                    Err(error) => {
1682                        let non_partial_timeout = Self::is_non_partial_timeout(&error);
1683                        if !non_partial_timeout {
1684                            *self.error_count.lock() += 1;
1685                            only_non_partial_timeouts = false;
1686                        }
1687                        if matches!(&error, TransportError::ConnectionClosed) {
1688                            self.connected.store(false, Ordering::Release);
1689                            return Err(error);
1690                        }
1691                        last_error = Some(error);
1692                    }
1693                }
1694            }
1695
1696            self.ensure_local_open()?;
1697            if only_non_partial_timeouts
1698                && last_error
1699                    .as_ref()
1700                    .is_some_and(Self::is_non_partial_timeout)
1701            {
1702                return Err(last_error.expect("timeout error disappeared"));
1703            }
1704
1705            self.connected.store(false, Ordering::Release);
1706            Err(last_error.unwrap_or_else(|| TransportError::SendFailed {
1707                attempts: self.config.max_retry_attempts,
1708                reason: "max retries exceeded".into(),
1709            }))
1710        }
1711        .await;
1712        if result.is_err() {
1713            self.stats.lock().send_errors += 1;
1714        }
1715        result
1716    }
1717
1718    async fn recv_frame(&self) -> TransportResult<Bytes> {
1719        let result: TransportResult<Bytes> = async {
1720            let cancellation = BlockingCancellation::new();
1721            let recv_guard = Arc::new(Arc::clone(&self.recv_lock).lock_owned().await);
1722            self.ensure_local_open()?;
1723            if !self.connected.load(Ordering::Acquire) {
1724                self.try_reconnect().await?;
1725            }
1726
1727            let mut last_error = None;
1728            let mut only_non_partial_timeouts = true;
1729
1730            for attempt in 0..self.config.max_retry_attempts {
1731                if attempt > 0 {
1732                    self.wait_for_retry_delay(self.config.retry_policy.delay(attempt - 1))
1733                        .await?;
1734                }
1735                self.ensure_local_open()?;
1736
1737                let operation = cancellation.state();
1738                let result = tokio::task::spawn_blocking({
1739                    let buffer = self.recv_buffer.load_full();
1740                    let terminal = Arc::clone(&self.terminal);
1741                    let worker_operation = Arc::clone(&operation);
1742                    #[cfg(test)]
1743                    let active_blocking_receives = Arc::clone(&self.active_blocking_receives);
1744                    let operation_guard = Arc::clone(&recv_guard);
1745                    let timeout = self.config.read_timeout.unwrap_or(Duration::from_secs(30));
1746                    move || {
1747                        #[cfg(test)]
1748                        let _active_operation =
1749                            ActiveBlockingOperation::new(active_blocking_receives);
1750                        let result = buffer.prepare_read(
1751                            timeout,
1752                            terminal.as_ref(),
1753                            worker_operation.as_ref(),
1754                        );
1755                        drop(operation_guard);
1756                        result.map(|prepared| (buffer, prepared))
1757                    }
1758                })
1759                .await;
1760
1761                let operation_result = match result {
1762                    Ok(Ok((buffer, prepared))) => buffer
1763                        .commit_read(
1764                            &prepared,
1765                            self.terminal.as_ref(),
1766                            operation.as_ref(),
1767                            self.state_lock.as_ref(),
1768                        )
1769                        .map(|()| prepared.payload),
1770                    Ok(Err(error)) => Err(error),
1771                    Err(error) => {
1772                        last_error = Some(TransportError::ReceiveFailed {
1773                            attempts: attempt + 1,
1774                            reason: error.to_string(),
1775                        });
1776                        only_non_partial_timeouts = false;
1777                        *self.error_count.lock() += 1;
1778                        continue;
1779                    }
1780                };
1781
1782                match operation_result {
1783                    Ok(bytes) => {
1784                        let mut stats = self.stats.lock();
1785                        stats.messages_received += 1;
1786                        stats.bytes_received += bytes.len() as u64;
1787                        return Ok(bytes);
1788                    }
1789                    Err(error) => {
1790                        let non_partial_timeout = Self::is_non_partial_timeout(&error);
1791                        if !non_partial_timeout {
1792                            *self.error_count.lock() += 1;
1793                            only_non_partial_timeouts = false;
1794                        }
1795                        if matches!(&error, TransportError::ConnectionClosed) {
1796                            self.connected.store(false, Ordering::Release);
1797                            return Err(error);
1798                        }
1799                        last_error = Some(error);
1800                    }
1801                }
1802            }
1803
1804            self.ensure_local_open()?;
1805            if only_non_partial_timeouts
1806                && last_error
1807                    .as_ref()
1808                    .is_some_and(Self::is_non_partial_timeout)
1809            {
1810                return Err(last_error.expect("timeout error disappeared"));
1811            }
1812
1813            self.connected.store(false, Ordering::Release);
1814            Err(last_error.unwrap_or_else(|| TransportError::ReceiveFailed {
1815                attempts: self.config.max_retry_attempts,
1816                reason: "max retries exceeded".into(),
1817            }))
1818        }
1819        .await;
1820        if result.is_err() {
1821            self.stats.lock().recv_errors += 1;
1822        }
1823        result
1824    }
1825
1826    fn is_connected(&self) -> bool {
1827        self.connection_is_open()
1828    }
1829
1830    async fn close(&self) -> TransportResult<()> {
1831        self.shutdown()
1832    }
1833
1834    fn stats(&self) -> Option<TransportStats> {
1835        Some(self.stats.lock().clone())
1836    }
1837
1838    fn name(&self) -> &str {
1839        &self.name
1840    }
1841}
1842
1843// Deprecated alias for backward compatibility
1844#[deprecated(since = "0.2.0", note = "Use SharedMemoryFrameTransport instead")]
1845pub type SharedMemoryTransport = SharedMemoryFrameTransport;
1846
1847#[cfg(test)]
1848mod tests {
1849    use super::*;
1850    use crate::{MessageChannelAdapter, RpcClient};
1851    use std::io::{BufRead, BufReader, Read, Write};
1852    use std::process::{Child, Command, Stdio};
1853    use std::sync::mpsc;
1854
1855    static NEXT_TEST_ID: AtomicU64 = AtomicU64::new(0);
1856    const CHILD_MODE_ENV: &str = "XRPC_SHM_SHUTDOWN_CHILD_MODE";
1857    const EXIT_MARKER: &str = "XRPC_SHUTDOWN_READY_FOR_RUNTIME_DROP";
1858
1859    fn unique_name(prefix: &str) -> String {
1860        format!(
1861            "{}-{}-{}",
1862            prefix,
1863            std::process::id(),
1864            NEXT_TEST_ID.fetch_add(1, Ordering::Relaxed)
1865        )
1866    }
1867
1868    fn test_control(capacity: usize) -> SharedMemoryControlBlock {
1869        let layout = SharedMemoryLayout::checked(
1870            capacity,
1871            std::mem::size_of::<SharedMemoryControlBlock>(),
1872            Event::size_of(None),
1873        )
1874        .unwrap();
1875        SharedMemoryControlBlock::new(capacity, layout)
1876    }
1877
1878    async fn wait_until(condition: impl Fn() -> bool) {
1879        let started = Instant::now();
1880        while !condition() {
1881            assert!(
1882                started.elapsed() < Duration::from_secs(1),
1883                "condition was not met before timeout"
1884            );
1885            tokio::time::sleep(Duration::from_millis(5)).await;
1886        }
1887    }
1888
1889    async fn wait_for_commit_hook(reached: mpsc::Receiver<()>) {
1890        tokio::task::spawn_blocking(move || reached.recv_timeout(Duration::from_secs(1)))
1891            .await
1892            .expect("commit hook waiter panicked")
1893            .expect("worker did not reach commit hook");
1894    }
1895
1896    #[test]
1897    fn test_event_wait_timeout_rejects_immediate_generic_failure() {
1898        assert!(!SharedMemoryRingBuffer::is_event_wait_timeout(
1899            "Failed waiting for signal",
1900            Duration::from_millis(1),
1901            Duration::from_millis(100),
1902        ));
1903        assert!(!SharedMemoryRingBuffer::is_event_wait_timeout(
1904            "Failed waiting for signal",
1905            Duration::from_micros(1),
1906            Duration::from_millis(1),
1907        ));
1908        assert!(!SharedMemoryRingBuffer::is_event_wait_timeout(
1909            "Failed waiting for signal: 22",
1910            Duration::from_millis(100),
1911            Duration::from_millis(100),
1912        ));
1913        assert!(!SharedMemoryRingBuffer::is_event_wait_timeout(
1914            "Unknown raw_sync failure",
1915            Duration::from_millis(100),
1916            Duration::from_millis(100),
1917        ));
1918    }
1919
1920    #[cfg(unix)]
1921    #[test]
1922    fn test_event_wait_timeout_accepts_elapsed_raw_sync_timeout() {
1923        assert!(SharedMemoryRingBuffer::is_event_wait_timeout(
1924            "Failed waiting for signal",
1925            Duration::from_millis(100),
1926            Duration::from_millis(100),
1927        ));
1928    }
1929
1930    #[test]
1931    fn test_connect_rejects_mapping_shorter_than_header() {
1932        let name = unique_name("test-short-header");
1933        let _mapping = ShmemConf::new()
1934            .size(std::mem::size_of::<SharedMemoryControlBlock>() - 1)
1935            .os_id(&name)
1936            .create()
1937            .unwrap();
1938
1939        let error = SharedMemoryRingBuffer::connect(&name).unwrap_err();
1940
1941        assert!(matches!(error, TransportError::InvalidBufferState(_)));
1942        assert!(error.to_string().contains("too small"));
1943    }
1944
1945    #[test]
1946    fn test_connect_validates_version_readiness_and_layout() {
1947        let name = unique_name("test-header-validation");
1948        let ring = SharedMemoryRingBuffer::create(&name, MIN_BUFFER_SIZE).unwrap();
1949        let control = ring.control();
1950        let control_size = std::mem::size_of::<SharedMemoryControlBlock>();
1951        let event_size = Event::size_of(None);
1952        let layout =
1953            SharedMemoryLayout::checked(MIN_BUFFER_SIZE, control_size, event_size).unwrap();
1954
1955        assert_eq!(control.readiness.load(Ordering::Acquire), SHM_READY);
1956        assert_eq!(
1957            control.control_size.load(Ordering::Acquire),
1958            control_size as u64
1959        );
1960        assert_eq!(
1961            control.event_size.load(Ordering::Acquire),
1962            event_size as u64
1963        );
1964        assert_eq!(
1965            control.total_size.load(Ordering::Acquire),
1966            layout.total_size as u64
1967        );
1968        assert_ne!(control.last_heartbeat.load(Ordering::Acquire), 0);
1969
1970        control.magic.store(0x5852_5043, Ordering::Release);
1971        assert!(SharedMemoryRingBuffer::connect(&name).is_err());
1972        control.magic.store(SHM_MAGIC, Ordering::Release);
1973
1974        control.version.store(SHM_VERSION - 1, Ordering::Release);
1975        assert!(SharedMemoryRingBuffer::connect(&name).is_err());
1976        control.version.store(SHM_VERSION, Ordering::Release);
1977
1978        control.readiness.store(SHM_INITIALIZING, Ordering::Release);
1979        assert!(SharedMemoryRingBuffer::connect(&name).is_err());
1980        control.readiness.store(SHM_READY, Ordering::Release);
1981
1982        control
1983            .control_size
1984            .store((control_size - 8) as u64, Ordering::Release);
1985        assert!(SharedMemoryRingBuffer::connect(&name).is_err());
1986        control
1987            .control_size
1988            .store(control_size as u64, Ordering::Release);
1989
1990        control
1991            .event_size
1992            .store((event_size + 1) as u64, Ordering::Release);
1993        assert!(SharedMemoryRingBuffer::connect(&name).is_err());
1994        control
1995            .event_size
1996            .store(event_size as u64, Ordering::Release);
1997
1998        control
1999            .capacity
2000            .store((MIN_BUFFER_SIZE - 1) as u64, Ordering::Release);
2001        assert!(SharedMemoryRingBuffer::connect(&name).is_err());
2002        control
2003            .capacity
2004            .store(MIN_BUFFER_SIZE as u64, Ordering::Release);
2005
2006        control
2007            .total_size
2008            .store((layout.total_size + 1) as u64, Ordering::Release);
2009        assert!(SharedMemoryRingBuffer::connect(&name).is_err());
2010        control
2011            .total_size
2012            .store(layout.total_size as u64, Ordering::Release);
2013
2014        let attached = SharedMemoryRingBuffer::connect(&name).unwrap();
2015        assert_eq!(
2016            control.attachment_state.load(Ordering::Acquire),
2017            ATTACHMENT_CLAIMED
2018        );
2019        drop(attached);
2020        assert_eq!(
2021            control.attachment_state.load(Ordering::Acquire),
2022            ATTACHMENT_FREE
2023        );
2024    }
2025
2026    #[test]
2027    fn test_close_waits_for_active_write_commit() {
2028        let control = Arc::new(test_control(MIN_BUFFER_SIZE));
2029        let closing_control = Arc::clone(&control);
2030        let write_commit = control.begin_write_commit().unwrap();
2031        let (closed_tx, closed_rx) = mpsc::sync_channel(1);
2032        let close_thread = std::thread::spawn(move || {
2033            closed_tx
2034                .send(closing_control.close_write_commits())
2035                .unwrap();
2036        });
2037
2038        let started = Instant::now();
2039        while control.write_commit_state.load(Ordering::Acquire) != WRITE_COMMIT_CLOSING {
2040            assert!(started.elapsed() < Duration::from_secs(1));
2041            std::thread::yield_now();
2042        }
2043        assert!(matches!(
2044            closed_rx.try_recv(),
2045            Err(mpsc::TryRecvError::Empty)
2046        ));
2047
2048        control.write_pos.store(8, Ordering::Release);
2049        drop(write_commit);
2050
2051        closed_rx
2052            .recv_timeout(Duration::from_secs(1))
2053            .unwrap()
2054            .unwrap();
2055        close_thread.join().unwrap();
2056        assert_eq!(control.write_pos.load(Ordering::Acquire), 8);
2057        assert_eq!(
2058            control.write_commit_state.load(Ordering::Acquire),
2059            WRITE_COMMIT_CLOSED
2060        );
2061    }
2062
2063    #[test]
2064    fn test_close_forces_recovery_of_abandoned_write_commit() {
2065        let control = Arc::new(test_control(MIN_BUFFER_SIZE));
2066        let write_commit = control.begin_write_commit().unwrap();
2067        std::mem::forget(write_commit);
2068        let started = Instant::now();
2069
2070        let error = control.close_write_commits().unwrap_err();
2071
2072        assert!(started.elapsed() < Duration::from_secs(1));
2073        assert!(error.to_string().contains("Forced recovery"));
2074        assert_eq!(
2075            control.write_commit_state.load(Ordering::Acquire),
2076            WRITE_COMMIT_CLOSED
2077        );
2078    }
2079
2080    #[test]
2081    fn test_payload_must_leave_ring_sentinel_byte() {
2082        let name = unique_name("test-payload-sentinel");
2083        let ring = SharedMemoryRingBuffer::create(&name, MIN_BUFFER_SIZE).unwrap();
2084        let terminal = AtomicBool::new(false);
2085        let state_lock = Mutex::new(());
2086        let oversized_operation = AtomicU8::new(OPERATION_PENDING);
2087        let oversized = vec![0u8; MIN_BUFFER_SIZE - 4];
2088
2089        let error = ring
2090            .prepare_write(&oversized, Duration::ZERO, &terminal, &oversized_operation)
2091            .unwrap_err();
2092
2093        assert!(matches!(
2094            error,
2095            TransportError::MessageTooLarge { size, max }
2096                if size == MIN_BUFFER_SIZE - 4 && max == MIN_BUFFER_SIZE - 5
2097        ));
2098
2099        let maximum_operation = AtomicU8::new(OPERATION_PENDING);
2100        let maximum = vec![1u8; MIN_BUFFER_SIZE - 5];
2101        let prepared = ring
2102            .prepare_write(&maximum, Duration::ZERO, &terminal, &maximum_operation)
2103            .unwrap();
2104        ring.commit_write(prepared, &terminal, &maximum_operation, &state_lock)
2105            .unwrap();
2106    }
2107
2108    #[test]
2109    fn test_blocking_preparation_does_not_publish_or_consume_positions() {
2110        let name = unique_name("test-prepare-before-commit");
2111        let ring = SharedMemoryRingBuffer::create(&name, MIN_BUFFER_SIZE).unwrap();
2112        let terminal = AtomicBool::new(false);
2113        let state_lock = Mutex::new(());
2114        let write_operation = AtomicU8::new(OPERATION_PENDING);
2115
2116        let prepared_write = ring
2117            .prepare_write(
2118                b"prepared frame",
2119                Duration::ZERO,
2120                &terminal,
2121                &write_operation,
2122            )
2123            .unwrap();
2124        assert_eq!(ring.control().write_pos.load(Ordering::Acquire), 0);
2125        assert_eq!(ring.control().available_read(), 0);
2126
2127        ring.commit_write(prepared_write, &terminal, &write_operation, &state_lock)
2128            .unwrap();
2129        let committed_write_pos = ring.control().write_pos.load(Ordering::Acquire);
2130        assert_ne!(committed_write_pos, 0);
2131
2132        let read_operation = AtomicU8::new(OPERATION_PENDING);
2133        let prepared_read = ring
2134            .prepare_read(Duration::ZERO, &terminal, &read_operation)
2135            .unwrap();
2136        assert_eq!(prepared_read.payload.as_ref(), b"prepared frame");
2137        assert_eq!(ring.control().read_pos.load(Ordering::Acquire), 0);
2138
2139        ring.commit_read(&prepared_read, &terminal, &read_operation, &state_lock)
2140            .unwrap();
2141        assert_eq!(
2142            ring.control().read_pos.load(Ordering::Acquire),
2143            committed_write_pos
2144        );
2145    }
2146
2147    #[tokio::test]
2148    async fn test_cross_process_basic() {
2149        let name = unique_name("test-basic");
2150        let config = SharedMemoryConfig::default();
2151        let server = SharedMemoryFrameTransport::create_server(&name, config).unwrap();
2152        let client = SharedMemoryFrameTransport::connect_client(&name).unwrap();
2153
2154        client.send_frame(b"Hello").await.unwrap();
2155        let msg = server.recv_frame().await.unwrap();
2156        assert_eq!(msg.as_ref(), b"Hello");
2157    }
2158
2159    #[tokio::test]
2160    async fn test_second_client_attachment_is_rejected() {
2161        let name = unique_name("test-single-client");
2162        let config = SharedMemoryConfig::default().with_max_retries(1);
2163        let server = SharedMemoryFrameTransport::create_server(&name, config.clone()).unwrap();
2164        let client =
2165            SharedMemoryFrameTransport::connect_client_with_config(&name, config.clone()).unwrap();
2166
2167        let error =
2168            SharedMemoryFrameTransport::connect_client_with_config(&name, config).unwrap_err();
2169
2170        assert!(matches!(error, TransportError::ConnectionFailed { .. }));
2171        assert_eq!(
2172            server
2173                .send_buffer
2174                .load_full()
2175                .control()
2176                .attachment_state
2177                .load(Ordering::Acquire),
2178            ATTACHMENT_CLAIMED
2179        );
2180        assert_eq!(
2181            server
2182                .recv_buffer
2183                .load_full()
2184                .control()
2185                .attachment_state
2186                .load(Ordering::Acquire),
2187            ATTACHMENT_CLAIMED
2188        );
2189
2190        client.send_frame(b"single client").await.unwrap();
2191        assert_eq!(
2192            server.recv_frame().await.unwrap().as_ref(),
2193            b"single client"
2194        );
2195    }
2196
2197    #[tokio::test]
2198    async fn test_partial_client_connection_releases_first_attachment() {
2199        let name = unique_name("test-partial-client-claim");
2200        let config = SharedMemoryConfig::default().with_max_retries(1);
2201        let server = SharedMemoryFrameTransport::create_server(&name, config.clone()).unwrap();
2202        let held_second_ring = SharedMemoryRingBuffer::connect(&format!("{}_s2c", name)).unwrap();
2203
2204        let error = SharedMemoryFrameTransport::connect_client_with_config(&name, config.clone())
2205            .unwrap_err();
2206
2207        assert!(matches!(error, TransportError::ConnectionFailed { .. }));
2208        assert_eq!(
2209            server
2210                .recv_buffer
2211                .load_full()
2212                .control()
2213                .attachment_state
2214                .load(Ordering::Acquire),
2215            ATTACHMENT_FREE
2216        );
2217
2218        drop(held_second_ring);
2219        let client = SharedMemoryFrameTransport::connect_client_with_config(&name, config).unwrap();
2220        client.send_frame(b"claimed after cleanup").await.unwrap();
2221        assert_eq!(
2222            server.recv_frame().await.unwrap().as_ref(),
2223            b"claimed after cleanup"
2224        );
2225    }
2226
2227    #[tokio::test]
2228    async fn test_ring_buffer_wrapping() {
2229        let name = unique_name("test-wrap");
2230        let config = SharedMemoryConfig::default().with_buffer_size(8192);
2231        let server = SharedMemoryFrameTransport::create_server(&name, config).unwrap();
2232        let client = Arc::new(SharedMemoryFrameTransport::connect_client(&name).unwrap());
2233
2234        let chunk_size = 1000;
2235        let num_chunks = 20;
2236        let send_client = Arc::clone(&client);
2237
2238        let send_handle = tokio::spawn(async move {
2239            for i in 0..num_chunks {
2240                let data = vec![i as u8; chunk_size];
2241                send_client.send_frame(&data).await.unwrap();
2242            }
2243        });
2244
2245        for i in 0..num_chunks {
2246            let msg = server.recv_frame().await.unwrap();
2247            assert_eq!(msg.len(), chunk_size);
2248            assert!(msg.iter().all(|&b| b == i as u8), "chunk {} corrupted", i);
2249        }
2250
2251        send_handle.await.unwrap();
2252    }
2253
2254    #[tokio::test]
2255    async fn test_auto_reconnect() {
2256        let name = unique_name("test-reconnect");
2257        let config = SharedMemoryConfig::default().with_auto_reconnect(true);
2258        let server = SharedMemoryFrameTransport::create_server(&name, config.clone()).unwrap();
2259        let client = SharedMemoryFrameTransport::connect_client_with_config(&name, config).unwrap();
2260
2261        client.send_frame(b"before").await.unwrap();
2262        let msg = server.recv_frame().await.unwrap();
2263        assert_eq!(msg.as_ref(), b"before");
2264
2265        client.connected.store(false, Ordering::Release);
2266        assert!(!client.is_connected());
2267
2268        client.send_frame(b"after").await.unwrap();
2269        assert!(client.is_connected());
2270
2271        let msg = server.recv_frame().await.unwrap();
2272        assert_eq!(msg.as_ref(), b"after");
2273    }
2274
2275    #[tokio::test]
2276    async fn test_client_heartbeats_outbound_ring_after_reconnect() {
2277        let name = unique_name("test-reconnect-heartbeat");
2278        let config = SharedMemoryConfig::default().with_max_retries(1);
2279        let server = SharedMemoryFrameTransport::create_server(&name, config.clone()).unwrap();
2280        let client =
2281            SharedMemoryFrameTransport::connect_client_with_config(&name, config.clone()).unwrap();
2282        let client_send = client.send_buffer.load_full();
2283        assert!(!client_send.is_owner);
2284        client_send
2285            .control()
2286            .last_heartbeat
2287            .store(0, Ordering::Release);
2288        client_send.tick_heartbeat();
2289        assert_ne!(
2290            client_send.control().last_heartbeat.load(Ordering::Acquire),
2291            0
2292        );
2293
2294        server.close().await.unwrap();
2295        drop(server);
2296        let restarted_server =
2297            SharedMemoryFrameTransport::create_server(&name, config.clone()).unwrap();
2298        restarted_server
2299            .recv_buffer
2300            .load_full()
2301            .control()
2302            .last_heartbeat
2303            .store(0, Ordering::Release);
2304
2305        client.try_reconnect().await.unwrap();
2306
2307        assert_ne!(
2308            restarted_server
2309                .recv_buffer
2310                .load_full()
2311                .control()
2312                .last_heartbeat
2313                .load(Ordering::Acquire),
2314            0
2315        );
2316        client.send_frame(b"after restart").await.unwrap();
2317        assert_eq!(
2318            restarted_server.recv_frame().await.unwrap().as_ref(),
2319            b"after restart"
2320        );
2321    }
2322
2323    #[tokio::test]
2324    async fn test_configurable_timeout() {
2325        let name = unique_name("test-timeout");
2326        let config = SharedMemoryConfig::default()
2327            .with_read_timeout(Duration::from_millis(100))
2328            .with_write_timeout(Duration::from_millis(100));
2329
2330        let _server = SharedMemoryFrameTransport::create_server(&name, config.clone()).unwrap();
2331        let client = SharedMemoryFrameTransport::connect_client_with_config(&name, config).unwrap();
2332
2333        let start = Instant::now();
2334        let result = client.recv_frame().await;
2335        let elapsed = start.elapsed();
2336
2337        assert!(result.is_err());
2338        assert!(
2339            elapsed < Duration::from_secs(1),
2340            "timeout took too long: {:?}",
2341            elapsed
2342        );
2343    }
2344
2345    #[tokio::test]
2346    async fn test_receive_timeout_keeps_transport_connected() {
2347        let name = unique_name("test-receive-timeout-connected");
2348        let config = SharedMemoryConfig::default()
2349            .with_read_timeout(Duration::from_millis(20))
2350            .with_retry_policy(RetryPolicy::Fixed { delay_ms: 1 })
2351            .with_max_retries(2);
2352        let receiver = SharedMemoryFrameTransport::create_server(&name, config.clone()).unwrap();
2353        let sender = SharedMemoryFrameTransport::connect_client_with_config(&name, config).unwrap();
2354
2355        let result = receiver.recv_frame().await;
2356
2357        assert!(matches!(
2358            result,
2359            Err(TransportError::Timeout { operation, .. }) if operation == "waiting for data"
2360        ));
2361        assert!(receiver.is_connected());
2362        assert!(receiver.is_healthy());
2363        assert!(sender.is_connected());
2364        assert_eq!(receiver.stats().unwrap().recv_errors, 1);
2365
2366        sender.send_frame(b"frame after timeout").await.unwrap();
2367        assert_eq!(
2368            receiver.recv_frame().await.unwrap().as_ref(),
2369            b"frame after timeout"
2370        );
2371    }
2372
2373    #[tokio::test]
2374    async fn test_send_timeout_keeps_transport_connected() {
2375        let name = unique_name("test-send-timeout-connected");
2376        let config = SharedMemoryConfig::default()
2377            .with_buffer_size(MIN_BUFFER_SIZE)
2378            .with_write_timeout(Duration::from_millis(20))
2379            .with_retry_policy(RetryPolicy::Fixed { delay_ms: 1 })
2380            .with_max_retries(2);
2381        let receiver = SharedMemoryFrameTransport::create_server(&name, config.clone()).unwrap();
2382        let sender = SharedMemoryFrameTransport::connect_client_with_config(&name, config).unwrap();
2383        let first_frame = vec![1u8; 3000];
2384        let blocked_frame = vec![2u8; 3000];
2385        let later_frame = vec![3u8; 3000];
2386        sender.send_frame(&first_frame).await.unwrap();
2387
2388        let result = sender.send_frame(&blocked_frame).await;
2389
2390        assert!(matches!(
2391            result,
2392            Err(TransportError::Timeout { operation, .. }) if operation == "waiting for buffer space"
2393        ));
2394        assert!(sender.is_connected());
2395        assert!(sender.is_healthy());
2396        assert!(receiver.is_connected());
2397        assert_eq!(sender.stats().unwrap().send_errors, 1);
2398
2399        assert_eq!(receiver.recv_frame().await.unwrap().as_ref(), first_frame);
2400        sender.send_frame(&later_frame).await.unwrap();
2401        assert_eq!(receiver.recv_frame().await.unwrap().as_ref(), later_frame);
2402    }
2403
2404    #[tokio::test]
2405    async fn test_local_close_interrupts_long_recv() {
2406        let name = unique_name("test-local-close-recv");
2407        let config = SharedMemoryConfig::default()
2408            .with_read_timeout(Duration::from_secs(30))
2409            .with_max_retries(1);
2410        let _server = SharedMemoryFrameTransport::create_server(&name, config.clone()).unwrap();
2411        let client = Arc::new(
2412            SharedMemoryFrameTransport::connect_client_with_config(&name, config).unwrap(),
2413        );
2414        let recv_client = Arc::clone(&client);
2415        let recv_task = tokio::spawn(async move { recv_client.recv_frame().await });
2416
2417        tokio::time::sleep(Duration::from_millis(75)).await;
2418        let close_started = Instant::now();
2419        client.close().await.unwrap();
2420        let result = tokio::time::timeout(Duration::from_secs(1), recv_task)
2421            .await
2422            .expect("recv did not stop after local close")
2423            .unwrap();
2424
2425        assert!(
2426            matches!(&result, Err(TransportError::ConnectionClosed)),
2427            "unexpected recv result: {:?}",
2428            result
2429        );
2430        assert!(close_started.elapsed() < Duration::from_secs(1));
2431    }
2432
2433    #[tokio::test]
2434    async fn test_peer_close_interrupts_recv() {
2435        let name = unique_name("test-peer-close-recv");
2436        let config = SharedMemoryConfig::default()
2437            .with_read_timeout(Duration::from_secs(30))
2438            .with_max_retries(1);
2439        let server = SharedMemoryFrameTransport::create_server(&name, config.clone()).unwrap();
2440        let client = Arc::new(
2441            SharedMemoryFrameTransport::connect_client_with_config(&name, config).unwrap(),
2442        );
2443        let recv_client = Arc::clone(&client);
2444        let recv_task = tokio::spawn(async move { recv_client.recv_frame().await });
2445
2446        tokio::time::sleep(Duration::from_millis(75)).await;
2447        server.close().await.unwrap();
2448        let result = tokio::time::timeout(Duration::from_secs(1), recv_task)
2449            .await
2450            .expect("recv did not stop after peer close")
2451            .unwrap();
2452
2453        assert!(
2454            matches!(&result, Err(TransportError::ConnectionClosed)),
2455            "unexpected recv result: {:?}",
2456            result
2457        );
2458        assert!(!client.is_connected());
2459    }
2460
2461    #[tokio::test]
2462    async fn test_close_is_idempotent() {
2463        let name = unique_name("test-idempotent-close");
2464        let server =
2465            SharedMemoryFrameTransport::create_server(&name, SharedMemoryConfig::default())
2466                .unwrap();
2467        let client = SharedMemoryFrameTransport::connect_client(&name).unwrap();
2468
2469        client.close().await.unwrap();
2470        client.close().await.unwrap();
2471
2472        assert!(!client.is_connected());
2473        assert!(!client.is_healthy());
2474        assert!(!server.is_connected());
2475    }
2476
2477    #[tokio::test]
2478    async fn test_no_reconnect_after_close() {
2479        let name = unique_name("test-no-reconnect-after-close");
2480        let config = SharedMemoryConfig::default().with_auto_reconnect(true);
2481        let _server = SharedMemoryFrameTransport::create_server(&name, config.clone()).unwrap();
2482        let client = SharedMemoryFrameTransport::connect_client_with_config(&name, config).unwrap();
2483
2484        client.close().await.unwrap();
2485
2486        assert!(matches!(
2487            client.try_reconnect().await,
2488            Err(TransportError::ConnectionClosed)
2489        ));
2490        assert!(matches!(
2491            client.send_frame(b"closed").await,
2492            Err(TransportError::ConnectionClosed)
2493        ));
2494        assert!(!client.is_connected());
2495    }
2496
2497    #[tokio::test]
2498    async fn test_connected_state_tracks_close() {
2499        let name = unique_name("test-connected-state");
2500        let server =
2501            SharedMemoryFrameTransport::create_server(&name, SharedMemoryConfig::default())
2502                .unwrap();
2503        let client = SharedMemoryFrameTransport::connect_client(&name).unwrap();
2504
2505        assert!(server.is_connected());
2506        assert!(client.is_connected());
2507        assert!(server.is_healthy());
2508        assert!(client.is_healthy());
2509
2510        client.close().await.unwrap();
2511
2512        assert!(!client.is_connected());
2513        assert!(!client.is_healthy());
2514        assert!(!server.is_connected());
2515        assert!(!server.is_healthy());
2516    }
2517
2518    #[tokio::test]
2519    async fn test_aborted_recv_stops_worker_without_consuming_frame() {
2520        let name = unique_name("test-aborted-recv");
2521        let config = SharedMemoryConfig::default()
2522            .with_read_timeout(Duration::from_secs(30))
2523            .with_max_retries(1);
2524        let receiver =
2525            Arc::new(SharedMemoryFrameTransport::create_server(&name, config.clone()).unwrap());
2526        let sender = SharedMemoryFrameTransport::connect_client_with_config(&name, config).unwrap();
2527        let recv_task = tokio::spawn({
2528            let receiver = Arc::clone(&receiver);
2529            async move { receiver.recv_frame().await }
2530        });
2531
2532        wait_until(|| receiver.active_blocking_receive_count() == 1).await;
2533        recv_task.abort();
2534        assert!(recv_task.await.unwrap_err().is_cancelled());
2535        wait_until(|| receiver.active_blocking_receive_count() == 0).await;
2536
2537        assert!(receiver.is_connected());
2538        sender.send_frame(b"later frame").await.unwrap();
2539        let frame = receiver.recv_frame().await.unwrap();
2540
2541        assert_eq!(frame.as_ref(), b"later frame");
2542        assert_eq!(receiver.active_blocking_receive_count(), 0);
2543        assert_eq!(sender.active_blocking_send_count(), 0);
2544    }
2545
2546    #[tokio::test]
2547    async fn test_aborted_send_returns_active_counter_to_zero() {
2548        let name = unique_name("test-aborted-send");
2549        let config = SharedMemoryConfig::default()
2550            .with_buffer_size(MIN_BUFFER_SIZE)
2551            .with_write_timeout(Duration::from_secs(30))
2552            .with_max_retries(1);
2553        let receiver = SharedMemoryFrameTransport::create_server(&name, config.clone()).unwrap();
2554        let sender = Arc::new(
2555            SharedMemoryFrameTransport::connect_client_with_config(&name, config).unwrap(),
2556        );
2557        let first_frame = vec![1u8; 3000];
2558        sender.send_frame(&first_frame).await.unwrap();
2559        let send_task = tokio::spawn({
2560            let sender = Arc::clone(&sender);
2561            async move { sender.send_frame(&vec![2u8; 3000]).await }
2562        });
2563
2564        wait_until(|| sender.active_blocking_send_count() == 1).await;
2565        send_task.abort();
2566        assert!(send_task.await.unwrap_err().is_cancelled());
2567        wait_until(|| sender.active_blocking_send_count() == 0).await;
2568
2569        assert!(sender.is_connected());
2570        assert_eq!(receiver.recv_frame().await.unwrap().as_ref(), first_frame);
2571        sender.send_frame(b"later frame").await.unwrap();
2572        assert_eq!(
2573            receiver.recv_frame().await.unwrap().as_ref(),
2574            b"later frame"
2575        );
2576        assert_eq!(sender.active_blocking_send_count(), 0);
2577        assert_eq!(receiver.active_blocking_receive_count(), 0);
2578    }
2579
2580    #[tokio::test]
2581    async fn test_cancel_before_write_commit_does_not_publish_frame() {
2582        let name = unique_name("test-cancel-write-commit");
2583        let config = SharedMemoryConfig::default().with_max_retries(1);
2584        let receiver = SharedMemoryFrameTransport::create_server(&name, config.clone()).unwrap();
2585        let sender = Arc::new(
2586            SharedMemoryFrameTransport::connect_client_with_config(&name, config).unwrap(),
2587        );
2588        let send_buffer = sender.send_buffer.load_full();
2589        let initial_write_pos = send_buffer.control().write_pos.load(Ordering::Acquire);
2590        let (reached, resume) = send_buffer.install_commit_test_hook();
2591        let send_task = tokio::spawn({
2592            let sender = Arc::clone(&sender);
2593            async move { sender.send_frame(b"cancelled frame").await }
2594        });
2595
2596        wait_for_commit_hook(reached).await;
2597        send_task.abort();
2598        assert!(send_task.await.unwrap_err().is_cancelled());
2599        resume.send(()).unwrap();
2600        wait_until(|| sender.active_blocking_send_count() == 0).await;
2601
2602        assert_eq!(
2603            send_buffer.control().write_pos.load(Ordering::Acquire),
2604            initial_write_pos
2605        );
2606        sender.send_frame(b"retained frame").await.unwrap();
2607        assert_eq!(
2608            receiver.recv_frame().await.unwrap().as_ref(),
2609            b"retained frame"
2610        );
2611    }
2612
2613    #[tokio::test]
2614    async fn test_cancel_before_read_commit_does_not_consume_frame() {
2615        let name = unique_name("test-cancel-read-commit");
2616        let config = SharedMemoryConfig::default().with_max_retries(1);
2617        let receiver =
2618            Arc::new(SharedMemoryFrameTransport::create_server(&name, config.clone()).unwrap());
2619        let sender = SharedMemoryFrameTransport::connect_client_with_config(&name, config).unwrap();
2620        sender.send_frame(b"retained frame").await.unwrap();
2621
2622        let recv_buffer = receiver.recv_buffer.load_full();
2623        let initial_read_pos = recv_buffer.control().read_pos.load(Ordering::Acquire);
2624        let (reached, resume) = recv_buffer.install_commit_test_hook();
2625        let recv_task = tokio::spawn({
2626            let receiver = Arc::clone(&receiver);
2627            async move { receiver.recv_frame().await }
2628        });
2629
2630        wait_for_commit_hook(reached).await;
2631        recv_task.abort();
2632        assert!(recv_task.await.unwrap_err().is_cancelled());
2633        resume.send(()).unwrap();
2634        wait_until(|| receiver.active_blocking_receive_count() == 0).await;
2635
2636        assert_eq!(
2637            recv_buffer.control().read_pos.load(Ordering::Acquire),
2638            initial_read_pos
2639        );
2640        assert_eq!(
2641            receiver.recv_frame().await.unwrap().as_ref(),
2642            b"retained frame"
2643        );
2644    }
2645
2646    #[tokio::test]
2647    async fn test_peer_close_before_write_commit_does_not_publish_frame() {
2648        let name = unique_name("test-peer-close-write-commit");
2649        let config = SharedMemoryConfig::default().with_max_retries(1);
2650        let receiver = SharedMemoryFrameTransport::create_server(&name, config.clone()).unwrap();
2651        let sender = Arc::new(
2652            SharedMemoryFrameTransport::connect_client_with_config(&name, config).unwrap(),
2653        );
2654        let send_buffer = sender.send_buffer.load_full();
2655        let initial_write_pos = send_buffer.control().write_pos.load(Ordering::Acquire);
2656        let (reached, resume) = send_buffer.install_commit_test_hook();
2657        let send_task = tokio::spawn({
2658            let sender = Arc::clone(&sender);
2659            async move { sender.send_frame(b"abandoned frame").await }
2660        });
2661
2662        wait_for_commit_hook(reached).await;
2663        receiver.close().await.unwrap();
2664        resume.send(()).unwrap();
2665        let result = send_task.await.unwrap();
2666
2667        assert!(matches!(result, Err(TransportError::ConnectionClosed)));
2668        assert_eq!(
2669            send_buffer.control().write_pos.load(Ordering::Acquire),
2670            initial_write_pos
2671        );
2672    }
2673
2674    #[tokio::test]
2675    async fn test_local_close_before_read_commit_does_not_consume_frame() {
2676        let name = unique_name("test-local-close-read-commit");
2677        let config = SharedMemoryConfig::default().with_max_retries(1);
2678        let receiver =
2679            Arc::new(SharedMemoryFrameTransport::create_server(&name, config.clone()).unwrap());
2680        let sender = SharedMemoryFrameTransport::connect_client_with_config(&name, config).unwrap();
2681        sender.send_frame(b"unconsumed frame").await.unwrap();
2682
2683        let recv_buffer = receiver.recv_buffer.load_full();
2684        let initial_read_pos = recv_buffer.control().read_pos.load(Ordering::Acquire);
2685        let (reached, resume) = recv_buffer.install_commit_test_hook();
2686        let recv_task = tokio::spawn({
2687            let receiver = Arc::clone(&receiver);
2688            async move { receiver.recv_frame().await }
2689        });
2690
2691        wait_for_commit_hook(reached).await;
2692        receiver.close().await.unwrap();
2693        resume.send(()).unwrap();
2694        let result = recv_task.await.unwrap();
2695
2696        assert!(matches!(result, Err(TransportError::ConnectionClosed)));
2697        assert_eq!(
2698            recv_buffer.control().read_pos.load(Ordering::Acquire),
2699            initial_read_pos
2700        );
2701    }
2702
2703    #[tokio::test]
2704    async fn test_complete_frame_is_delivered_after_peer_close() {
2705        let name = unique_name("test-frame-before-peer-close");
2706        let server = SharedMemoryFrameTransport::create_server(
2707            &name,
2708            SharedMemoryConfig::default().with_max_retries(1),
2709        )
2710        .unwrap();
2711        let client = SharedMemoryFrameTransport::connect_client_with_config(
2712            &name,
2713            SharedMemoryConfig::default().with_max_retries(1),
2714        )
2715        .unwrap();
2716
2717        server.send_frame(b"committed frame").await.unwrap();
2718        server.close().await.unwrap();
2719
2720        let frame = client.recv_frame().await.unwrap();
2721        assert_eq!(frame.as_ref(), b"committed frame");
2722    }
2723
2724    #[test]
2725    fn shutdown_child_entry() {
2726        let Ok(mode) = std::env::var(CHILD_MODE_ENV) else {
2727            return;
2728        };
2729
2730        let runtime = tokio::runtime::Builder::new_multi_thread()
2731            .enable_all()
2732            .build()
2733            .expect("failed to create Tokio runtime");
2734
2735        runtime.block_on(async move {
2736            let name = format!("xrpc-shutdown-{}-{mode}", std::process::id());
2737            let config = SharedMemoryConfig::default()
2738                .with_read_timeout(Duration::from_secs(300))
2739                .with_max_retries(3);
2740            let _server = SharedMemoryFrameTransport::create_server(&name, config.clone())
2741                .expect("failed to create SHM server");
2742            let transport = SharedMemoryFrameTransport::connect_client_with_config(&name, config)
2743                .expect("failed to connect SHM client");
2744            let client = RpcClient::new(MessageChannelAdapter::new(transport));
2745            let handle = client.try_start().expect("failed to start RPC client");
2746            let channel = client.transport();
2747
2748            tokio::time::timeout(Duration::from_secs(5), async {
2749                loop {
2750                    if channel.inner().active_blocking_receive_count() > 0 {
2751                        break;
2752                    }
2753                    tokio::task::yield_now().await;
2754                }
2755            })
2756            .await
2757            .expect("SHM blocking receive did not start");
2758
2759            match mode.as_str() {
2760                "joined" => {
2761                    client.close().await.expect("failed to close RPC client");
2762                    handle.join().await.expect("receive task failed");
2763                }
2764                "dropped" => {
2765                    client.close().await.expect("failed to close RPC client");
2766                    drop(handle);
2767                }
2768                "client-dropped" => {
2769                    drop(handle);
2770                    drop(client);
2771                }
2772                _ => panic!("unknown child mode: {mode}"),
2773            }
2774
2775            println!("{EXIT_MARKER}");
2776            std::io::stdout()
2777                .flush()
2778                .expect("failed to flush child stdout");
2779        });
2780    }
2781
2782    fn spawn_shutdown_child(mode: &str) -> Child {
2783        Command::new(std::env::current_exe().expect("failed to locate unit test executable"))
2784            .arg("--exact")
2785            .arg("transport::shared_memory::tests::shutdown_child_entry")
2786            .arg("--nocapture")
2787            .env(CHILD_MODE_ENV, mode)
2788            .stdout(Stdio::piped())
2789            .stderr(Stdio::piped())
2790            .spawn()
2791            .expect("failed to spawn shutdown child")
2792    }
2793
2794    fn assert_shutdown_child_exits_promptly(mode: &str) {
2795        let mut child = spawn_shutdown_child(mode);
2796        let stdout = child.stdout.take().expect("child stdout was not captured");
2797        let (line_tx, line_rx) = mpsc::channel();
2798
2799        std::thread::spawn(move || {
2800            let reader = BufReader::new(stdout);
2801            for line in reader.lines() {
2802                match line {
2803                    Ok(line) => {
2804                        if line_tx.send(line).is_err() {
2805                            break;
2806                        }
2807                    }
2808                    Err(_) => break,
2809                }
2810            }
2811        });
2812
2813        let marker_deadline = Instant::now() + Duration::from_secs(10);
2814        let mut output = Vec::new();
2815        let mut reached_marker = false;
2816        while Instant::now() < marker_deadline {
2817            match line_rx.recv_timeout(Duration::from_millis(100)) {
2818                Ok(line) => {
2819                    reached_marker |= line.contains(EXIT_MARKER);
2820                    output.push(line);
2821                    if reached_marker {
2822                        break;
2823                    }
2824                }
2825                Err(mpsc::RecvTimeoutError::Timeout) => {}
2826                Err(mpsc::RecvTimeoutError::Disconnected) => break,
2827            }
2828        }
2829
2830        if !reached_marker {
2831            let _ = child.kill();
2832            let _ = child.wait();
2833            panic!(
2834                "shutdown child did not reach runtime drop marker for mode {mode}: {}",
2835                output.join("\n")
2836            );
2837        }
2838
2839        let exit_deadline = Instant::now() + Duration::from_secs(2);
2840        while Instant::now() < exit_deadline {
2841            if let Some(status) = child.try_wait().expect("failed to query child status") {
2842                assert!(
2843                    status.success(),
2844                    "shutdown child failed for mode {mode}: {status}"
2845                );
2846                return;
2847            }
2848            std::thread::sleep(Duration::from_millis(20));
2849        }
2850
2851        let _ = child.kill();
2852        let _ = child.wait();
2853        let mut stderr = String::new();
2854        if let Some(mut child_stderr) = child.stderr.take() {
2855            let _ = child_stderr.read_to_string(&mut stderr);
2856        }
2857        panic!(
2858            "shutdown child did not exit within two seconds for mode {mode}\nstdout:\n{}\nstderr:\n{stderr}",
2859            output.join("\n")
2860        );
2861    }
2862
2863    #[test]
2864    fn joined_handle_exits_without_read_timeout_tail() {
2865        if std::env::var_os(CHILD_MODE_ENV).is_none() {
2866            assert_shutdown_child_exits_promptly("joined");
2867        }
2868    }
2869
2870    #[test]
2871    fn dropped_handle_exits_without_read_timeout_tail() {
2872        if std::env::var_os(CHILD_MODE_ENV).is_none() {
2873            assert_shutdown_child_exits_promptly("dropped");
2874        }
2875    }
2876
2877    #[test]
2878    fn dropped_client_exits_without_read_timeout_tail() {
2879        if std::env::var_os(CHILD_MODE_ENV).is_none() {
2880            assert_shutdown_child_exits_promptly("client-dropped");
2881        }
2882    }
2883}