Skip to content

Commit 99b5032

Browse files
authored
Merge branch 'main' into feat/tcp-connecotr-hardening
2 parents 2608ed4 + b32cfce commit 99b5032

7 files changed

Lines changed: 168 additions & 4 deletions

File tree

Cargo.lock

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,10 @@ members = [
3838
"examples/hello-mailbox",
3939
"examples/hello-mailbox-async",
4040
"examples/hello-single-latest-async",
41+
"examples/hello-spmc-ring-async",
4142
"examples/readme-quickstart",
4243
# Benchmarking infrastructure — host-only, excluded from default-members
43-
"aimdb-bench",
44+
"aimdb-bench",
4445
]
4546
exclude = ["_external"]
4647
resolver = "2"

Makefile

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ test:
184184

185185
fmt:
186186
@printf "$(GREEN)Formatting code (workspace members only)...$(NC)\n"
187-
@for pkg in aimdb-derive aimdb-data-contracts aimdb-core aimdb-client aimdb-embassy-adapter aimdb-tokio-adapter aimdb-wasm-adapter aimdb-sync aimdb-persistence aimdb-persistence-sqlite aimdb-mqtt-connector aimdb-knx-connector aimdb-ws-protocol aimdb-websocket-connector aimdb-uds-connector aimdb-serial-connector aimdb-tcp-connector aimdb-codegen aimdb-cli aimdb-mcp sync-api-demo tokio-mqtt-connector-demo embassy-mqtt-connector-demo tokio-knx-connector-demo embassy-knx-connector-demo embassy-serial-connector-demo embassy-bench-stm32h5 weather-mesh-common weather-hub weather-station-alpha weather-station-beta hello-mailbox hello-mailbox-async hello-single-latest-async aimdb-bench; do \
187+
@for pkg in aimdb-derive aimdb-data-contracts aimdb-core aimdb-client aimdb-embassy-adapter aimdb-tokio-adapter aimdb-wasm-adapter aimdb-sync aimdb-persistence aimdb-persistence-sqlite aimdb-mqtt-connector aimdb-knx-connector aimdb-ws-protocol aimdb-websocket-connector aimdb-uds-connector aimdb-serial-connector aimdb-tcp-connector aimdb-codegen aimdb-cli aimdb-mcp sync-api-demo tokio-mqtt-connector-demo embassy-mqtt-connector-demo tokio-knx-connector-demo embassy-knx-connector-demo embassy-serial-connector-demo embassy-bench-stm32h5 weather-mesh-common weather-hub weather-station-alpha weather-station-beta hello-mailbox hello-mailbox-async hello-single-latest-async hello-spmc-ring-async aimdb-bench; do \
188188
printf "$(YELLOW) → Formatting $$pkg$(NC)\n"; \
189189
cargo fmt -p $$pkg 2>/dev/null || true; \
190190
done
@@ -193,7 +193,7 @@ fmt:
193193
fmt-check:
194194
@printf "$(GREEN)Checking code formatting (workspace members only)...$(NC)\n"
195195
@FAILED=0; \
196-
for pkg in aimdb-derive aimdb-data-contracts aimdb-core aimdb-client aimdb-embassy-adapter aimdb-tokio-adapter aimdb-wasm-adapter aimdb-sync aimdb-persistence aimdb-persistence-sqlite aimdb-mqtt-connector aimdb-knx-connector aimdb-ws-protocol aimdb-websocket-connector aimdb-uds-connector aimdb-serial-connector aimdb-tcp-connector aimdb-codegen aimdb-cli aimdb-mcp sync-api-demo tokio-mqtt-connector-demo embassy-mqtt-connector-demo tokio-knx-connector-demo embassy-knx-connector-demo embassy-serial-connector-demo embassy-bench-stm32h5 weather-mesh-common weather-hub weather-station-alpha weather-station-beta hello-mailbox hello-mailbox-async hello-single-latest-async aimdb-bench; do \
196+
for pkg in aimdb-derive aimdb-data-contracts aimdb-core aimdb-client aimdb-embassy-adapter aimdb-tokio-adapter aimdb-wasm-adapter aimdb-sync aimdb-persistence aimdb-persistence-sqlite aimdb-mqtt-connector aimdb-knx-connector aimdb-ws-protocol aimdb-websocket-connector aimdb-uds-connector aimdb-serial-connector aimdb-tcp-connector aimdb-codegen aimdb-cli aimdb-mcp sync-api-demo tokio-mqtt-connector-demo embassy-mqtt-connector-demo tokio-knx-connector-demo embassy-knx-connector-demo embassy-serial-connector-demo embassy-bench-stm32h5 weather-mesh-common weather-hub weather-station-alpha weather-station-beta hello-mailbox hello-mailbox-async hello-single-latest-async hello-spmc-ring-async aimdb-bench; do \
197197
printf "$(YELLOW) → Checking $$pkg$(NC)\n"; \
198198
if ! cargo fmt -p $$pkg -- --check 2>&1; then \
199199
printf "$(RED)❌ Formatting check failed for $$pkg$(NC)\n"; \
@@ -422,6 +422,8 @@ examples:
422422
cargo build --package hello-mailbox-async
423423
@printf "$(YELLOW) → Building hello-single-latest-async$(NC)\n"
424424
cargo build --package hello-single-latest-async
425+
@printf "$(YELLOW) → Building hello-spmc-ring-async$(NC)\n"
426+
cargo build --package hello-spmc-ring-async
425427
@printf "$(YELLOW) → Building readme-quickstart (compiled README example)$(NC)\n"
426428
cargo build --package readme-quickstart
427429
@printf "$(GREEN)All examples built successfully!$(NC)\n"

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ docker compose up
131131

