chore: initial release v0.1.0

Runtime-agnostic oneshot broadcast channel for Rust.
This commit is contained in:
swaits committed 2025-10-13 19:36:37 -06:00
commit e8e55b7676
64 files changed
+23966

No files matched your search

+168
View File
@@ -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
}
+209
View File
@@ -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
}
+245
View File
@@ -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
}
+308
View File
@@ -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
}
+371
View File
@@ -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
}
+110
View File
@@ -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)
);
}
+113
View File
@@ -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");
}
+87
View File
@@ -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");
}