use bytes::Bytes;
use monocoque::rt::{LocalRuntime, TcpListener};
use monocoque::zmq::{PullSocket, PushSocket, SocketOptions};
use std::alloc::{GlobalAlloc, Layout, System};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::mpsc;
use std::thread;
static ALLOCS: AtomicUsize = AtomicUsize::new(0);
static COUNTING: AtomicUsize = AtomicUsize::new(0);
struct Counting;
unsafe impl GlobalAlloc for Counting {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
if COUNTING.load(Ordering::Relaxed) != 0 {
ALLOCS.fetch_add(1, Ordering::Relaxed);
}
unsafe { System.alloc(layout) }
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
unsafe { System.dealloc(ptr, layout) }
}
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
if COUNTING.load(Ordering::Relaxed) != 0 {
ALLOCS.fetch_add(1, Ordering::Relaxed);
}
unsafe { System.realloc(ptr, layout, new_size) }
}
}
#[global_allocator]
static GLOBAL: Counting = Counting;
const ADDR: &str = "127.0.0.1:0";
const PAYLOAD: usize = 64;
const WARM: usize = 4_000;
const MEAS: usize = 200_000;
const MAX_WINDOW_ALLOCS: usize = MEAS / 50;
#[test]
fn single_frame_hot_path_makes_no_per_message_allocation() {
let (port_tx, port_rx) = mpsc::channel::<u16>();
let sender = thread::spawn(move || {
let rt = LocalRuntime::new().unwrap();
rt.block_on(async move {
let port = port_rx.recv().unwrap();
let mut push = PushSocket::connect_with_options(
("127.0.0.1", port),
SocketOptions::default()
.with_buffer_sizes(16384, 16384)
.with_write_coalescing(true),
)
.await
.unwrap();
let payload = Bytes::from(vec![0u8; PAYLOAD]);
for _ in 0..(WARM + MEAS) {
push.send_one(payload.clone()).await.unwrap();
}
push.flush().await.unwrap();
});
});
let rt = LocalRuntime::new().unwrap();
rt.block_on(async move {
let listener = TcpListener::bind(ADDR).await.unwrap();
port_tx.send(listener.local_addr().unwrap().port()).unwrap();
let (stream, _) = listener.accept().await.unwrap();
let mut pull = PullSocket::from_tcp_with_options(
stream,
SocketOptions::default().with_buffer_sizes(16384, 16384),
)
.await
.unwrap();
let mut buf: Vec<Bytes> = Vec::with_capacity(4);
let mut warmed = 0usize;
while warmed < WARM {
if !pull.recv_into(&mut buf).await.unwrap() {
break;
}
warmed += 1;
while warmed < WARM && pull.try_recv_into(&mut buf).unwrap() {
warmed += 1;
}
}
COUNTING.store(1, Ordering::Relaxed);
let mut got = 0usize;
while got < MEAS {
if !pull.recv_into(&mut buf).await.unwrap() {
break;
}
got += 1;
while got < MEAS && pull.try_recv_into(&mut buf).unwrap() {
got += 1;
}
}
COUNTING.store(0, Ordering::Relaxed);
let allocs = ALLOCS.load(Ordering::Relaxed);
assert_eq!(got, MEAS, "received {got} of {MEAS} messages");
assert!(
allocs <= MAX_WINDOW_ALLOCS,
"single-frame hot path allocated {allocs} times over {MEAS} messages \
(ceiling {MAX_WINDOW_ALLOCS}). That is roughly one allocation per \
{} messages; a per-message allocation on the send or receive path \
would show ~{MEAS}. Something reintroduced per-message allocation.",
MEAS / allocs.max(1),
);
});
sender.join().unwrap();
}