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,564 @@
|
||||
use announcement::Announcement;
|
||||
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
|
||||
use std::hint::black_box;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
// ============================================================================
|
||||
// SHUTDOWN SIGNAL PATTERN - Notify all workers to shut down
|
||||
// ============================================================================
|
||||
|
||||
fn bench_shutdown_signal(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("pattern/shutdown_signal");
|
||||
|
||||
// Simulate shutdown signal to various numbers of workers
|
||||
for num_workers in [10, 100, 1_000, 10_000].iter() {
|
||||
group.throughput(Throughput::Elements(*num_workers as u64));
|
||||
group.bench_with_input(
|
||||
BenchmarkId::from_parameter(num_workers),
|
||||
num_workers,
|
||||
|b, &n| {
|
||||
b.iter(|| {
|
||||
let (shutdown_tx, shutdown_announcement) = Announcement::new();
|
||||
|
||||
// Create N worker listeners
|
||||
let mut workers = Vec::with_capacity(n);
|
||||
for _ in 0..n {
|
||||
workers.push(shutdown_announcement.listener());
|
||||
}
|
||||
|
||||
// Send shutdown signal
|
||||
shutdown_tx.announce(Arc::new(())).unwrap();
|
||||
|
||||
// All workers check for shutdown
|
||||
for worker in &workers {
|
||||
black_box(worker.try_listen());
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_shutdown_with_tokio_tasks(c: &mut Criterion) {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let mut group = c.benchmark_group("pattern/shutdown_async");
|
||||
group.sample_size(10);
|
||||
group.measurement_time(Duration::from_secs(10));
|
||||
|
||||
for num_workers in [10, 100].iter() {
|
||||
group.throughput(Throughput::Elements(*num_workers as u64));
|
||||
group.bench_with_input(
|
||||
BenchmarkId::from_parameter(num_workers),
|
||||
num_workers,
|
||||
|b, &n| {
|
||||
b.to_async(&rt).iter(|| async move {
|
||||
let (shutdown_tx, shutdown_announcement) = Announcement::new();
|
||||
|
||||
// Spawn N worker tasks
|
||||
let mut handles = Vec::with_capacity(n);
|
||||
for i in 0..n {
|
||||
let listener = shutdown_announcement.listener();
|
||||
handles.push(tokio::spawn(async move {
|
||||
// Wait for shutdown
|
||||
listener.listen().await.unwrap();
|
||||
i
|
||||
}));
|
||||
}
|
||||
|
||||
// Small delay to let tasks start
|
||||
tokio::time::sleep(Duration::from_millis(1)).await;
|
||||
|
||||
// Send shutdown signal
|
||||
shutdown_tx.announce(Arc::new(())).unwrap();
|
||||
|
||||
// Wait for all workers to complete
|
||||
for handle in handles {
|
||||
black_box(handle.await.unwrap());
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// CONFIG BROADCAST PATTERN - Share configuration to all services
|
||||
// ============================================================================
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[allow(dead_code)]
|
||||
struct Config {
|
||||
database_url: String,
|
||||
api_key: String,
|
||||
max_connections: usize,
|
||||
timeout_ms: u64,
|
||||
}
|
||||
|
||||
fn bench_config_broadcast(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("pattern/config_broadcast");
|
||||
|
||||
for num_services in [10, 100].iter() {
|
||||
group.throughput(Throughput::Elements(*num_services as u64));
|
||||
group.bench_with_input(
|
||||
BenchmarkId::from_parameter(num_services),
|
||||
num_services,
|
||||
|b, &n| {
|
||||
b.iter(|| {
|
||||
let (config_tx, config_announcement) = Announcement::new();
|
||||
|
||||
// Create N service listeners
|
||||
let mut services = Vec::with_capacity(n);
|
||||
for _ in 0..n {
|
||||
services.push(config_announcement.listener());
|
||||
}
|
||||
|
||||
// Broadcast configuration
|
||||
let config = Arc::new(Config {
|
||||
database_url: "postgres://localhost/db".to_string(),
|
||||
api_key: "secret-key-12345".to_string(),
|
||||
max_connections: 100,
|
||||
timeout_ms: 5000,
|
||||
});
|
||||
|
||||
config_tx.announce(config).unwrap();
|
||||
|
||||
// All services receive config
|
||||
for service in &services {
|
||||
black_box(service.try_listen());
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_config_broadcast_async(c: &mut Criterion) {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let mut group = c.benchmark_group("pattern/config_broadcast_async");
|
||||
group.sample_size(10);
|
||||
group.measurement_time(Duration::from_secs(10));
|
||||
|
||||
for num_services in [10, 100].iter() {
|
||||
group.throughput(Throughput::Elements(*num_services as u64));
|
||||
group.bench_with_input(
|
||||
BenchmarkId::from_parameter(num_services),
|
||||
num_services,
|
||||
|b, &n| {
|
||||
b.to_async(&rt).iter(|| async move {
|
||||
let (config_tx, config_announcement) = Announcement::new();
|
||||
|
||||
// Spawn N service tasks waiting for config
|
||||
let mut handles = Vec::with_capacity(n);
|
||||
for i in 0..n {
|
||||
let listener = config_announcement.listener();
|
||||
handles.push(tokio::spawn(async move {
|
||||
// Wait for config
|
||||
let config: Arc<Config> = listener.listen().await.unwrap();
|
||||
// Use config
|
||||
(i, config.max_connections)
|
||||
}));
|
||||
}
|
||||
|
||||
// Small delay to let tasks start
|
||||
tokio::time::sleep(Duration::from_millis(1)).await;
|
||||
|
||||
// Broadcast configuration
|
||||
let config = Arc::new(Config {
|
||||
database_url: "postgres://localhost/db".to_string(),
|
||||
api_key: "secret-key-12345".to_string(),
|
||||
max_connections: 100,
|
||||
timeout_ms: 5000,
|
||||
});
|
||||
|
||||
config_tx.announce(config).unwrap();
|
||||
|
||||
// Wait for all services to process config
|
||||
for handle in handles {
|
||||
black_box(handle.await.unwrap());
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// EVENT FANOUT PATTERN - Broadcast events to many subscribers
|
||||
// ============================================================================
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[allow(dead_code)]
|
||||
struct Event {
|
||||
event_type: String,
|
||||
timestamp: u64,
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
fn bench_event_fanout(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("pattern/event_fanout");
|
||||
|
||||
for num_subscribers in [10, 100, 1_000].iter() {
|
||||
group.throughput(Throughput::Elements(*num_subscribers as u64));
|
||||
group.bench_with_input(
|
||||
BenchmarkId::from_parameter(num_subscribers),
|
||||
num_subscribers,
|
||||
|b, &n| {
|
||||
b.iter(|| {
|
||||
let (event_tx, event_announcement) = Announcement::new();
|
||||
|
||||
// Create N subscriber listeners
|
||||
let mut subscribers = Vec::with_capacity(n);
|
||||
for _ in 0..n {
|
||||
subscribers.push(event_announcement.listener());
|
||||
}
|
||||
|
||||
// Broadcast event
|
||||
let event = Arc::new(Event {
|
||||
event_type: "user.created".to_string(),
|
||||
timestamp: 1234567890,
|
||||
data: vec![1, 2, 3, 4, 5],
|
||||
});
|
||||
|
||||
event_tx.announce(event).unwrap();
|
||||
|
||||
// All subscribers receive event
|
||||
for subscriber in &subscribers {
|
||||
black_box(subscriber.try_listen());
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_event_fanout_async(c: &mut Criterion) {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let mut group = c.benchmark_group("pattern/event_fanout_async");
|
||||
group.sample_size(10);
|
||||
group.measurement_time(Duration::from_secs(10));
|
||||
|
||||
for num_subscribers in [10, 100].iter() {
|
||||
group.throughput(Throughput::Elements(*num_subscribers as u64));
|
||||
group.bench_with_input(
|
||||
BenchmarkId::from_parameter(num_subscribers),
|
||||
num_subscribers,
|
||||
|b, &n| {
|
||||
b.to_async(&rt).iter(|| async move {
|
||||
let (event_tx, event_announcement) = Announcement::new();
|
||||
|
||||
// Spawn N subscriber tasks
|
||||
let mut handles = Vec::with_capacity(n);
|
||||
for i in 0..n {
|
||||
let listener = event_announcement.listener();
|
||||
handles.push(tokio::spawn(async move {
|
||||
let event: Arc<Event> = listener.listen().await.unwrap();
|
||||
(i, event.timestamp)
|
||||
}));
|
||||
}
|
||||
|
||||
// Small delay
|
||||
tokio::time::sleep(Duration::from_millis(1)).await;
|
||||
|
||||
// Broadcast event
|
||||
let event = Arc::new(Event {
|
||||
event_type: "user.created".to_string(),
|
||||
timestamp: 1234567890,
|
||||
data: vec![1, 2, 3, 4, 5],
|
||||
});
|
||||
|
||||
event_tx.announce(event).unwrap();
|
||||
|
||||
// Wait for all subscribers
|
||||
for handle in handles {
|
||||
black_box(handle.await.unwrap());
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// REQUEST-RESPONSE MULTIPLEXING - Multiple waiters for single response
|
||||
// ============================================================================
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[allow(dead_code)]
|
||||
struct Response {
|
||||
status: u16,
|
||||
body: String,
|
||||
}
|
||||
|
||||
fn bench_request_response_multiplex(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("pattern/request_response_multiplex");
|
||||
|
||||
// Multiple tasks waiting for same HTTP response
|
||||
for num_waiters in [2, 5, 10, 50, 100].iter() {
|
||||
group.throughput(Throughput::Elements(*num_waiters as u64));
|
||||
group.bench_with_input(
|
||||
BenchmarkId::from_parameter(num_waiters),
|
||||
num_waiters,
|
||||
|b, &n| {
|
||||
b.iter(|| {
|
||||
let (response_tx, response_announcement) = Announcement::new();
|
||||
|
||||
// Create N waiters for the same response
|
||||
let mut waiters = Vec::with_capacity(n);
|
||||
for _ in 0..n {
|
||||
waiters.push(response_announcement.listener());
|
||||
}
|
||||
|
||||
// Simulate response arrives
|
||||
let response = Arc::new(Response {
|
||||
status: 200,
|
||||
body: "OK".to_string(),
|
||||
});
|
||||
|
||||
response_tx.announce(response).unwrap();
|
||||
|
||||
// All waiters get response
|
||||
for waiter in &waiters {
|
||||
black_box(waiter.try_listen());
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_request_response_multiplex_async(c: &mut Criterion) {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let mut group = c.benchmark_group("pattern/request_response_multiplex_async");
|
||||
group.sample_size(10);
|
||||
|
||||
for num_waiters in [2, 5, 10, 50, 100].iter() {
|
||||
group.throughput(Throughput::Elements(*num_waiters as u64));
|
||||
group.bench_with_input(
|
||||
BenchmarkId::from_parameter(num_waiters),
|
||||
num_waiters,
|
||||
|b, &n| {
|
||||
b.to_async(&rt).iter(|| async move {
|
||||
let (response_tx, response_announcement) = Announcement::new();
|
||||
|
||||
// Spawn N tasks waiting for response
|
||||
let mut handles = Vec::with_capacity(n);
|
||||
for i in 0..n {
|
||||
let listener = response_announcement.listener();
|
||||
handles.push(tokio::spawn(async move {
|
||||
let response: Arc<Response> = listener.listen().await.unwrap();
|
||||
(i, response.status)
|
||||
}));
|
||||
}
|
||||
|
||||
// Response arrives
|
||||
let response = Arc::new(Response {
|
||||
status: 200,
|
||||
body: "OK".to_string(),
|
||||
});
|
||||
|
||||
response_tx.announce(response).unwrap();
|
||||
|
||||
// Wait for all waiters
|
||||
for handle in handles {
|
||||
black_box(handle.await.unwrap());
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// LAZY INITIALIZATION PATTERN - Signal when resource is ready
|
||||
// ============================================================================
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[allow(dead_code)]
|
||||
struct Database {
|
||||
connection_pool: Arc<Vec<String>>,
|
||||
max_connections: usize,
|
||||
}
|
||||
|
||||
fn bench_lazy_initialization(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("pattern/lazy_initialization");
|
||||
|
||||
for num_clients in [10, 100].iter() {
|
||||
group.throughput(Throughput::Elements(*num_clients as u64));
|
||||
group.bench_with_input(
|
||||
BenchmarkId::from_parameter(num_clients),
|
||||
num_clients,
|
||||
|b, &n| {
|
||||
b.iter(|| {
|
||||
let (db_ready_tx, db_ready_announcement) = Announcement::new();
|
||||
|
||||
// N clients waiting for database to be ready
|
||||
let mut clients = Vec::with_capacity(n);
|
||||
for _ in 0..n {
|
||||
clients.push(db_ready_announcement.listener());
|
||||
}
|
||||
|
||||
// Simulate database initialization
|
||||
let db = Arc::new(Database {
|
||||
connection_pool: Arc::new(vec![
|
||||
"conn1".to_string(),
|
||||
"conn2".to_string(),
|
||||
"conn3".to_string(),
|
||||
]),
|
||||
max_connections: 10,
|
||||
});
|
||||
|
||||
// Signal ready
|
||||
db_ready_tx.announce(db).unwrap();
|
||||
|
||||
// All clients can now proceed
|
||||
for client in &clients {
|
||||
black_box(client.try_listen());
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_lazy_initialization_async(c: &mut Criterion) {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
let mut group = c.benchmark_group("pattern/lazy_initialization_async");
|
||||
group.sample_size(10);
|
||||
|
||||
for num_clients in [10, 100].iter() {
|
||||
group.throughput(Throughput::Elements(*num_clients as u64));
|
||||
group.bench_with_input(
|
||||
BenchmarkId::from_parameter(num_clients),
|
||||
num_clients,
|
||||
|b, &n| {
|
||||
b.to_async(&rt).iter(|| async move {
|
||||
let (db_ready_tx, db_ready_announcement) = Announcement::new();
|
||||
|
||||
// Spawn N client tasks
|
||||
let mut handles = Vec::with_capacity(n);
|
||||
for i in 0..n {
|
||||
let listener = db_ready_announcement.listener();
|
||||
handles.push(tokio::spawn(async move {
|
||||
// Wait for database
|
||||
let db: Arc<Database> = listener.listen().await.unwrap();
|
||||
(i, db.max_connections)
|
||||
}));
|
||||
}
|
||||
|
||||
let db = Arc::new(Database {
|
||||
connection_pool: Arc::new(vec![
|
||||
"conn1".to_string(),
|
||||
"conn2".to_string(),
|
||||
"conn3".to_string(),
|
||||
]),
|
||||
max_connections: 10,
|
||||
});
|
||||
|
||||
db_ready_tx.announce(db).unwrap();
|
||||
|
||||
// Wait for all clients
|
||||
for handle in handles {
|
||||
black_box(handle.await.unwrap());
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// BARRIER PATTERN - Multiple announcements that all must complete
|
||||
// ============================================================================
|
||||
|
||||
fn bench_barrier_pattern(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("pattern/barrier");
|
||||
|
||||
// N tasks each announce completion, all wait for all to complete
|
||||
for num_tasks in [5, 10, 25].iter() {
|
||||
group.throughput(Throughput::Elements(*num_tasks as u64));
|
||||
group.bench_with_input(
|
||||
BenchmarkId::from_parameter(num_tasks),
|
||||
num_tasks,
|
||||
|b, &n| {
|
||||
b.iter(|| {
|
||||
let mut announcements = Vec::with_capacity(n);
|
||||
let mut announcers = Vec::with_capacity(n);
|
||||
|
||||
// Create N announcement channels (one per task)
|
||||
for _ in 0..n {
|
||||
let (tx, announcement) = Announcement::<Arc<i32>>::new();
|
||||
announcers.push(tx);
|
||||
announcements.push(announcement);
|
||||
}
|
||||
|
||||
// Each task listens to all other tasks
|
||||
let mut all_listeners = Vec::with_capacity(n * n);
|
||||
for announcement in &announcements {
|
||||
for _ in 0..n {
|
||||
all_listeners.push(announcement.listener());
|
||||
}
|
||||
}
|
||||
|
||||
// All tasks complete
|
||||
for (i, announcer) in announcers.into_iter().enumerate() {
|
||||
announcer.announce(Arc::new(i as i32)).unwrap();
|
||||
}
|
||||
|
||||
// Verify all received
|
||||
for listener in &all_listeners {
|
||||
black_box(listener.try_listen());
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
shutdown,
|
||||
bench_shutdown_signal,
|
||||
bench_shutdown_with_tokio_tasks,
|
||||
);
|
||||
|
||||
criterion_group!(config, bench_config_broadcast, bench_config_broadcast_async,);
|
||||
|
||||
criterion_group!(events, bench_event_fanout, bench_event_fanout_async,);
|
||||
|
||||
criterion_group!(
|
||||
multiplex,
|
||||
bench_request_response_multiplex,
|
||||
bench_request_response_multiplex_async,
|
||||
);
|
||||
|
||||
criterion_group!(
|
||||
lazy_init,
|
||||
bench_lazy_initialization,
|
||||
bench_lazy_initialization_async,
|
||||
);
|
||||
|
||||
criterion_group!(barrier, bench_barrier_pattern,);
|
||||
|
||||
criterion_main!(shutdown, config, events, multiplex, lazy_init, barrier);
|
||||
Reference in new issue
Block a user