Skip to content

Commit ca586c1

Browse files
committed
docs: add examples for deferred reply and selective receive
Add comprehensive examples demonstrating the new features: 1. gen_server_deferred_reply.rs: - Job processor with immediate and deferred replies - Demonstrates CallResponse::NoReply pattern - Shows async work with ReplyHandle - Proves mailbox remains responsive during deferred operations - Includes quick jobs, slow jobs, and API fetch patterns 2. actor_selective_receive.rs: - RPC pattern with correlation IDs - Client/Server with selective receive - Multiple concurrent requests demonstration - Non-blocking try_receive for monitoring - Shows messages staying in mailbox when not matched Both examples include: - Clear documentation and run instructions - Emoji-enhanced output for readability - Real-world use cases (job processing, RPC) - Comprehensive demonstration of API features
1 parent 85480cd commit ca586c1

2 files changed

Lines changed: 561 additions & 0 deletions

File tree

Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
//! Example demonstrating selective receive with RPC-style pattern.
2+
//!
3+
//! This example shows how to use selective receive (similar to Erlang's
4+
//! `receive` with pattern matching) to implement RPC with correlation IDs.
5+
//! Actors can wait for specific messages while leaving others in the mailbox.
6+
//!
7+
//! Run with: cargo run --example actor_selective_receive
8+
9+
use async_trait::async_trait;
10+
use joerl::{Actor, ActorContext, ActorSystem, Message, Pid};
11+
use std::sync::Arc;
12+
use std::time::Duration;
13+
use tokio::sync::Mutex;
14+
15+
/// Request message with correlation ID
16+
#[derive(Debug, Clone)]
17+
struct Request {
18+
correlation_id: u64,
19+
from: Pid,
20+
query: String,
21+
}
22+
23+
/// Response message with correlation ID
24+
#[derive(Debug, Clone)]
25+
struct Response {
26+
correlation_id: u64,
27+
result: String,
28+
}
29+
30+
/// Command to trigger RPC
31+
#[derive(Debug, Clone)]
32+
enum Command {
33+
MakeRequest(String),
34+
MakeMultipleRequests,
35+
}
36+
37+
/// Server actor that responds to requests
38+
struct ServerActor {
39+
name: String,
40+
}
41+
42+
#[async_trait]
43+
impl Actor for ServerActor {
44+
async fn handle_message(&mut self, msg: Message, ctx: &mut ActorContext) {
45+
if let Some(req) = msg.downcast_ref::<Request>() {
46+
println!(
47+
"🔵 [{}] Received request {} from {:?}: '{}'",
48+
self.name, req.correlation_id, req.from, req.query
49+
);
50+
51+
// Simulate processing time
52+
tokio::time::sleep(Duration::from_millis(100)).await;
53+
54+
// Send response back
55+
let response = Response {
56+
correlation_id: req.correlation_id,
57+
result: format!("[{}] Processed: {}", self.name, req.query),
58+
};
59+
60+
ctx.send(req.from, Box::new(response)).await.ok();
61+
println!(
62+
"✅ [{}] Sent response for request {}",
63+
self.name, req.correlation_id
64+
);
65+
}
66+
}
67+
}
68+
69+
/// Client actor that makes RPC calls using selective receive
70+
struct ClientActor {
71+
name: String,
72+
server: Pid,
73+
next_id: u64,
74+
results: Arc<Mutex<Vec<(u64, String)>>>,
75+
}
76+
77+
#[async_trait]
78+
impl Actor for ClientActor {
79+
async fn handle_message(&mut self, msg: Message, ctx: &mut ActorContext) {
80+
// Handle commands
81+
if let Some(cmd) = msg.downcast_ref::<Command>() {
82+
match cmd {
83+
Command::MakeRequest(query) => {
84+
let request_id = self.next_id;
85+
self.next_id += 1;
86+
87+
println!(
88+
"🟢 [{}] Making RPC call {} with query: '{}'",
89+
self.name, request_id, query
90+
);
91+
92+
// Send request
93+
let request = Request {
94+
correlation_id: request_id,
95+
from: ctx.pid(),
96+
query: query.clone(),
97+
};
98+
ctx.send(self.server, Box::new(request)).await.ok();
99+
100+
// Use selective receive to wait for specific response
101+
// Other messages will remain in mailbox
102+
let response = ctx
103+
.receive_timeout(
104+
|msg| {
105+
msg.downcast_ref::<Response>()
106+
.filter(|r| r.correlation_id == request_id)
107+
.cloned()
108+
},
109+
Duration::from_secs(2),
110+
)
111+
.await;
112+
113+
match response {
114+
Some(resp) => {
115+
println!(
116+
"✨ [{}] Got response for request {}: '{}'",
117+
self.name, request_id, resp.result
118+
);
119+
self.results
120+
.lock()
121+
.await
122+
.push((resp.correlation_id, resp.result));
123+
}
124+
None => {
125+
println!(
126+
"⏰ [{}] Timeout waiting for response to request {}",
127+
self.name, request_id
128+
);
129+
}
130+
}
131+
}
132+
133+
Command::MakeMultipleRequests => {
134+
// Make multiple concurrent requests
135+
// Each will wait for its specific response using correlation ID
136+
let queries = vec![
137+
"What is 2+2?",
138+
"What is the capital of France?",
139+
"What is Rust?",
140+
];
141+
142+
for query in queries {
143+
let request_id = self.next_id;
144+
self.next_id += 1;
145+
146+
println!(
147+
"🟢 [{}] Making RPC call {} with query: '{}'",
148+
self.name, request_id, query
149+
);
150+
151+
let request = Request {
152+
correlation_id: request_id,
153+
from: ctx.pid(),
154+
query: query.to_string(),
155+
};
156+
ctx.send(self.server, Box::new(request)).await.ok();
157+
}
158+
159+
// Now collect all responses - selective receive ensures
160+
// we get them in any order they arrive
161+
for _ in 0..3 {
162+
let response = ctx
163+
.receive_timeout(
164+
|msg| msg.downcast_ref::<Response>().cloned(),
165+
Duration::from_secs(2),
166+
)
167+
.await;
168+
169+
if let Some(resp) = response {
170+
println!(
171+
"✨ [{}] Got response {}: '{}'",
172+
self.name, resp.correlation_id, resp.result
173+
);
174+
self.results
175+
.lock()
176+
.await
177+
.push((resp.correlation_id, resp.result));
178+
}
179+
}
180+
}
181+
}
182+
}
183+
}
184+
}
185+
186+
/// Actor that demonstrates try_receive (non-blocking)
187+
struct MonitorActor {
188+
monitored: Pid,
189+
stats: Arc<Mutex<Vec<String>>>,
190+
}
191+
192+
#[async_trait]
193+
impl Actor for MonitorActor {
194+
async fn handle_message(&mut self, msg: Message, ctx: &mut ActorContext) {
195+
if let Some(cmd) = msg.downcast_ref::<&str>() {
196+
if *cmd == "check" {
197+
println!("👀 [Monitor] Checking for any responses...");
198+
199+
// Try to receive without blocking
200+
// This checks pending queue and current mailbox
201+
let response = ctx.try_receive(|msg| msg.downcast_ref::<Response>().cloned());
202+
203+
match response {
204+
Some(resp) => {
205+
let stat = format!("Found response {} in mailbox", resp.correlation_id);
206+
println!("📊 [Monitor] {}", stat);
207+
self.stats.lock().await.push(stat);
208+
}
209+
None => {
210+
println!("📊 [Monitor] No responses in mailbox");
211+
}
212+
}
213+
}
214+
}
215+
}
216+
}
217+
218+
#[tokio::main]
219+
async fn main() {
220+
println!("=== Selective Receive Example ===\n");
221+
222+
let system = Arc::new(ActorSystem::new());
223+
224+
// Spawn server
225+
let server = system.spawn(ServerActor {
226+
name: "Server".to_string(),
227+
});
228+
229+
// Spawn client
230+
let results = Arc::new(Mutex::new(Vec::new()));
231+
let client = system.spawn(ClientActor {
232+
name: "Client".to_string(),
233+
server: server.pid(),
234+
next_id: 1,
235+
results: results.clone(),
236+
});
237+
238+
println!("=== Single RPC Call ===\n");
239+
240+
// Make a single RPC call
241+
client
242+
.send(Box::new(Command::MakeRequest("Hello, Server!".to_string())))
243+
.await
244+
.unwrap();
245+
246+
tokio::time::sleep(Duration::from_millis(200)).await;
247+
248+
println!("\n=== Multiple Concurrent RPC Calls ===\n");
249+
250+
// Make multiple concurrent calls
251+
client
252+
.send(Box::new(Command::MakeMultipleRequests))
253+
.await
254+
.unwrap();
255+
256+
tokio::time::sleep(Duration::from_millis(500)).await;
257+
258+
println!("\n=== Try Receive (Non-blocking) ===\n");
259+
260+
// Spawn monitor that uses try_receive
261+
let monitor_stats = Arc::new(Mutex::new(Vec::new()));
262+
let monitor = system.spawn(MonitorActor {
263+
monitored: server.pid(),
264+
stats: monitor_stats.clone(),
265+
});
266+
267+
// Send some responses to server's mailbox for monitor to find
268+
for i in 1..=3 {
269+
monitor
270+
.send(Box::new(Response {
271+
correlation_id: 100 + i,
272+
result: format!("Background response {}", i),
273+
}))
274+
.await
275+
.unwrap();
276+
}
277+
278+
// Check multiple times
279+
for _ in 0..4 {
280+
monitor.send(Box::new("check")).await.unwrap();
281+
tokio::time::sleep(Duration::from_millis(50)).await;
282+
}
283+
284+
tokio::time::sleep(Duration::from_millis(200)).await;
285+
286+
// Print results
287+
println!("\n=== Results ===");
288+
let all_results = results.lock().await;
289+
println!("Client received {} responses:", all_results.len());
290+
for (id, result) in all_results.iter() {
291+
println!(" [{}] {}", id, result);
292+
}
293+
294+
let monitor_results = monitor_stats.lock().await;
295+
println!("\nMonitor stats: {} items found", monitor_results.len());
296+
for stat in monitor_results.iter() {
297+
println!(" {}", stat);
298+
}
299+
300+
println!("\n=== Key Concepts Demonstrated ===");
301+
println!("1. RPC with correlation IDs - wait for specific responses");
302+
println!("2. Selective receive with timeout - don't block forever");
303+
println!("3. Multiple concurrent requests - each gets its own response");
304+
println!("4. Non-blocking try_receive - check mailbox without waiting");
305+
println!("5. Messages not matching pattern stay in mailbox for later\n");
306+
}

0 commit comments

Comments
 (0)