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:
let rt = tokio::runtime::Builder::new_multi_thread().worker_threads(1).max_blocking_threads(4).enable_all().build()
Why this configuration is chosen
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.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 viatokio::task::spawn_blockingorblock_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.
- Use the
debug_tokiofeature flag, which initializes theconsole-subscriberinrun.rs:RUSTFLAGS="--cfg tokio_unstable" cargo build --features "debug_tokio" - The
tokio_unstablecompiler 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:
cargo install --locked tokio-console
Running and Reading
- Launch the compiled application:
- Open a separate terminal window and run
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
Futurestate 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.