Use of an asynchronous runtime for the main application

From Aquarium-Control
Revision as of 13:28, 11 July 2026 by Uwe (talk | contribs)
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:

```rust 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`:

  ```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.