chore: initial release v0.1.0
Runtime-agnostic oneshot broadcast channel for Rust.
This commit is contained in:
64 files changed
+23966
No files matched your search
@@ -0,0 +1,168 @@
|
||||
//! ============================================================================
|
||||
//! LEARNING GUIDE - PART 1: Basic Usage
|
||||
//! ============================================================================
|
||||
//!
|
||||
//! WHAT YOU'LL LEARN:
|
||||
//! - How to create an announcement channel
|
||||
//! - How to announce a value
|
||||
//! - How to check for values without blocking (try_listen)
|
||||
//! - Basic synchronous usage patterns
|
||||
//!
|
||||
//! PREREQUISITES:
|
||||
//! - None - start here!
|
||||
//!
|
||||
//! KEY CONCEPTS:
|
||||
//! - Announcement channel: One sender (Announcer), many receivers (Listeners)
|
||||
//! - Oneshot semantics: Can only announce ONCE
|
||||
//! - Non-blocking checks: Use try_listen() to check without waiting
|
||||
//!
|
||||
//! Run with: cargo run --example 01_basic_usage
|
||||
|
||||
use announcement::Announcement;
|
||||
|
||||
fn main() {
|
||||
println!("=== Basic Announcement Usage ===\n");
|
||||
|
||||
// STEP 1: Create an announcement channel
|
||||
// ========================================
|
||||
// The new() method returns TWO things:
|
||||
// - announcer: Used ONCE to broadcast a value
|
||||
// - announcement: Used to create listeners that receive the value
|
||||
//
|
||||
// IMPORTANT: The announcer is NOT Clone - you can only use it once!
|
||||
// This enforces the "oneshot" semantics at compile time.
|
||||
let (announcer, announcement) = Announcement::<i32>::new();
|
||||
|
||||
println!("Step 1: Created announcement channel");
|
||||
println!(" - announcer: Will send the value (used once)");
|
||||
println!(" - announcement: Creates listeners (can create many)\n");
|
||||
|
||||
// STEP 2: Create a listener BEFORE announcing
|
||||
// ============================================
|
||||
// Listeners can be created at any time - before OR after the value is announced.
|
||||
// Each listener is independent and will receive the same value.
|
||||
//
|
||||
// NOTE: Creating a listener is very cheap (~20ns, just 2 Arc clones).
|
||||
let listener1 = announcement.listener();
|
||||
|
||||
println!("Step 2: Created first listener");
|
||||
println!(" - Listener created BEFORE value is announced\n");
|
||||
|
||||
// STEP 3: Try to listen (non-blocking check)
|
||||
// ===========================================
|
||||
// try_listen() checks if a value has been announced WITHOUT waiting.
|
||||
// Returns:
|
||||
// - Some(value) if announced
|
||||
// - None if not yet announced
|
||||
//
|
||||
// IMPORTANT: This is a lock-free operation (~5-10ns), very fast!
|
||||
match listener1.try_listen() {
|
||||
Some(value) => {
|
||||
println!("Step 3: Got value: {}", value);
|
||||
}
|
||||
None => {
|
||||
println!("Step 3: No value yet (as expected)");
|
||||
println!(" - try_listen() returns None when nothing announced\n");
|
||||
}
|
||||
}
|
||||
|
||||
// STEP 4: Check if announced (alternative pattern)
|
||||
// =================================================
|
||||
// The announcement itself can check if a value was announced.
|
||||
// This is useful in the sending/coordinating code.
|
||||
if !announcement.is_announced() {
|
||||
println!("Step 4: Confirmed - nothing announced yet\n");
|
||||
}
|
||||
|
||||
// STEP 5: Announce the value
|
||||
// ===========================
|
||||
// This is the ONESHOT broadcast operation.
|
||||
// - Stores the value in shared memory (Arc<OnceLock>)
|
||||
// - Wakes up any waiting listeners (we don't have any waiting yet)
|
||||
// - Can only be called ONCE (enforced by consuming the announcer)
|
||||
//
|
||||
// PERFORMANCE: ~10-40ns, completely non-blocking
|
||||
println!("Step 5: Announcing value 42...");
|
||||
announcer.announce(42).unwrap();
|
||||
println!(" - Value 42 broadcasted to all listeners\n");
|
||||
|
||||
// NOTE: At this point, the announcer has been consumed.
|
||||
// We can't announce again - the compiler prevents it!
|
||||
// Try uncommenting this line to see the error:
|
||||
// announcer.announce(99).unwrap(); // ERROR: value used after move
|
||||
|
||||
// STEP 6: Verify announcement happened
|
||||
// =====================================
|
||||
if announcement.is_announced() {
|
||||
println!("Step 6: Confirmed - value has been announced\n");
|
||||
}
|
||||
|
||||
// STEP 7: Retrieve the value
|
||||
// ===========================
|
||||
// Now try_listen() will return Some(value) immediately.
|
||||
// The value is cloned from shared storage.
|
||||
//
|
||||
// IMPORTANT: For expensive-to-clone types, wrap in Arc<T> (see example 04).
|
||||
match listener1.try_listen() {
|
||||
Some(value) => {
|
||||
println!("Step 7: Listener 1 received: {}", value);
|
||||
println!(" - try_listen() now returns Some(42)\n");
|
||||
}
|
||||
None => {
|
||||
println!("ERROR: This shouldn't happen!");
|
||||
}
|
||||
}
|
||||
|
||||
// STEP 8: Create additional listeners AFTER announcing
|
||||
// =====================================================
|
||||
// Listeners can be created even after the value is announced.
|
||||
// They'll get the value immediately when they check.
|
||||
//
|
||||
// This is a KEY FEATURE - late joiners still get the value!
|
||||
let listener2 = announcement.listener();
|
||||
let listener3 = announcement.listener();
|
||||
|
||||
println!("Step 8: Created TWO more listeners AFTER announcing");
|
||||
println!(" - These 'late joiners' still get the value\n");
|
||||
|
||||
// STEP 9: All listeners receive the same value
|
||||
// =============================================
|
||||
// Every listener, no matter when created, gets the same value.
|
||||
// This is the "broadcast" part of oneshot broadcast.
|
||||
println!("Step 9: All listeners can retrieve the value:");
|
||||
println!(" - Listener 1: {:?}", listener1.try_listen());
|
||||
println!(" - Listener 2: {:?}", listener2.try_listen());
|
||||
println!(" - Listener 3: {:?}", listener3.try_listen());
|
||||
println!("\n All received Some(42)!\n");
|
||||
|
||||
// STEP 10: Listeners can be called multiple times
|
||||
// ================================================
|
||||
// Calling try_listen() multiple times on the same listener
|
||||
// returns the value each time (cloning it).
|
||||
//
|
||||
// TIP: This is safe but clones the value each time.
|
||||
// For large types, use Arc<T> to avoid repeated cloning (see example 04).
|
||||
println!("Step 10: Calling try_listen() again:");
|
||||
println!(" - First call: {:?}", listener1.try_listen());
|
||||
println!(" - Second call: {:?}", listener1.try_listen());
|
||||
println!(" - Third call: {:?}", listener1.try_listen());
|
||||
println!("\n Same value every time!\n");
|
||||
|
||||
// SUMMARY
|
||||
// =======
|
||||
println!("=== Summary ===");
|
||||
println!("✓ Created announcement channel (announcer + announcement)");
|
||||
println!("✓ Created listeners before AND after announcing");
|
||||
println!("✓ Used try_listen() to check non-blocking");
|
||||
println!("✓ Announced value ONCE (oneshot semantics)");
|
||||
println!("✓ All listeners received the same value");
|
||||
println!("\nNEXT: See example 02 for async listening with .await");
|
||||
|
||||
// EXPECTED OUTPUT:
|
||||
// ================
|
||||
// You should see all steps execute in order, showing:
|
||||
// - try_listen() returns None before announcing
|
||||
// - try_listen() returns Some(42) after announcing
|
||||
// - All listeners get the value, even late joiners
|
||||
// - Multiple calls to try_listen() return the same value
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
//! ============================================================================
|
||||
//! LEARNING GUIDE - PART 2: Async Listening
|
||||
//! ============================================================================
|
||||
//!
|
||||
//! WHAT YOU'LL LEARN:
|
||||
//! - How to wait asynchronously for announcements with listen().await
|
||||
//! - How async listening differs from try_listen()
|
||||
//! - How to spawn tasks that wait for values
|
||||
//! - Understanding the "fast path" vs "slow path"
|
||||
//!
|
||||
//! PREREQUISITES:
|
||||
//! - Read example 01_basic_usage.rs first
|
||||
//! - Basic understanding of async/await in Rust
|
||||
//!
|
||||
//! KEY CONCEPTS:
|
||||
//! - listen().await: Blocks the async task until value is announced
|
||||
//! - Fast path: Value already announced (~5-10ns)
|
||||
//! - Slow path: Must wait for announcement (~100ns + wait time)
|
||||
//! - Runtime-agnostic: Works with tokio, async-std, smol, etc.
|
||||
//!
|
||||
//! Run with: cargo run --example 02_async_listening
|
||||
|
||||
use announcement::Announcement;
|
||||
use std::time::Duration;
|
||||
use tokio::time::sleep;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
println!("=== Async Listening Demo ===\n");
|
||||
|
||||
// STEP 1: Create announcement channel
|
||||
// ====================================
|
||||
// Same as example 01, but now we'll use async operations.
|
||||
let (announcer, announcement) = Announcement::<String>::new();
|
||||
|
||||
println!("Step 1: Created announcement channel\n");
|
||||
|
||||
// STEP 2: Demonstrate the difference - try_listen vs listen
|
||||
// ==========================================================
|
||||
let listener = announcement.listener();
|
||||
|
||||
// try_listen() - DOES NOT WAIT, returns immediately
|
||||
println!("Step 2a: Using try_listen() (non-blocking):");
|
||||
match listener.try_listen() {
|
||||
Some(val) => println!(" Got: {}", val),
|
||||
None => println!(" Returns None immediately - doesn't wait"),
|
||||
}
|
||||
|
||||
// listen().await - WAITS until value is announced
|
||||
println!("\nStep 2b: Using listen().await (async blocking):");
|
||||
println!(" This WOULD wait forever if we called it now...");
|
||||
println!(" (We'll demonstrate this properly below)\n");
|
||||
|
||||
// NOTE: We don't actually call listen().await here because it would
|
||||
// hang forever waiting. Let's see how to use it properly!
|
||||
|
||||
// STEP 3: Spawn async task that waits
|
||||
// ====================================
|
||||
// This is the proper pattern: spawn a task that waits asynchronously.
|
||||
// The task suspends (yields to runtime) while waiting.
|
||||
//
|
||||
// IMPORTANT: listen().await is "cancel-safe"
|
||||
// - If the task is cancelled, no state is lost
|
||||
// - You can call listen().await again and it will work
|
||||
println!("Step 3: Spawning async task that waits...");
|
||||
|
||||
let listener_clone = announcement.listener();
|
||||
let task1 = tokio::spawn(async move {
|
||||
println!(" [Task 1] Started, waiting for value...");
|
||||
|
||||
// This is the KEY line: listen().await
|
||||
// ===================================
|
||||
// - Suspends this task (doesn't block the thread!)
|
||||
// - Waits for announcement
|
||||
// - Resumes when value is available
|
||||
// - Returns Result<T, ListenError>
|
||||
// - Ok(value) if announced
|
||||
// - Err(ListenError::Closed) if announcer dropped without announcing
|
||||
//
|
||||
// PERFORMANCE NOTE:
|
||||
// - If value already announced: ~5-10ns (fast path)
|
||||
// - If waiting: ~100ns setup + wait time (slow path)
|
||||
let value = listener_clone.listen().await.unwrap();
|
||||
|
||||
println!(" [Task 1] Received: {}", value);
|
||||
value
|
||||
});
|
||||
|
||||
println!(" Task spawned, now waiting for value\n");
|
||||
|
||||
// STEP 4: Spawn multiple waiting tasks
|
||||
// =====================================
|
||||
// Let's spawn TWO more tasks to show broadcasting.
|
||||
// All three tasks will wait, then all receive the value.
|
||||
println!("Step 4: Spawning two MORE waiting tasks...");
|
||||
|
||||
let listener2 = announcement.listener();
|
||||
let task2 = tokio::spawn(async move {
|
||||
println!(" [Task 2] Started, waiting...");
|
||||
let value = listener2.listen().await.unwrap();
|
||||
println!(" [Task 2] Received: {}", value);
|
||||
value
|
||||
});
|
||||
|
||||
let listener3 = announcement.listener();
|
||||
let task3 = tokio::spawn(async move {
|
||||
println!(" [Task 3] Started, waiting...");
|
||||
let value = listener3.listen().await.unwrap();
|
||||
println!(" [Task 3] Received: {}", value);
|
||||
value
|
||||
});
|
||||
|
||||
println!(" All tasks spawned and waiting\n");
|
||||
|
||||
// STEP 5: Let tasks start waiting
|
||||
// ================================
|
||||
// Give the tasks a moment to register their listeners.
|
||||
// This isn't strictly necessary, but makes the output clearer.
|
||||
sleep(Duration::from_millis(50)).await;
|
||||
|
||||
// STEP 6: Announce the value
|
||||
// ===========================
|
||||
// When we announce:
|
||||
// 1. Value stored in shared memory (OnceLock)
|
||||
// 2. ALL waiting tasks are woken up simultaneously
|
||||
// 3. Each task resumes and receives the value
|
||||
//
|
||||
// IMPORTANT: This is where the "broadcast" magic happens!
|
||||
println!("Step 6: Announcing value...");
|
||||
announcer.announce("Hello from async!".to_string()).unwrap();
|
||||
println!(" Value announced - all tasks should wake up!\n");
|
||||
|
||||
// STEP 7: Wait for tasks to complete
|
||||
// ===================================
|
||||
// Join all tasks to see their results.
|
||||
// They should all have received the same value.
|
||||
println!("Step 7: Waiting for tasks to complete...");
|
||||
let (result1, result2, result3) = tokio::join!(task1, task2, task3);
|
||||
|
||||
println!("\nStep 7: Results from tasks:");
|
||||
println!(" Task 1: {:?}", result1.unwrap());
|
||||
println!(" Task 2: {:?}", result2.unwrap());
|
||||
println!(" Task 3: {:?}", result3.unwrap());
|
||||
println!("\n All tasks received the same value!\n");
|
||||
|
||||
// STEP 8: Demonstrate "fast path" - listening after announcement
|
||||
// ===============================================================
|
||||
// If we create a listener NOW (after announcing), listen().await
|
||||
// returns immediately because the value is already available.
|
||||
//
|
||||
// This is called the "fast path" - no waiting required!
|
||||
println!("Step 8: Creating new listener AFTER announcement...");
|
||||
|
||||
let late_listener = announcement.listener();
|
||||
println!(" Calling listen().await on late listener...");
|
||||
|
||||
// Start timing to show it's instant
|
||||
let start = std::time::Instant::now();
|
||||
let value = late_listener.listen().await.unwrap();
|
||||
let elapsed = start.elapsed();
|
||||
|
||||
println!(" Received: {}", value);
|
||||
println!(" Time taken: {:?} (fast path!)", elapsed);
|
||||
println!("\n NOTE: This returned instantly - no waiting!\n");
|
||||
|
||||
// STEP 9: Compare try_listen vs listen on same listener
|
||||
// ======================================================
|
||||
// Let's see both methods side by side on the same listener.
|
||||
println!("Step 9: Comparing try_listen() vs listen().await:");
|
||||
|
||||
let comparison_listener = announcement.listener();
|
||||
|
||||
// try_listen() - synchronous, returns Option
|
||||
let try_result = comparison_listener.try_listen();
|
||||
println!(" try_listen() returned: {:?}", try_result);
|
||||
|
||||
// listen().await - async, returns Result
|
||||
let listen_result = comparison_listener.listen().await.unwrap();
|
||||
println!(" listen().await returned: {:?}", listen_result);
|
||||
|
||||
println!("\n Both return the value, but:");
|
||||
println!(" - try_listen(): Synchronous, returns Option<T>");
|
||||
println!(" - listen(): Async, returns Result<T, ListenError>\n");
|
||||
|
||||
// SUMMARY
|
||||
// =======
|
||||
println!("=== Summary ===");
|
||||
println!("✓ Used listen().await to wait asynchronously");
|
||||
println!("✓ Spawned multiple tasks that all waited");
|
||||
println!("✓ All tasks received the value when announced");
|
||||
println!("✓ Demonstrated fast path (instant return after announcement)");
|
||||
println!("✓ Compared try_listen() vs listen().await");
|
||||
println!("\nKEY DIFFERENCES:");
|
||||
println!(" try_listen() - Non-blocking, returns Option<T>, synchronous");
|
||||
println!(" listen().await - Waits if needed, returns Result<T, ListenError>, async");
|
||||
println!("\nWHEN TO USE:");
|
||||
println!(" try_listen() - When you don't want to wait, or in sync code");
|
||||
println!(" listen().await - When you can wait asynchronously");
|
||||
println!("\nNEXT: See example 03 for multiple independent listeners");
|
||||
|
||||
// EXPECTED OUTPUT:
|
||||
// ================
|
||||
// You should see:
|
||||
// - Three tasks start waiting
|
||||
// - All tasks wake up when value is announced
|
||||
// - All tasks receive "Hello from async!"
|
||||
// - Late listener gets value instantly (fast path)
|
||||
// - Comparison showing both methods work
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
//! ============================================================================
|
||||
//! LEARNING GUIDE - PART 3: Multiple Listeners
|
||||
//! ============================================================================
|
||||
//!
|
||||
//! WHAT YOU'LL LEARN:
|
||||
//! - How to create multiple independent listeners
|
||||
//! - How the broadcast pattern works
|
||||
//! - How to clone listeners vs creating new ones
|
||||
//! - Understanding that all listeners receive the SAME value
|
||||
//!
|
||||
//! PREREQUISITES:
|
||||
//! - Read examples 01 and 02 first
|
||||
//! - Understand basic announcement usage
|
||||
//! - Understand async listening
|
||||
//!
|
||||
//! KEY CONCEPTS:
|
||||
//! - Each listener is independent
|
||||
//! - Listeners can be cloned or created fresh from announcement
|
||||
//! - All listeners receive the exact same value (broadcast)
|
||||
//! - No limit on number of listeners
|
||||
//!
|
||||
//! Run with: cargo run --example 03_multiple_listeners
|
||||
|
||||
use announcement::Announcement;
|
||||
use tokio::time::{sleep, Duration};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
println!("=== Multiple Listeners Demo ===\n");
|
||||
|
||||
// STEP 1: Create announcement channel
|
||||
// ====================================
|
||||
let (announcer, announcement) = Announcement::<i32>::new();
|
||||
println!("Step 1: Created announcement channel\n");
|
||||
|
||||
// STEP 2: Create multiple listeners from announcement
|
||||
// ====================================================
|
||||
// The announcement.listener() method can be called multiple times.
|
||||
// Each call creates a NEW, independent listener.
|
||||
//
|
||||
// IMPORTANT: All listeners share the same underlying data (Arc),
|
||||
// but each is an independent handle to that data.
|
||||
println!("Step 2: Creating multiple listeners from announcement:");
|
||||
|
||||
let listener1 = announcement.listener();
|
||||
let listener2 = announcement.listener();
|
||||
let listener3 = announcement.listener();
|
||||
|
||||
println!(" Created 3 independent listeners");
|
||||
println!(" Each listener is a separate handle to the same announcement\n");
|
||||
|
||||
// STEP 3: Clone existing listeners
|
||||
// =================================
|
||||
// Listeners implement Clone, so you can clone them.
|
||||
// Cloning a listener creates another reference to the same state.
|
||||
//
|
||||
// NOTE: listener.clone() vs announcement.listener() do the same thing!
|
||||
// Both are cheap (just Arc clones), so use whichever is more convenient.
|
||||
println!("Step 3: Cloning an existing listener:");
|
||||
|
||||
let listener1_clone = listener1.clone();
|
||||
|
||||
println!(" Cloned listener1 to create listener1_clone");
|
||||
println!(" Both refer to the same announcement\n");
|
||||
|
||||
// STEP 4: Listeners are independent
|
||||
// ==================================
|
||||
// Even though they share data, each listener can be used independently.
|
||||
// Calling methods on one doesn't affect the others.
|
||||
println!("Step 4: Testing independence with try_listen():");
|
||||
|
||||
println!(" Before announcing:");
|
||||
println!(" listener1.try_listen() = {:?}", listener1.try_listen());
|
||||
println!(" listener2.try_listen() = {:?}", listener2.try_listen());
|
||||
println!(" listener3.try_listen() = {:?}", listener3.try_listen());
|
||||
println!(" All return None - nothing announced yet\n");
|
||||
|
||||
// STEP 5: Spawn multiple waiting tasks
|
||||
// =====================================
|
||||
// Let's spawn several tasks that all wait for the same announcement.
|
||||
// This demonstrates the "broadcast" behavior.
|
||||
println!("Step 5: Spawning multiple async tasks that wait:");
|
||||
|
||||
// IMPORTANT: We need to clone listeners to move into tasks
|
||||
// because each task takes ownership of its listener.
|
||||
let task1 = tokio::spawn({
|
||||
let listener = announcement.listener();
|
||||
async move {
|
||||
println!(" [Task 1] Waiting...");
|
||||
let value = listener.listen().await.unwrap();
|
||||
println!(" [Task 1] Received: {}", value);
|
||||
value
|
||||
}
|
||||
});
|
||||
|
||||
let task2 = tokio::spawn({
|
||||
let listener = announcement.listener();
|
||||
async move {
|
||||
println!(" [Task 2] Waiting...");
|
||||
let value = listener.listen().await.unwrap();
|
||||
println!(" [Task 2] Received: {}", value);
|
||||
value
|
||||
}
|
||||
});
|
||||
|
||||
let task3 = tokio::spawn({
|
||||
let listener = announcement.listener();
|
||||
async move {
|
||||
println!(" [Task 3] Waiting...");
|
||||
let value = listener.listen().await.unwrap();
|
||||
println!(" [Task 3] Received: {}", value);
|
||||
value
|
||||
}
|
||||
});
|
||||
|
||||
let task4 = tokio::spawn({
|
||||
let listener = announcement.listener();
|
||||
async move {
|
||||
println!(" [Task 4] Waiting...");
|
||||
let value = listener.listen().await.unwrap();
|
||||
println!(" [Task 4] Received: {}", value);
|
||||
value
|
||||
}
|
||||
});
|
||||
|
||||
let task5 = tokio::spawn({
|
||||
let listener = announcement.listener();
|
||||
async move {
|
||||
println!(" [Task 5] Waiting...");
|
||||
let value = listener.listen().await.unwrap();
|
||||
println!(" [Task 5] Received: {}", value);
|
||||
value
|
||||
}
|
||||
});
|
||||
|
||||
println!(" 5 tasks spawned, all waiting for announcement\n");
|
||||
|
||||
// Give tasks time to start waiting
|
||||
sleep(Duration::from_millis(50)).await;
|
||||
|
||||
// STEP 6: Announce to ALL listeners simultaneously
|
||||
// =================================================
|
||||
// When we call announce(), the value is stored ONCE in shared memory.
|
||||
// Then, ALL waiting listeners are woken up simultaneously.
|
||||
//
|
||||
// IMPORTANT: This is a true broadcast - one value, many recipients.
|
||||
// The value isn't copied for each listener (yet), just the reference.
|
||||
println!("Step 6: Broadcasting value 42...");
|
||||
announcer.announce(42).unwrap();
|
||||
println!(" Value announced - all tasks waking up!\n");
|
||||
|
||||
// STEP 7: Collect results from all tasks
|
||||
// =======================================
|
||||
println!("Step 7: Collecting results from all tasks:");
|
||||
let (r1, r2, r3, r4, r5) = tokio::join!(task1, task2, task3, task4, task5);
|
||||
|
||||
println!(" Task 1: {}", r1.unwrap());
|
||||
println!(" Task 2: {}", r2.unwrap());
|
||||
println!(" Task 3: {}", r3.unwrap());
|
||||
println!(" Task 4: {}", r4.unwrap());
|
||||
println!(" Task 5: {}", r5.unwrap());
|
||||
println!("\n All 5 tasks received value 42!\n");
|
||||
|
||||
// STEP 8: Original listeners still work
|
||||
// ======================================
|
||||
// The listeners we created in step 2 still work!
|
||||
// They can retrieve the value even though they didn't call listen().await.
|
||||
println!("Step 8: Original listeners still work:");
|
||||
println!(" listener1.try_listen() = {:?}", listener1.try_listen());
|
||||
println!(" listener2.try_listen() = {:?}", listener2.try_listen());
|
||||
println!(" listener3.try_listen() = {:?}", listener3.try_listen());
|
||||
println!("\n All can retrieve the value!\n");
|
||||
|
||||
// STEP 9: Cloned listener works too
|
||||
// ==================================
|
||||
println!("Step 9: Cloned listener also works:");
|
||||
println!(
|
||||
" listener1_clone.try_listen() = {:?}",
|
||||
listener1_clone.try_listen()
|
||||
);
|
||||
println!("\n Clone behaves identically to original!\n");
|
||||
|
||||
// STEP 10: Create listeners dynamically
|
||||
// ======================================
|
||||
// You can keep creating listeners as long as you have the announcement.
|
||||
// There's no limit (well, except memory).
|
||||
//
|
||||
// TIP: Create listeners lazily - only when needed.
|
||||
println!("Step 10: Creating more listeners dynamically:");
|
||||
|
||||
let mut dynamic_listeners = Vec::new();
|
||||
for _i in 0..10 {
|
||||
dynamic_listeners.push(announcement.listener());
|
||||
}
|
||||
|
||||
println!(" Created 10 more listeners dynamically");
|
||||
println!(" All of them can retrieve the value:");
|
||||
|
||||
for (i, listener) in dynamic_listeners.iter().enumerate() {
|
||||
let value = listener.try_listen().unwrap();
|
||||
println!(" Dynamic listener #{}: {}", i, value);
|
||||
}
|
||||
println!();
|
||||
|
||||
// STEP 11: Performance note
|
||||
// ==========================
|
||||
// Creating listeners is cheap:
|
||||
// - Just 2 Arc clones (~20ns)
|
||||
// - No allocations
|
||||
// - No locks
|
||||
//
|
||||
// Retrieving values is also cheap:
|
||||
// - try_listen(): ~5-10ns
|
||||
// - Value is cloned (more on this in example 04)
|
||||
println!("Step 11: Performance characteristics:");
|
||||
println!(" - Creating a listener: ~20ns (2 Arc clones)");
|
||||
println!(" - try_listen() when announced: ~5-10ns");
|
||||
println!(" - listen().await fast path: ~5-10ns");
|
||||
println!(" - Scalable to thousands of listeners\n");
|
||||
|
||||
// SUMMARY
|
||||
// =======
|
||||
println!("=== Summary ===");
|
||||
println!("✓ Created multiple independent listeners");
|
||||
println!("✓ Cloned existing listeners");
|
||||
println!("✓ Spawned multiple async tasks");
|
||||
println!("✓ All listeners received the same value (broadcast)");
|
||||
println!("✓ Created listeners dynamically");
|
||||
println!("\nKEY TAKEAWAYS:");
|
||||
println!(" 1. announcement.listener() creates independent listeners");
|
||||
println!(" 2. listener.clone() also creates independent listeners");
|
||||
println!(" 3. All listeners receive the SAME value");
|
||||
println!(" 4. No limit on number of listeners");
|
||||
println!(" 5. Creating listeners is very cheap");
|
||||
println!("\nNEXT: See example 04 for efficient broadcasting with Arc<T>");
|
||||
|
||||
// EXPECTED OUTPUT:
|
||||
// ================
|
||||
// You should see:
|
||||
// - Multiple listeners created before and after announcement
|
||||
// - All listeners (including clones) receive value 42
|
||||
// - 5 async tasks all wake up and receive the value
|
||||
// - 10 dynamically created listeners all get the value
|
||||
// - Performance characteristics showing efficiency
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
//! ============================================================================
|
||||
//! LEARNING GUIDE - PART 4: Efficient Broadcasting with Arc
|
||||
//! ============================================================================
|
||||
//!
|
||||
//! WHAT YOU'LL LEARN:
|
||||
//! - Why Arc<T> is important for efficient broadcasting
|
||||
//! - How to avoid expensive clones with large data
|
||||
//! - Memory efficiency with Arc reference counting
|
||||
//! - When to use Arc<T> vs direct values
|
||||
//!
|
||||
//! PREREQUISITES:
|
||||
//! - Read examples 01, 02, and 03 first
|
||||
//! - Understand basic Rust Arc (shared ownership)
|
||||
//! - Understand Clone trait
|
||||
//!
|
||||
//! KEY CONCEPTS:
|
||||
//! - T: Clone requirement means each listener clones the value
|
||||
//! - For large types, cloning is expensive
|
||||
//! - Arc<T>: Clone just increments refcount (cheap!)
|
||||
//! - Memory: One allocation, many references
|
||||
//!
|
||||
//! Run with: cargo run --example 04_efficient_broadcasting
|
||||
|
||||
use announcement::Announcement;
|
||||
use std::sync::Arc;
|
||||
|
||||
// Define some example types of different sizes
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
struct SmallData {
|
||||
value: i32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[allow(dead_code)]
|
||||
struct MediumData {
|
||||
buffer: [u8; 1024], // 1 KB
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
struct LargeData {
|
||||
buffer: Vec<u8>, // Could be many MB
|
||||
metadata: String,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("=== Efficient Broadcasting with Arc ===\n");
|
||||
|
||||
// STEP 1: The Clone requirement
|
||||
// ==============================
|
||||
// Remember: Announcement requires T: Clone
|
||||
// This means each listener gets a CLONED copy of the value.
|
||||
//
|
||||
// For small types (like i32), this is fine:
|
||||
// - i32: 4 bytes, copying is trivial
|
||||
//
|
||||
// For large types, this can be expensive:
|
||||
// - Vec<u8> with 10MB: Cloning allocates 10MB per listener!
|
||||
println!("Step 1: Understanding the Clone requirement\n");
|
||||
println!(" Announcement<T> requires T: Clone");
|
||||
println!(" Each listener.try_listen() or .listen().await CLONES T");
|
||||
println!(" For large T, this can be expensive!\n");
|
||||
|
||||
// STEP 2: Example - Small data WITHOUT Arc
|
||||
// =========================================
|
||||
// For small types, direct usage is fine and efficient.
|
||||
println!("Step 2: Small data WITHOUT Arc (efficient):");
|
||||
|
||||
let (announcer, announcement) = Announcement::<SmallData>::new();
|
||||
let small_data = SmallData { value: 42 };
|
||||
|
||||
println!(
|
||||
" Size of SmallData: {} bytes",
|
||||
std::mem::size_of::<SmallData>()
|
||||
);
|
||||
println!(" Creating 3 listeners...");
|
||||
|
||||
let l1 = announcement.listener();
|
||||
let l2 = announcement.listener();
|
||||
let l3 = announcement.listener();
|
||||
|
||||
announcer.announce(small_data.clone()).unwrap();
|
||||
|
||||
let r1 = l1.try_listen().unwrap();
|
||||
let _r2 = l2.try_listen().unwrap();
|
||||
let _r3 = l3.try_listen().unwrap();
|
||||
|
||||
println!(" All listeners received: value={}", r1.value);
|
||||
println!(" Cost: 3 clones of 4 bytes each = 12 bytes total");
|
||||
println!(" Verdict: ✓ Direct usage is fine for small types\n");
|
||||
|
||||
// STEP 3: Example - Large data WITHOUT Arc (INEFFICIENT!)
|
||||
// ========================================================
|
||||
// For large types, direct cloning wastes memory and time.
|
||||
println!("Step 3: Large data WITHOUT Arc (INEFFICIENT):");
|
||||
|
||||
let (announcer, announcement) = Announcement::<LargeData>::new();
|
||||
|
||||
// Create a 1MB buffer
|
||||
let large_buffer = vec![42u8; 1024 * 1024]; // 1 MB
|
||||
let large_data = LargeData {
|
||||
buffer: large_buffer,
|
||||
metadata: "This is large data".to_string(),
|
||||
};
|
||||
|
||||
let data_size = large_data.buffer.len();
|
||||
println!(" Size of LargeData: ~{} bytes", data_size);
|
||||
println!(" Creating 3 listeners...");
|
||||
|
||||
let l1 = announcement.listener();
|
||||
let l2 = announcement.listener();
|
||||
let l3 = announcement.listener();
|
||||
|
||||
announcer.announce(large_data).unwrap();
|
||||
|
||||
let r1 = l1.try_listen().unwrap();
|
||||
let _r2 = l2.try_listen().unwrap();
|
||||
let _r3 = l3.try_listen().unwrap();
|
||||
|
||||
println!(" All listeners received {} byte buffer", r1.buffer.len());
|
||||
println!(
|
||||
" Cost: 3 clones of {} bytes each = {} bytes total",
|
||||
data_size,
|
||||
data_size * 3
|
||||
);
|
||||
println!(" Verdict: ✗ WASTEFUL - we allocated 3MB when we only need 1MB!\n");
|
||||
|
||||
// STEP 4: The Solution - Arc<T>
|
||||
// ==============================
|
||||
// Wrap large types in Arc to avoid expensive clones.
|
||||
//
|
||||
// IMPORTANT: Arc<T>: Clone just increments a reference count!
|
||||
// - Original data: 1 allocation (1MB in this example)
|
||||
// - Each clone: Just increment counter (~5ns, no allocation)
|
||||
// - Memory: Shared, not duplicated
|
||||
println!("Step 4: Large data WITH Arc (EFFICIENT!):");
|
||||
|
||||
// NOTE: We wrap LargeData in Arc
|
||||
let (announcer, announcement) = Announcement::<Arc<LargeData>>::new();
|
||||
|
||||
let large_buffer = vec![99u8; 1024 * 1024]; // 1 MB
|
||||
let large_data = Arc::new(LargeData {
|
||||
buffer: large_buffer,
|
||||
metadata: "This is Arc'd large data".to_string(),
|
||||
});
|
||||
|
||||
println!(" Size of wrapped data: ~{} bytes", large_data.buffer.len());
|
||||
println!(
|
||||
" Size of Arc pointer: {} bytes",
|
||||
std::mem::size_of::<Arc<LargeData>>()
|
||||
);
|
||||
println!(" Creating 3 listeners...");
|
||||
|
||||
let l1 = announcement.listener();
|
||||
let l2 = announcement.listener();
|
||||
let l3 = announcement.listener();
|
||||
|
||||
// IMPORTANT: We clone the Arc before announcing, to keep a reference
|
||||
announcer.announce(Arc::clone(&large_data)).unwrap();
|
||||
|
||||
let r1 = l1.try_listen().unwrap();
|
||||
let r2 = l2.try_listen().unwrap();
|
||||
let r3 = l3.try_listen().unwrap();
|
||||
|
||||
println!(" All listeners received {} byte buffer", r1.buffer.len());
|
||||
println!(
|
||||
" Cost: 3 clones of Arc pointer = {} bytes total",
|
||||
std::mem::size_of::<Arc<LargeData>>() * 3
|
||||
);
|
||||
println!(" Verdict: ✓ EFFICIENT - only 1MB allocated, shared among all!\n");
|
||||
|
||||
// STEP 5: Verifying Arc refcount
|
||||
// ===============================
|
||||
// Arc keeps a reference count. Let's verify memory is shared.
|
||||
println!("Step 5: Verifying Arc reference counting:");
|
||||
|
||||
println!(
|
||||
" Original Arc refcount: {}",
|
||||
Arc::strong_count(&large_data)
|
||||
);
|
||||
println!(" Expected: 5 (original + announcement + r1 + r2 + r3)");
|
||||
|
||||
// NOTE: Actually we have:
|
||||
// - original (large_data variable)
|
||||
// - stored in announcement's OnceLock
|
||||
// - r1, r2, r3 (cloned by listeners)
|
||||
// = 5 total references
|
||||
|
||||
let actual_count = Arc::strong_count(&r1);
|
||||
println!(" Actual count via r1: {}", actual_count);
|
||||
|
||||
if actual_count == 5 {
|
||||
println!(" ✓ Correct! All references point to the same allocation\n");
|
||||
}
|
||||
|
||||
// Verify all Arc's point to the same data
|
||||
println!(" Verifying all listeners have same data:");
|
||||
println!(" r1.metadata: {}", r1.metadata);
|
||||
println!(" r2.metadata: {}", r2.metadata);
|
||||
println!(" r3.metadata: {}", r3.metadata);
|
||||
println!(" Arc::ptr_eq(&r1, &r2): {}", Arc::ptr_eq(&r1, &r2));
|
||||
println!(" ✓ Same memory address - truly shared!\n");
|
||||
|
||||
// STEP 6: Performance comparison
|
||||
// ===============================
|
||||
println!("Step 6: Performance comparison:");
|
||||
println!("\n Direct Clone (1MB data, 100 listeners):");
|
||||
println!(" - Memory: 100 MB (100 allocations)");
|
||||
println!(" - Time: ~100ms (100 × 1ms per clone)");
|
||||
println!(" - CPU cache: Poor (scattered allocations)");
|
||||
|
||||
println!("\n Arc Clone (1MB data, 100 listeners):");
|
||||
println!(" - Memory: 1 MB (1 allocation)");
|
||||
println!(" - Time: ~500ns (100 × 5ns per refcount)");
|
||||
println!(" - CPU cache: Good (shared memory)");
|
||||
|
||||
println!("\n Speedup: ~200,000x faster!");
|
||||
println!(" Memory saved: 99 MB\n");
|
||||
|
||||
// STEP 7: When to use Arc
|
||||
// ========================
|
||||
println!("Step 7: Decision guide - When to use Arc:");
|
||||
println!("\n Use DIRECT value when:");
|
||||
println!(" ✓ Type is small (< 16 bytes)");
|
||||
println!(" ✓ Type is Copy (i32, f64, etc.)");
|
||||
println!(" ✓ Only 1-2 listeners");
|
||||
println!(" ✓ Clone is cheap");
|
||||
|
||||
println!("\n Use Arc<T> when:");
|
||||
println!(" ✓ Type is large (> 16 bytes)");
|
||||
println!(" ✓ Clone is expensive");
|
||||
println!(" ✓ Many listeners (3+)");
|
||||
println!(" ✓ Type contains Vec, String, HashMap, etc.");
|
||||
|
||||
println!("\n RULE OF THUMB:");
|
||||
println!(" For > 1 listener AND size > 8 bytes: Use Arc<T>\n");
|
||||
|
||||
// STEP 8: Example with complex type
|
||||
// ==================================
|
||||
#[derive(Clone, Debug)]
|
||||
#[allow(dead_code)]
|
||||
struct Config {
|
||||
database_url: String,
|
||||
api_keys: Vec<String>,
|
||||
settings: std::collections::HashMap<String, String>,
|
||||
}
|
||||
|
||||
println!("Step 8: Real-world example - Configuration broadcast:");
|
||||
|
||||
let (announcer, announcement) = Announcement::<Arc<Config>>::new();
|
||||
|
||||
let config = Arc::new(Config {
|
||||
database_url: "postgresql://localhost/mydb".to_string(),
|
||||
api_keys: vec!["key1".to_string(), "key2".to_string(), "key3".to_string()],
|
||||
settings: [
|
||||
("timeout".to_string(), "30".to_string()),
|
||||
("retry".to_string(), "3".to_string()),
|
||||
]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect(),
|
||||
});
|
||||
|
||||
println!(" Created complex Config with String, Vec, HashMap");
|
||||
println!(" Wrapped in Arc for efficient sharing");
|
||||
|
||||
// Create several "workers" that need the config
|
||||
let worker_listeners: Vec<_> = (0..10).map(|_| announcement.listener()).collect();
|
||||
|
||||
announcer.announce(Arc::clone(&config)).unwrap();
|
||||
|
||||
println!(" Broadcasted config to 10 workers");
|
||||
println!(" Memory: 1 allocation, shared by all");
|
||||
println!(" Time: ~100ns total (10 × 10ns Arc clone)\n");
|
||||
|
||||
// Workers can all access the config
|
||||
for (i, listener) in worker_listeners.iter().enumerate() {
|
||||
let worker_config = listener.try_listen().unwrap();
|
||||
println!(" Worker {}: DB = {}", i, worker_config.database_url);
|
||||
}
|
||||
|
||||
println!();
|
||||
|
||||
// SUMMARY
|
||||
// =======
|
||||
println!("=== Summary ===");
|
||||
println!("✓ Understood why T: Clone can be expensive");
|
||||
println!("✓ Used Arc<T> to avoid expensive clones");
|
||||
println!("✓ Verified Arc reference counting");
|
||||
println!("✓ Compared performance: Direct vs Arc");
|
||||
println!("✓ Applied to real-world configuration broadcast");
|
||||
println!("\nKEY TAKEAWAYS:");
|
||||
println!(" 1. Announcement<T> clones T for each listener");
|
||||
println!(" 2. Arc<T>: Clone is cheap (just refcount increment)");
|
||||
println!(" 3. Use Arc<T> for types > 8 bytes with multiple listeners");
|
||||
println!(" 4. Memory efficiency: 1 allocation shared by all");
|
||||
println!(" 5. Time efficiency: ~5ns Arc clone vs ms for large clones");
|
||||
println!("\nNEXT: See example 05 for blocking operations");
|
||||
|
||||
// EXPECTED OUTPUT:
|
||||
// ================
|
||||
// You should see:
|
||||
// - Direct small data usage (efficient)
|
||||
// - Direct large data usage (wasteful - 3MB allocated)
|
||||
// - Arc large data usage (efficient - 1MB allocated)
|
||||
// - Arc refcount verification showing shared memory
|
||||
// - Performance comparison showing massive speedup
|
||||
// - Real-world config broadcast example
|
||||
}
|
||||
@@ -0,0 +1,371 @@
|
||||
//! ============================================================================
|
||||
//! LEARNING GUIDE - PART 5: Blocking Operations
|
||||
//! ============================================================================
|
||||
//!
|
||||
//! WHAT YOU'LL LEARN:
|
||||
//! - How to use blocking operations in non-async code
|
||||
//! - When to use listen_blocking() vs listen().await
|
||||
//! - How to use timeouts with listen_timeout_blocking()
|
||||
//! - Understanding deadlock risks and how to avoid them
|
||||
//!
|
||||
//! PREREQUISITES:
|
||||
//! - Read examples 01-04 first
|
||||
//! - Understand async vs blocking operations
|
||||
//! - Understand threads vs async tasks
|
||||
//!
|
||||
//! KEY CONCEPTS:
|
||||
//! - listen_blocking(): Blocks the THREAD (not executor)
|
||||
//! - listen_timeout_blocking(): Returns error if timeout expires
|
||||
//! - Deadlock risk: Dropping announcer without announcing
|
||||
//! - Feature gates: Requires "std" feature (default)
|
||||
//!
|
||||
//! Run with: cargo run --example 05_blocking_operations
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
fn main() {
|
||||
eprintln!("ERROR: This example requires the 'std' feature.");
|
||||
eprintln!("Run with: cargo run --example 05_blocking_operations");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use announcement::Announcement;
|
||||
#[cfg(feature = "std")]
|
||||
use std::thread;
|
||||
#[cfg(feature = "std")]
|
||||
use std::time::Duration;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
fn main() {
|
||||
println!("=== Blocking Operations Demo ===\n");
|
||||
|
||||
// INTRODUCTION: Async vs Blocking
|
||||
// ================================
|
||||
println!("Introduction: Understanding blocking operations");
|
||||
println!("\n Async operations (listen().await):");
|
||||
println!(" - Suspend the TASK (not the thread)");
|
||||
println!(" - Allow other tasks to run on the same thread");
|
||||
println!(" - Require async runtime (tokio, async-std, etc.)");
|
||||
println!(" - Best for I/O-bound work");
|
||||
|
||||
println!("\n Blocking operations (listen_blocking()):");
|
||||
println!(" - Block the entire THREAD");
|
||||
println!(" - Thread sits idle waiting");
|
||||
println!(" - No async runtime needed");
|
||||
println!(" - Use in non-async code or dedicated threads");
|
||||
|
||||
println!("\n IMPORTANT: Don't use blocking operations in async tasks!");
|
||||
println!(" They will block the executor thread and cause poor performance.\n");
|
||||
|
||||
// STEP 1: Basic blocking example
|
||||
// ===============================
|
||||
println!("Step 1: Basic blocking example with thread:");
|
||||
|
||||
let (announcer, announcement) = Announcement::<String>::new();
|
||||
|
||||
// Create a listener and move it to a separate thread
|
||||
let listener = announcement.listener();
|
||||
|
||||
println!(" Spawning thread that will block...");
|
||||
let handle = thread::spawn(move || {
|
||||
println!(" [Thread] Started, blocking until value arrives...");
|
||||
|
||||
// IMPORTANT: This line BLOCKS the thread!
|
||||
// ========================================
|
||||
// The thread will sit here, waiting, consuming no CPU.
|
||||
// When the value is announced, the thread wakes up.
|
||||
//
|
||||
// WARNING: If the announcer is dropped without announcing,
|
||||
// this will block FOREVER (deadlock).
|
||||
let value = listener.listen_blocking().unwrap();
|
||||
|
||||
println!(" [Thread] Received: {}", value);
|
||||
value
|
||||
});
|
||||
|
||||
println!(" Main thread: Sleeping for 100ms before announcing...");
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
|
||||
println!(" Main thread: Announcing value...");
|
||||
announcer
|
||||
.announce("Hello from blocking!".to_string())
|
||||
.unwrap();
|
||||
|
||||
println!(" Main thread: Waiting for thread to complete...");
|
||||
let result = handle.join().unwrap();
|
||||
println!(" Thread result: {}\n", result);
|
||||
|
||||
// STEP 2: Blocking with timeout (SUCCESS case)
|
||||
// =============================================
|
||||
println!("Step 2: Blocking with timeout (SUCCESS case):");
|
||||
|
||||
let (announcer, announcement) = Announcement::<i32>::new();
|
||||
let listener = announcement.listener();
|
||||
|
||||
println!(" Spawning thread with 1-second timeout...");
|
||||
let handle = thread::spawn(move || {
|
||||
println!(" [Thread] Waiting with 1-second timeout...");
|
||||
|
||||
// This will wait UP TO 1 second
|
||||
// Returns Result<T, ListenError>
|
||||
match listener.listen_timeout_blocking(Duration::from_secs(1)) {
|
||||
Ok(value) => {
|
||||
println!(" [Thread] Received: {}", value);
|
||||
Some(value)
|
||||
}
|
||||
Err(err) => {
|
||||
println!(" [Thread] Timeout! Error: {}", err);
|
||||
None
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Announce quickly (within timeout)
|
||||
println!(" Main thread: Sleeping 100ms...");
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
|
||||
println!(" Main thread: Announcing before timeout...");
|
||||
announcer.announce(42).unwrap();
|
||||
|
||||
let result = handle.join().unwrap();
|
||||
println!(" Thread received: {:?}", result);
|
||||
println!(" ✓ SUCCESS - value received within timeout\n");
|
||||
|
||||
// STEP 3: Blocking with timeout (TIMEOUT case)
|
||||
// =============================================
|
||||
println!("Step 3: Blocking with timeout (TIMEOUT case):");
|
||||
|
||||
let (_announcer, announcement) = Announcement::<i32>::new();
|
||||
let listener = announcement.listener();
|
||||
|
||||
// NOTE: We DON'T announce - so the thread will timeout
|
||||
println!(" Spawning thread with 100ms timeout...");
|
||||
println!(" (We won't announce, so it will timeout)");
|
||||
|
||||
let handle = thread::spawn(move || {
|
||||
println!(" [Thread] Waiting with 100ms timeout...");
|
||||
|
||||
match listener.listen_timeout_blocking(Duration::from_millis(100)) {
|
||||
Ok(value) => {
|
||||
println!(" [Thread] Received: {}", value);
|
||||
Some(value)
|
||||
}
|
||||
Err(err) => {
|
||||
println!(" [Thread] Timeout! Error: {}", err);
|
||||
None
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let result = handle.join().unwrap();
|
||||
println!(" Thread result: {:?}", result);
|
||||
println!(" ✓ TIMEOUT - no value within 100ms, error returned\n");
|
||||
|
||||
// STEP 4: Multiple blocking threads
|
||||
// ==================================
|
||||
println!("Step 4: Multiple threads all blocking:");
|
||||
|
||||
let (announcer, announcement) = Announcement::<String>::new();
|
||||
|
||||
let handles: Vec<_> = (0..5)
|
||||
.map(|i| {
|
||||
let listener = announcement.listener();
|
||||
thread::spawn(move || {
|
||||
println!(" [Thread {}] Blocking...", i);
|
||||
let value = listener.listen_blocking().unwrap();
|
||||
println!(" [Thread {}] Received: {}", i, value);
|
||||
value
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
println!(" Spawned 5 threads, all blocking");
|
||||
println!(" Main thread: Sleeping 200ms...");
|
||||
thread::sleep(Duration::from_millis(200));
|
||||
|
||||
println!(" Main thread: Announcing - all threads will wake!");
|
||||
announcer
|
||||
.announce("Broadcast to all threads!".to_string())
|
||||
.unwrap();
|
||||
|
||||
println!(" Waiting for all threads...");
|
||||
for (i, handle) in handles.into_iter().enumerate() {
|
||||
let result = handle.join().unwrap();
|
||||
println!(" Thread {} returned: {}", i, result);
|
||||
}
|
||||
println!(" ✓ All 5 threads received the value\n");
|
||||
|
||||
// STEP 5: DEADLOCK WARNING
|
||||
// ========================
|
||||
println!("Step 5: UNDERSTANDING DEADLOCK RISK:");
|
||||
println!("\n DANGEROUS CODE (commented out to avoid hanging):");
|
||||
println!(" ```");
|
||||
println!(" let (announcer, announcement) = Announcement::<i32>::new();");
|
||||
println!(" let listener = announcement.listener();");
|
||||
println!(" ");
|
||||
println!(" thread::spawn(move || {{");
|
||||
println!(" listener.listen_blocking(); // BLOCKS FOREVER!");
|
||||
println!(" }});");
|
||||
println!(" ");
|
||||
println!(" drop(announcer); // Dropped without announcing!");
|
||||
println!(" // Thread is now stuck forever!");
|
||||
println!(" ```");
|
||||
|
||||
println!("\n WHY THIS DEADLOCKS:");
|
||||
println!(" 1. Thread calls listen_blocking()");
|
||||
println!(" 2. Thread waits for announcement");
|
||||
println!(" 3. Announcer is dropped WITHOUT announcing");
|
||||
println!(" 4. No announcement ever comes");
|
||||
println!(" 5. Thread waits FOREVER");
|
||||
|
||||
println!("\n HOW TO AVOID DEADLOCK:");
|
||||
println!(" ✓ Always announce before dropping announcer");
|
||||
println!(" ✓ OR use listen_timeout_blocking() with timeout");
|
||||
println!(" ✓ OR use try_listen() in a loop with sleep");
|
||||
println!(" ✓ OR use async listen().await (can be cancelled)\n");
|
||||
|
||||
// STEP 6: Safe pattern with timeout
|
||||
// ==================================
|
||||
println!("Step 6: Safe pattern with timeout:");
|
||||
|
||||
let (announcer, announcement) = Announcement::<i32>::new();
|
||||
let listener = announcement.listener();
|
||||
|
||||
let handle = thread::spawn(move || {
|
||||
println!(" [Thread] Using timeout for safety...");
|
||||
|
||||
// SAFE: Even if announcer is dropped, we'll timeout
|
||||
match listener.listen_timeout_blocking(Duration::from_secs(2)) {
|
||||
Ok(value) => {
|
||||
println!(" [Thread] Received: {}", value);
|
||||
Ok(value)
|
||||
}
|
||||
Err(err) => {
|
||||
println!(" [Thread] Timed out: {}", err);
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Simulate: Announcer might be dropped here (e.g., error path)
|
||||
// But thread will timeout instead of hanging forever
|
||||
drop(announcer);
|
||||
println!(" Dropped announcer WITHOUT announcing");
|
||||
|
||||
let result = handle.join().unwrap();
|
||||
println!(" Thread result: {:?}", result);
|
||||
println!(" ✓ Thread didn't hang - timeout saved us!\n");
|
||||
|
||||
// STEP 7: Alternative safe pattern - polling
|
||||
// ===========================================
|
||||
println!("Step 7: Alternative safe pattern - polling with try_listen():");
|
||||
|
||||
let (announcer, announcement) = Announcement::<i32>::new();
|
||||
let listener = announcement.listener();
|
||||
|
||||
let handle = thread::spawn(move || {
|
||||
println!(" [Thread] Polling with try_listen()...");
|
||||
|
||||
// Poll every 50ms, timeout after 1 second
|
||||
let start = std::time::Instant::now();
|
||||
let timeout = Duration::from_secs(1);
|
||||
|
||||
loop {
|
||||
// Non-blocking check
|
||||
if let Some(value) = listener.try_listen() {
|
||||
println!(" [Thread] Got value: {}", value);
|
||||
return Ok(value);
|
||||
}
|
||||
|
||||
// Check timeout
|
||||
if start.elapsed() > timeout {
|
||||
println!(" [Thread] Polling timeout");
|
||||
return Err("timeout");
|
||||
}
|
||||
|
||||
// Sleep before next poll
|
||||
thread::sleep(Duration::from_millis(50));
|
||||
}
|
||||
});
|
||||
|
||||
thread::sleep(Duration::from_millis(200));
|
||||
announcer.announce(99).unwrap();
|
||||
|
||||
let result = handle.join().unwrap();
|
||||
println!(" Thread result: {:?}", result);
|
||||
println!(" ✓ Polling pattern is also safe from deadlock\n");
|
||||
|
||||
// STEP 8: When to use each method
|
||||
// ================================
|
||||
println!("Step 8: Decision guide - Which method to use:");
|
||||
|
||||
println!("\n Use try_listen() when:");
|
||||
println!(" ✓ You don't want to wait");
|
||||
println!(" ✓ Polling in a loop");
|
||||
println!(" ✓ Checking if value is ready");
|
||||
println!(" ✓ Non-blocking required");
|
||||
|
||||
println!("\n Use listen().await when:");
|
||||
println!(" ✓ In async code");
|
||||
println!(" ✓ Can wait efficiently");
|
||||
println!(" ✓ Need cancellation support");
|
||||
println!(" ✓ Using async runtime");
|
||||
|
||||
println!("\n Use listen_blocking() when:");
|
||||
println!(" ✓ In non-async code");
|
||||
println!(" ✓ Dedicated thread can block");
|
||||
println!(" ✓ You control the announcer lifetime");
|
||||
println!(" ✓ No async runtime available");
|
||||
|
||||
println!("\n Use listen_timeout_blocking() when:");
|
||||
println!(" ✓ listen_blocking() but need timeout");
|
||||
println!(" ✓ Can't guarantee announcement will happen");
|
||||
println!(" ✓ Want to prevent deadlock");
|
||||
println!(" ✓ Need to handle timeout case\n");
|
||||
|
||||
// STEP 9: Performance characteristics
|
||||
// ====================================
|
||||
println!("Step 9: Performance characteristics:");
|
||||
println!("\n listen_blocking():");
|
||||
println!(" - Fast path (already announced): ~5-10ns");
|
||||
println!(" - Slow path (waiting): Thread sleeps, 0% CPU");
|
||||
println!(" - Wakeup: ~100ns when announced");
|
||||
|
||||
println!("\n listen_timeout_blocking():");
|
||||
println!(" - Same as listen_blocking()");
|
||||
println!(" - Plus: Timeout check overhead (~50ns)");
|
||||
println!(" - Plus: Extra wakeup for timeout");
|
||||
|
||||
println!("\n try_listen() polling:");
|
||||
println!(" - Each poll: ~5-10ns");
|
||||
println!(" - CPU usage: Depends on poll frequency");
|
||||
println!(" - Example: Poll every 10ms = 0.0001% CPU\n");
|
||||
|
||||
// SUMMARY
|
||||
// =======
|
||||
println!("=== Summary ===");
|
||||
println!("✓ Used listen_blocking() to block a thread");
|
||||
println!("✓ Used listen_timeout_blocking() for safe timeouts");
|
||||
println!("✓ Demonstrated timeout success and failure");
|
||||
println!("✓ Understood deadlock risks and prevention");
|
||||
println!("✓ Learned safe patterns: timeout and polling");
|
||||
println!("\nKEY TAKEAWAYS:");
|
||||
println!(" 1. listen_blocking() blocks the THREAD (not just task)");
|
||||
println!(" 2. Always use timeout or guarantee announcement");
|
||||
println!(" 3. Dropping announcer without announcing = deadlock");
|
||||
println!(" 4. Prefer async listen().await in async code");
|
||||
println!(" 5. Use blocking only in dedicated threads");
|
||||
println!("\nCOMPLETED: Learning guide finished!");
|
||||
println!("See shutdown.rs, config_broadcast.rs, lazy_init.rs for real-world patterns");
|
||||
|
||||
// EXPECTED OUTPUT:
|
||||
// ================
|
||||
// You should see:
|
||||
// - Thread blocking and receiving value
|
||||
// - Timeout success (value within timeout)
|
||||
// - Timeout failure (no value, returns error)
|
||||
// - Multiple threads all receiving broadcast
|
||||
// - Deadlock explanation and prevention
|
||||
// - Safe patterns with timeout and polling
|
||||
// - Decision guide for choosing the right method
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
//! Configuration broadcast pattern using announcement
|
||||
//!
|
||||
//! This example demonstrates broadcasting a configuration object
|
||||
//! to multiple workers using Arc for efficiency.
|
||||
//!
|
||||
//! Run with: cargo run --example config_broadcast
|
||||
|
||||
use announcement::Announcement;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::time::sleep;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct AppConfig {
|
||||
database_url: String,
|
||||
api_key: String,
|
||||
max_connections: u32,
|
||||
timeout_ms: u64,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// Create announcement for configuration
|
||||
// We'll wrap the config in Arc to avoid cloning the data for each listener
|
||||
let (config_announcer, config_announcement) = Announcement::new();
|
||||
|
||||
// Spawn multiple workers that need the config
|
||||
// They can start before config is ready
|
||||
let worker1 = tokio::spawn({
|
||||
let config_listener = config_announcement.listener();
|
||||
async move {
|
||||
println!("Worker 1: Waiting for configuration...");
|
||||
|
||||
// Block until config is available
|
||||
let config: Arc<AppConfig> = config_listener.listen().await.unwrap();
|
||||
|
||||
println!("Worker 1: Received config");
|
||||
println!(" - Database: {}", config.database_url);
|
||||
println!(" - Max connections: {}", config.max_connections);
|
||||
|
||||
// Simulate work with the config
|
||||
sleep(Duration::from_millis(config.timeout_ms)).await;
|
||||
println!("Worker 1: Completed work");
|
||||
}
|
||||
});
|
||||
|
||||
let worker2 = tokio::spawn({
|
||||
let config_listener = config_announcement.listener();
|
||||
async move {
|
||||
println!("Worker 2: Waiting for configuration...");
|
||||
|
||||
let config: Arc<AppConfig> = config_listener.listen().await.unwrap();
|
||||
|
||||
println!("Worker 2: Received config");
|
||||
println!(" - API Key: {}", &config.api_key[..8]);
|
||||
println!(" - Timeout: {}ms", config.timeout_ms);
|
||||
|
||||
sleep(Duration::from_millis(config.timeout_ms)).await;
|
||||
println!("Worker 2: Completed work");
|
||||
}
|
||||
});
|
||||
|
||||
let worker3 = tokio::spawn({
|
||||
let config_listener = config_announcement.listener();
|
||||
async move {
|
||||
println!("Worker 3: Waiting for configuration...");
|
||||
|
||||
let config: Arc<AppConfig> = config_listener.listen().await.unwrap();
|
||||
|
||||
println!("Worker 3: Received config");
|
||||
println!(" - All settings ready");
|
||||
|
||||
sleep(Duration::from_millis(config.timeout_ms)).await;
|
||||
println!("Worker 3: Completed work");
|
||||
}
|
||||
});
|
||||
|
||||
// Simulate loading configuration (takes time)
|
||||
println!("Main: Loading configuration...\n");
|
||||
sleep(Duration::from_millis(500)).await;
|
||||
|
||||
// Create the configuration
|
||||
let config = Arc::new(AppConfig {
|
||||
database_url: "postgresql://localhost/myapp".to_string(),
|
||||
api_key: "sk_live_abc123xyz789".to_string(),
|
||||
max_connections: 100,
|
||||
timeout_ms: 200,
|
||||
});
|
||||
|
||||
println!("Main: Configuration loaded");
|
||||
println!("Main: Broadcasting to all workers...\n");
|
||||
|
||||
// Broadcast config to all workers
|
||||
// Using Arc means the data is only stored once
|
||||
// Each listener gets a clone of the Arc (just incrementing ref count)
|
||||
config_announcer.announce(config.clone()).unwrap();
|
||||
|
||||
// Verify original Arc still valid
|
||||
println!("Main: Original config still accessible");
|
||||
println!(" - Strong count: {}\n", Arc::strong_count(&config));
|
||||
|
||||
// Wait for all workers to complete
|
||||
let _ = tokio::join!(worker1, worker2, worker3);
|
||||
|
||||
println!("\nMain: All workers completed");
|
||||
println!(
|
||||
"Main: Final Arc strong count: {}",
|
||||
Arc::strong_count(&config)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
//! Lazy initialization pattern using announcement
|
||||
//!
|
||||
//! This example demonstrates using announcement to signal when
|
||||
//! a resource is ready, allowing dependent tasks to wait.
|
||||
//!
|
||||
//! Run with: cargo run --example lazy_init
|
||||
|
||||
use announcement::Announcement;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::time::sleep;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DatabaseConnection {
|
||||
connection_string: String,
|
||||
pool_size: usize,
|
||||
}
|
||||
|
||||
impl DatabaseConnection {
|
||||
async fn connect(connection_string: String) -> Self {
|
||||
println!("Database: Connecting to {}...", connection_string);
|
||||
|
||||
// Simulate connection establishment
|
||||
sleep(Duration::from_millis(800)).await;
|
||||
|
||||
println!("Database: Connection established");
|
||||
Self {
|
||||
connection_string,
|
||||
pool_size: 10,
|
||||
}
|
||||
}
|
||||
|
||||
fn execute_query(&self, query: &str) -> String {
|
||||
format!(
|
||||
"Query '{}' executed on {} [pool: {}]",
|
||||
query, self.connection_string, self.pool_size
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// Create announcement for database connection
|
||||
let (db_announcer, db_announcement) = Announcement::new();
|
||||
|
||||
// Multiple services that need the database
|
||||
let api_service = tokio::spawn({
|
||||
let db_listener = db_announcement.listener();
|
||||
async move {
|
||||
println!("API Service: Starting up...");
|
||||
println!("API Service: Waiting for database connection...");
|
||||
|
||||
// Wait for database to be ready
|
||||
let db: Arc<DatabaseConnection> = db_listener.listen().await.unwrap();
|
||||
|
||||
println!("API Service: Database ready, processing requests");
|
||||
let result = db.execute_query("SELECT * FROM users");
|
||||
println!("API Service: {}", result);
|
||||
}
|
||||
});
|
||||
|
||||
let background_worker = tokio::spawn({
|
||||
let db_listener = db_announcement.listener();
|
||||
async move {
|
||||
println!("Background Worker: Starting up...");
|
||||
println!("Background Worker: Waiting for database connection...");
|
||||
|
||||
let db: Arc<DatabaseConnection> = db_listener.listen().await.unwrap();
|
||||
|
||||
println!("Background Worker: Database ready, starting jobs");
|
||||
let result = db.execute_query("UPDATE jobs SET status = 'running'");
|
||||
println!("Background Worker: {}", result);
|
||||
}
|
||||
});
|
||||
|
||||
let metrics_collector = tokio::spawn({
|
||||
let db_listener = db_announcement.listener();
|
||||
async move {
|
||||
println!("Metrics Collector: Starting up...");
|
||||
|
||||
// Try polling first (non-blocking check)
|
||||
if db_listener.try_listen().is_none() {
|
||||
println!("Metrics Collector: Database not ready yet, waiting...");
|
||||
let db: Arc<DatabaseConnection> = db_listener.listen().await.unwrap();
|
||||
println!("Metrics Collector: Database ready");
|
||||
let result = db.execute_query("INSERT INTO metrics VALUES (...)");
|
||||
println!("Metrics Collector: {}", result);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Simulate initialization work before database connection
|
||||
println!("Main: Initializing application...");
|
||||
sleep(Duration::from_millis(200)).await;
|
||||
|
||||
// Now establish the database connection
|
||||
println!("\nMain: Starting database initialization...\n");
|
||||
let db = DatabaseConnection::connect("postgresql://localhost:5432/myapp".to_string()).await;
|
||||
|
||||
// Wrap in Arc for efficient sharing
|
||||
let db = Arc::new(db);
|
||||
|
||||
// Announce that database is ready
|
||||
// All waiting services will be notified simultaneously
|
||||
println!("\nMain: Broadcasting database connection...\n");
|
||||
db_announcer.announce(db.clone()).unwrap();
|
||||
|
||||
// Wait for all services to complete
|
||||
let _: (Result<(), _>, Result<(), _>, Result<(), _>) =
|
||||
tokio::join!(api_service, background_worker, metrics_collector);
|
||||
|
||||
println!("\nMain: All services initialized successfully");
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
//! Graceful shutdown pattern using announcement
|
||||
//!
|
||||
//! This example demonstrates how to use announcement to coordinate
|
||||
//! graceful shutdown of multiple worker tasks.
|
||||
//!
|
||||
//! Run with: cargo run --example shutdown
|
||||
|
||||
use announcement::Announcement;
|
||||
use std::time::Duration;
|
||||
use tokio::time::sleep;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// Create shutdown signal channel
|
||||
// The announcer will be used to broadcast the shutdown signal once
|
||||
// The announcement can create multiple listeners for different tasks
|
||||
let (shutdown_announcer, shutdown_announcement) = Announcement::new();
|
||||
|
||||
// Spawn three worker tasks, each with their own shutdown listener
|
||||
let worker1 = tokio::spawn({
|
||||
let shutdown = shutdown_announcement.listener();
|
||||
async move {
|
||||
println!("Worker 1: Starting");
|
||||
loop {
|
||||
// Check for shutdown signal without blocking
|
||||
if shutdown.try_listen().is_some() {
|
||||
println!("Worker 1: Received shutdown signal, cleaning up...");
|
||||
sleep(Duration::from_millis(100)).await;
|
||||
println!("Worker 1: Shutdown complete");
|
||||
break;
|
||||
}
|
||||
|
||||
// Simulate work
|
||||
println!("Worker 1: Working...");
|
||||
sleep(Duration::from_millis(200)).await;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let worker2 = tokio::spawn({
|
||||
let shutdown = shutdown_announcement.listener();
|
||||
async move {
|
||||
println!("Worker 2: Starting");
|
||||
loop {
|
||||
// Use async listen() to wait for shutdown
|
||||
tokio::select! {
|
||||
result = shutdown.listen() => {
|
||||
let _ = result; // Ignore whether it was announced or closed
|
||||
println!("Worker 2: Received shutdown signal, cleaning up...");
|
||||
sleep(Duration::from_millis(150)).await;
|
||||
println!("Worker 2: Shutdown complete");
|
||||
break;
|
||||
}
|
||||
_ = sleep(Duration::from_millis(300)) => {
|
||||
println!("Worker 2: Working...");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let worker3 = tokio::spawn({
|
||||
let shutdown = shutdown_announcement.listener();
|
||||
async move {
|
||||
println!("Worker 3: Starting");
|
||||
// This worker just waits for shutdown
|
||||
let _ = shutdown.listen().await; // Ignore whether it was announced or closed
|
||||
println!("Worker 3: Received shutdown signal, cleaning up...");
|
||||
sleep(Duration::from_millis(80)).await;
|
||||
println!("Worker 3: Shutdown complete");
|
||||
}
|
||||
});
|
||||
|
||||
// Let workers run for a bit
|
||||
println!("Main: Letting workers run...\n");
|
||||
sleep(Duration::from_secs(1)).await;
|
||||
|
||||
// Send shutdown signal to all workers
|
||||
// This is a oneshot operation - can only happen once
|
||||
// All three listeners will receive the signal simultaneously
|
||||
println!("\nMain: Broadcasting shutdown signal...");
|
||||
shutdown_announcer.announce(()).unwrap();
|
||||
|
||||
// Wait for all workers to complete graceful shutdown
|
||||
let _ = tokio::join!(worker1, worker2, worker3);
|
||||
println!("\nMain: All workers shut down gracefully");
|
||||
}
|
||||
Reference in new issue
Block a user