Use of an asynchronous runtime for the main application

From Aquarium-Control
Jump to navigation Jump to search

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

  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: 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: cargo install --locked tokio-console

Running and Reading

1. Launch the compiled application: 2. 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. File:File.jpg

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.