Use of an asynchronous runtime for the main application
Overview
The Aquarium Control application uses a hybrid execution model to balance responsiveness, low latency, and hardware constraints. It uses the **Tokio asynchronous runtime** for coordinating sensor logging, network communication, safety interlocks, and scheduling. Concurrently, it spawns dedicated **native operating system threads** for operations requiring strict timing or blocking I/O (such as direct GPIO sensor reading).
---
Task and Thread Inventory
The table below lists all tasks and threads running in the application:
| Name | Description | Type (OS Thread / Async Task) | Usage of block_on
|
|---|---|---|---|
| Main Thread | Bootstraps the application, reads configuration, establishes database pools, builds the Tokio runtime, and blocks on the execution of all tasks. | OS Thread | Yes (rt.block_on)
|
TcpCommunication
|
Mock simulator thread that handles TCP connections and commands from the GUI simulator. Runs only when use_simulator is active.
|
OS Thread | No |
Dht
|
Direct driver reader thread for the DHT22 ambient temperature and humidity sensor. Handled as an OS thread to prevent blocking the async runtime. | OS Thread | No |
i2c_interface
|
Manages serial I2C bus communications for sensor expansion units. | Async Task | No |
signal_handler
|
Captures shutdown signals (SIGINT, SIGTERM) and coordinates the graceful, phase-based shutdown of all sub-components. | Async Task | No |
schedule_check
|
Performs regular database checks against the active rules database to ensure operations are authorized based on the current time. | Async Task | No |
sensor_manager
|
Coordinates, aggregates, and caches values read from individual sensor tasks (DS18B20, DHT22, Atlas Scientific). | Async Task | No |
atlas_scientific
|
Manages data acquisition and calibration for the Atlas Scientific EZO pH and conductivity probes. | Async Task | No |
relay_manager
|
Acts as the central actuator gateway, processing on/off/pulse commands and sending them directly to the hardware relays. | Async Task | No |
data_logger
|
Periodically logs sensor values and thermal controller statuses into the MySQL database. | Async Task | No |
temperature_gradient
|
Monitors the rate of temperature changes to detect heater or ventilation inefficiencies/failures. | Async Task | No |
heating
|
Orchestrates the heating loop, using hysteresis or safety controls to actuate the aquarium heater. | Async Task | No |
ventilation
|
Controls the surface cooling fan loop, maintaining temperature thresholds. | Async Task | No |
monitors
|
Runs regular diagnostic checks and outputs warning/critical logs when states drift outside target thresholds. | Async Task | No |
refill
|
Drives the fresh water top-off system, managing level switch states and pump runs with safety timers. | Async Task | No |
feed
|
Monitors the feeding schedule and actuates the automatic feeder. | Async Task | No |
balling
|
Oversees peristaltic mineral pumps to dose minerals into the aquarium according to schedule. | Async Task | No |
tank_level_switch
|
Periodically reads the state of physical water level floats or simulator ports. | Async Task | No |
watchdog
|
Performs periodic heartbeats to ensure system safety loops are executing. | Async Task | No |
ds18b20
|
Periodically queries 1-Wire DS18B20 digital thermometers for water temperature. | Async Task | No |
messaging
|
Stub mock receiver that routes command queues when running on non-Linux platforms (like macOS). | Async Task | No |
Tokio Runtime Configuration
The Tokio runtime is explicitly built at startup with a constrained configuration:
<syntaxhighlight lang="rust" line> let rt = tokio::runtime::Builder::new_multi_thread()
.worker_threads(1) .max_blocking_threads(4) .enable_all() .build()
<syntaxhighlight>
Why this configuration is chosen
1. **`worker_threads(1)`**: The application runs on Raspberry Pi. Limiting the event loop scheduler to one worker thread minimizes CPU context-switching overhead and prevents resource exhaustion. 2. **`max_blocking_threads(4)`**: Restricting the blocking thread pool prevents the runtime from spawning too many concurrent threads when database pings, file operations, or synchronous calculations are invoked (such as via `tokio::task::spawn_blocking` or `block_in_place`).
Developer Diagnostics with Tokio Console
For runtime inspection, task profiling, and detecting deadlocks or starvation, you can run the application with **Tokio Console** integration.
Prerequisites & Compilation
Tokio Console requires unstable instrumentation. 1. Use the `debug_tokio` feature flag, which initializes the `console-subscriber` in `run.rs`:
```bash RUSTFLAGS="--cfg tokio_unstable" cargo build --features "debug_tokio" ```
2. The `tokio_unstable` compiler flag ensures that Tokio exposes internal tracing endpoints (like the task builder names).
Installing the Terminal Console
Install the client CLI on your development machine (macOS/Linux) via Cargo: ```bash cargo install --locked tokio-console ```
Running and Reading
1. Launch the compiled application: 2. Open a separate terminal window and run:
```bash tokio-console ```
This presents a live dashboard of running tasks, showing busy poll times, total lifetime, and idle periods.
Interpreting Task Size Warnings
When checking with `tokio-console`, you see warnings that tasks like `signal_handler`, `relay_manager`, `data_logger`, or `heating` are **1024 bytes or larger**.
- **These warnings can be safely ignored.**
- The size refers to the stack footprint of the compiler-generated `Future` state machine. In debug builds, these are larger due to lack of optimization.
- Because these tasks are long-lived workers spawned once at startup and run until shutdown, they are allocated on the heap only once. A 1–2 KiB heap footprint has zero impact on application performance, fragmentation, or memory leaks.