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 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; }
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 write_signal: Box<dyn EventImpl>,
460 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 let known_unix_timeout = reason == "Failed waiting for signal"
491 || reason.ends_with(": 110")
492 || reason.ends_with(": 60");
493 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 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 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 std::ptr::copy_nonoverlapping(src.as_ptr(), self.data.add(offset), first_chunk);
1017
1018 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 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 std::ptr::copy_nonoverlapping(self.data.add(offset), dst.as_mut_ptr(), first_chunk);
1162
1163 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#[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#[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#[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 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 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 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(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}