132132
| Buffer | Semantics | Use cases |
133133
| --- | --- | --- |
134-
| **SPMC Ring** | Bounded stream with independent consumers | Sensor telemetry, event logs |
134+
| [**SPMC Ring**](examples/hello-spmc-ring-async) | Bounded stream with independent consumers | Sensor telemetry, event logs |
135135
| [**SingleLatest**](examples/hello-single-latest-async) | Only the current value matters | Feature flags, config, UI state |
136136
| [**Mailbox**](examples/hello-mailbox) / [**async Mailbox**](examples/hello-mailbox-async)| Latest instruction wins | Device commands, actuation, RPC |
137137

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[package]
2+
name = "hello-spmc-ring-async"
3+
version.workspace = true
4+
edition.workspace = true
5+
license.workspace = true
6+
description = "AimDB minimal example demonstrating async SPMC buffer"
7+
publish = false
8+
9+
[dependencies]
10+
aimdb-core = { path = "../../aimdb-core", features = ["std"] }
11+
aimdb-tokio-adapter = { path = "../../aimdb-tokio-adapter", features = ["tokio-runtime"] }
12+
tokio = { workspace = true, features = ["time"] }
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# hello-spmc-ring-async: async Spmc Ring buffer demo
2+
The Spmc Ring buffer supports Single Producer - Multi Consumer pattern. It features a ring buffer with fixed capacity. Each consumer could read the full sequence at its own rate without waiting for consumption states of other consumers.
3+
4+
## When to use
5+
This Spmc Ring buffer demo is useful in a variety of scenarios where you need to broadcast messages to different types of consumers but you are not sure if one could lag behind others.
6+
7+
## How it works
8+
The Spmc buffer demo works by simulating:
9+
- A producer generating a message at fixed interval (struct `Temperature`);
10+
- A consumer/observer 01 consuming at a rate faster than producer's;
11+
- A consumer/observer 02 consuming at a rate slower than producer's, and eventually lagging behind (messages dropped), as the ring size is small (10 slots);
12+
13+
## How to run
14+
From the workspace root, run:
15+
```
16+
cargo run -p hello-spmc-ring-async
17+
```
18+
**Expected output**
19+
```
20+
=== hello-spmc-ring-async: SPMC ring buffer demo ===
21+
22+
Ring size: 10
23+
Producer at rate 20.0 messages/sec
24+
Observer 01 at rate 25.0 message/sec
25+
Observer 02 at rate 6.7 message/sec
26+
27+
28+
Observer 01: At 1783932208: -18 celcius
29+
Observer 02: At 1783932208: -18 celcius
30+
Observer 01: At 1783932208: -16 celcius
31+
Observer 01: At 1783932208: -14 celcius
32+
...
33+
Observer 02: At 1783932208: -2 celcius
34+
Observer 01: At 1783932209: 30 celcius
35+
Observer 01: At 1783932209: 32 celcius
36+
Observer 01: At 1783932209: 34 celcius
37+
Observer 02 lagged, dropped 2
38+
Observer 02: At 1783932209: 4 celcius
39+
...
40+
41+
```
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
use std::time::Duration;
2+
use std::{fmt::Display, sync::Arc};
3+
4+
use aimdb_core::{buffer::BufferCfg, AimDbBuilder, Producer, RuntimeContext};
5+
use aimdb_core::{Consumer, DbError};
6+
use aimdb_tokio_adapter::{TokioAdapter, TokioRecordRegistrarExt};
7+
8+
#[derive(Debug, Clone, PartialEq)]
9+
pub struct Temperature {
10+
/// Temperature in degrees Celsius
11+
pub celcius: f32,
12+
13+
/// Unix timestamp (milliseconds)
14+
pub timestamp: u64,
15+
}
16+
17+
impl Display for Temperature {
18+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
19+
write!(f, "At {}: {} celcius", self.timestamp, self.celcius)
20+
}
21+
}
22+
23+
// Main function
24+
#[tokio::main]
25+
async fn main() -> Result<(), DbError> {
26+
println!("=== hello-spmc-ring-async: SPMC ring buffer demo ===\n");
27+
println!("Ring size: 10");
28+
println!("Producer at rate 20.0 messages/sec");
29+
println!("Observer 01 at rate 25.0 messages/sec");
30+
println!("Observer 02 at rate 6.7 messages/sec");
31+
println!("\n");
32+
33+
// configuration
34+
let adapter = Arc::new(TokioAdapter);
35+
let mut builder = AimDbBuilder::new().runtime(adapter);
36+
37+
// Two observers consuming at different rates
38+
builder.configure::<Temperature>("sensor.temp", |reg| {
39+
reg.buffer(BufferCfg::SpmcRing { capacity: 10 })
40+
.source(rollout_source)
41+
.tap(rollout_observer01)
42+
.tap(rollout_observer02);
43+
});
44+
45+
let (_db, runner) = builder.build().await?;
46+
tokio::spawn(runner.run());
47+
tokio::time::sleep(Duration::from_millis(6000)).await;
48+
49+
Ok(())
50+
}
51+
52+
async fn rollout_source(ctx: RuntimeContext, producer: Producer<Temperature>) {
53+
let time = ctx.time();
54+
let t0: f32 = -20.0;
55+
for i in 0..40 {
56+
time.sleep_millis(50).await;
57+
// Read wall-clock time through the runtime abstraction rather than
58+
// `SystemTime::now()`, so the example stays runtime/sim-friendly.
59+
let timestamp = time.unix_time().map(|(secs, _)| secs).unwrap_or(0);
60+
publich_rollout(&producer, t0 + (i as f32 + 1.0) * 2.0, timestamp).await;
61+
}
62+
}
63+
64+
async fn publich_rollout(producer: &Producer<Temperature>, t: f32, timestamp: u64) {
65+
let temperature = Temperature {
66+
celcius: t,
67+
timestamp,
68+
};
69+
producer.produce(temperature);
70+
}
71+
72+
async fn rollout_observer01(ctx: RuntimeContext, consumer: Consumer<Temperature>) {
73+
let mut reader = consumer.subscribe();
74+
let time = ctx.time();
75+
76+
while let Ok(t) = reader.recv().await {
77+
println!("Observer 01: {}", t);
78+
time.sleep_millis(40).await;
79+
}
80+
}
81+
82+
async fn rollout_observer02(ctx: RuntimeContext, consumer: Consumer<Temperature>) {
83+
let mut reader = consumer.subscribe();
84+
let time = ctx.time();
85+
86+
// Consume at slower rate, must miss data
87+
loop {
88+
match reader.recv().await {
89+
Ok(t) => {
90+
println!("Observer 02: {}", t);
91+
time.sleep_millis(150).await;
92+
}
93+
Err(DbError::BufferLagged { lag_count, .. }) => {
94+
println!("Observer 02 lagged, dropped {lag_count}");
95+
}
96+
Err(_) => break,
97+
};
98+
}
99+
}

0 commit comments

Comments
 (0)