use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{Receiver, Sender};
use std::sync::{Arc, Mutex};
use super::{PeerIdentity, RpcTransport};
use crate::rpc::{RpcError, RpcResult};
pub struct MemTransport {
tx: Sender<Vec<u8>>,
rx: Mutex<Receiver<Vec<u8>>>,
peer: PeerIdentity,
desc: &'static str,
timeout: Mutex<Option<std::time::Duration>>,
closed: Arc<AtomicBool>,
peer_closed: Arc<AtomicBool>,
}
impl MemTransport {
pub fn pair() -> (Self, Self) {
let (a_tx, a_rx) = std::sync::mpsc::channel();
let (b_tx, b_rx) = std::sync::mpsc::channel();
let peer = self_identity();
let a_closed = Arc::new(AtomicBool::new(false));
let b_closed = Arc::new(AtomicBool::new(false));
(
MemTransport {
tx: a_tx,
rx: Mutex::new(b_rx),
peer: peer.clone(),
desc: "mem",
timeout: Mutex::new(None),
closed: Arc::clone(&a_closed),
peer_closed: Arc::clone(&b_closed),
},
MemTransport {
tx: b_tx,
rx: Mutex::new(a_rx),
peer,
desc: "mem",
timeout: Mutex::new(None),
closed: b_closed,
peer_closed: a_closed,
},
)
}
fn either_end_shut(&self) -> bool {
self.closed.load(Ordering::SeqCst) || self.peer_closed.load(Ordering::SeqCst)
}
}
pub(crate) fn self_identity() -> PeerIdentity {
PeerIdentity::Local {
uid: rustix::process::getuid().as_raw(),
pid: std::process::id() as i32,
}
}
impl RpcTransport for MemTransport {
fn send_frame(&self, buf: &[u8]) -> RpcResult<()> {
if buf.len() > super::MAX_FRAME_LEN {
return Err(RpcError::FrameTooLarge {
declared: buf.len(),
max: super::MAX_FRAME_LEN,
});
}
if self.either_end_shut() {
return Err(RpcError::EndOfStream);
}
self.tx
.send(buf.to_vec())
.map_err(|_| RpcError::EndOfStream)
}
fn recv_frame(&self) -> RpcResult<Vec<u8>> {
let timeout = *self.timeout.lock().expect("mem timeout poisoned");
let rx = self.rx.lock().expect("mem rx poisoned");
const TICK: std::time::Duration = std::time::Duration::from_millis(20);
let deadline = timeout.and_then(|d| std::time::Instant::now().checked_add(d));
loop {
let wait = match deadline {
Some(at) => {
let left = at.saturating_duration_since(std::time::Instant::now());
if left.is_zero() {
return Err(RpcError::Timeout);
}
left.min(TICK)
}
None => TICK,
};
match rx.recv_timeout(wait) {
Ok(frame) => return Ok(frame),
Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => {
return Err(RpcError::EndOfStream);
}
Err(std::sync::mpsc::RecvTimeoutError::Timeout) => {
if self.either_end_shut() {
return Err(RpcError::EndOfStream);
}
}
}
}
}
fn peer_identity(&self) -> PeerIdentity {
self.peer.clone()
}
fn describe(&self) -> &str {
self.desc
}
fn shutdown(&self) -> RpcResult<()> {
self.closed.store(true, Ordering::SeqCst);
Ok(())
}
fn set_read_timeout(&self, timeout: Option<std::time::Duration>) -> RpcResult<()> {
*self.timeout.lock().expect("mem timeout poisoned") = timeout;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
#[test]
fn mem_roundtrip_all_sizes() {
let (a, b) = MemTransport::pair();
for size in [0usize, 1, 64, 64 * 1024, 1 << 20, (1 << 20) + 1] {
let payload: Vec<u8> = (0..size).map(|i| (i % 251) as u8).collect();
a.send_frame(&payload).expect("send");
assert_eq!(b.recv_frame().expect("recv"), payload, "size {size}");
}
}
#[test]
fn mem_shutdown_models_a_linux_socket() {
let (a, b) = MemTransport::pair();
a.send_frame(b"a->b before").expect("send before shutdown");
b.send_frame(b"b->a before").expect("send before shutdown");
a.shutdown().expect("shutdown");
a.shutdown().expect("a second shutdown is Ok");
assert_eq!(
a.recv_frame().expect("queued frame survives"),
b"b->a before"
);
assert!(matches!(a.recv_frame(), Err(RpcError::EndOfStream)));
assert!(
matches!(a.send_frame(b"after"), Err(RpcError::EndOfStream)),
"a send after shutdown must fail, not queue"
);
assert_eq!(b.recv_frame().expect("peer drains"), b"a->b before");
assert!(matches!(b.recv_frame(), Err(RpcError::EndOfStream)));
assert!(matches!(b.send_frame(b"x"), Err(RpcError::EndOfStream)));
}
#[test]
fn mem_peer_identity_is_current_process() {
let (a, _b) = MemTransport::pair();
assert_eq!(
a.peer_identity(),
PeerIdentity::Local {
uid: rustix::process::getuid().as_raw(),
pid: std::process::id() as i32,
}
);
assert_eq!(a.describe(), "mem");
}
#[test]
fn mem_peer_closed_on_drop() {
let (a, b) = MemTransport::pair();
drop(b);
assert!(matches!(a.recv_frame(), Err(RpcError::EndOfStream)));
assert!(matches!(a.send_frame(b"x"), Err(RpcError::EndOfStream)));
}
#[test]
fn mem_bidirectional_concurrent_no_deadlock() {
let (a, b) = MemTransport::pair();
let a = Arc::new(a);
let b = Arc::new(b);
const N: usize = 10_000;
let a_send = {
let a = a.clone();
std::thread::spawn(move || {
for i in 0..N {
a.send_frame(&(i as u32).to_le_bytes()).unwrap();
}
})
};
let b_send = {
let b = b.clone();
std::thread::spawn(move || {
for i in 0..N {
b.send_frame(&(i as u32).to_le_bytes()).unwrap();
}
})
};
for i in 0..N {
let got = b.recv_frame().unwrap();
assert_eq!(u32::from_le_bytes(got.try_into().unwrap()), i as u32);
}
for i in 0..N {
let got = a.recv_frame().unwrap();
assert_eq!(u32::from_le_bytes(got.try_into().unwrap()), i as u32);
}
a_send.join().unwrap();
b_send.join().unwrap();
}
#[test]
fn unaddable_read_timeout_does_not_panic() {
let (a, b) = MemTransport::pair();
a.set_read_timeout(Some(std::time::Duration::MAX))
.expect("set_read_timeout");
b.send_frame(b"hi").expect("send");
assert_eq!(a.recv_frame().expect("recv"), b"hi");
}
}