use rusteron_client::*;
use rusteron_media_driver::testing::{find_unused_udp_port, EmbeddedDriver};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::thread::sleep;
use std::time::{Duration, Instant};
const REQUEST_STREAM_ID: i32 = 10001;
const RESPONSE_STREAM_ID: i32 = 10002;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let driver = EmbeddedDriver::launch()?;
let ctx = AeronContext::new()?;
ctx.set_dir(&cformat!("{}", driver.dir()))?;
ctx.set_error_handler(Some(|code: i32, msg: &str| eprintln!("aeron error {code}: {msg}")))?;
let aeron = Aeron::new(&ctx)?;
aeron.start()?;
let request_port = find_unused_udp_port(21000).expect("no free port");
let response_port = find_unused_udp_port(request_port + 1).expect("no free port");
let request_endpoint = format!("localhost:{request_port}");
let response_control_endpoint = format!("localhost:{response_port}");
let request_channel = AeronUriStringBuilder::udp(&request_endpoint)?.build(256)?;
let running = Arc::new(AtomicBool::new(true));
let server = {
let running = running.clone();
let response_control = response_control_endpoint.clone();
let request_channel = request_channel.clone();
let aeron_dir = driver.dir().to_string();
std::thread::spawn(move || -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let ctx = AeronContext::new()?;
ctx.set_dir(&cformat!("{aeron_dir}"))?;
let aeron = Aeron::new(&ctx)?;
aeron.start()?;
let pending_clients: Arc<Mutex<Vec<i64>>> = Arc::new(Mutex::new(Vec::new()));
let on_image_clients = pending_clients.clone();
let image_handler = Handler::new(move |_subscription: AeronSubscription, image: AeronImage| {
if let Ok(constants) = image.get_constants() {
on_image_clients.lock().unwrap().push(constants.correlation_id());
}
});
let server_subscription = aeron
.async_add_subscription(
&cformat!("{request_channel}"),
REQUEST_STREAM_ID,
Some(&image_handler),
Handlers::NONE,
)?
.poll_blocking(Duration::from_secs(5))?;
let mut responders: Vec<AeronPublication> = Vec::new();
let mut inbox: Vec<Vec<u8>> = Vec::new();
while running.load(Ordering::Acquire) {
for correlation_id in pending_clients.lock().unwrap().drain(..) {
let channel = AeronUriStringBuilder::udp_control(&response_control, ControlMode::Response)?
.response_correlation_id(correlation_id)?
.build(256)?;
let publication = aeron
.async_add_publication(&cformat!("{channel}"), RESPONSE_STREAM_ID)?
.poll_blocking(Duration::from_secs(5))?;
println!("[server] response publication ready for client image {correlation_id}");
responders.push(publication);
}
server_subscription.poll_fn(|buf, _hdr| inbox.push(buf.to_vec()), 16)?;
for request in inbox.drain(..) {
let reply = String::from_utf8_lossy(&request).to_uppercase();
println!(
"[server] request {:?} -> reply {:?}",
String::from_utf8_lossy(&request),
reply
);
for publication in &responders {
let deadline = Instant::now() + Duration::from_secs(5);
loop {
match publication.offer(reply.as_bytes()) {
Ok(_) => break,
Err(e) if e.is_retryable() && Instant::now() < deadline => {
sleep(Duration::from_millis(1))
}
Err(e) => return Err(format!("server reply failed: {e}").into()),
}
}
}
}
sleep(Duration::from_millis(1));
}
Ok(())
})
};
let response_subscription = aeron
.async_add_subscription(
&AeronUriStringBuilder::udp_control(&response_control_endpoint, ControlMode::Response)?
.build(256)?
.into_c_string(),
RESPONSE_STREAM_ID,
Handlers::NONE,
Handlers::NONE,
)?
.poll_blocking(Duration::from_secs(5))?;
let registration_id = response_subscription.get_constants()?.registration_id();
let request_publication = aeron
.async_add_publication(
&AeronUriStringBuilder::udp(&request_endpoint)?
.response_correlation_id(registration_id)?
.build(256)?
.into_c_string(),
REQUEST_STREAM_ID,
)?
.poll_blocking(Duration::from_secs(5))?;
println!("[client] requesting with response-correlation-id={registration_id}");
let deadline = Instant::now() + Duration::from_secs(10);
loop {
match request_publication.offer(b"hello response channels") {
Ok(_) => break,
Err(e) if e.is_retryable() && Instant::now() < deadline => sleep(Duration::from_millis(10)),
Err(e) => return Err(format!("request failed: {e}").into()),
}
}
let reply: Arc<Mutex<Option<String>>> = Arc::new(Mutex::new(None));
let reply_sink = reply.clone();
let deadline = Instant::now() + Duration::from_secs(10);
while reply.lock().unwrap().is_none() && Instant::now() < deadline {
response_subscription.poll_fn(
|buf, _hdr| {
*reply_sink.lock().unwrap() = Some(String::from_utf8_lossy(buf).to_string());
},
16,
)?;
sleep(Duration::from_millis(1));
}
let reply = reply.lock().unwrap().clone().ok_or("no response received")?;
println!("[client] got response {reply:?}");
assert_eq!(reply, "HELLO RESPONSE CHANNELS");
running.store(false, Ordering::Release);
server
.join()
.expect("server thread panicked")
.map_err(|e| e.to_string())?;
println!("request/response roundtrip complete");
Ok(())
}