docs+test: thread-safety contract for EquipmentDataModel
INTEGRATION.md §3 used to show a sensor-poll thread calling model->svids.set_value() directly while the io_context thread reads the same SVID for an inbound S1F3. That's a data race — there are zero locks anywhere in EquipmentDataModel and there's no intention to add them. The library is single-threaded by design; the doc was just inviting trouble. This commit makes the actual contract explicit: - INTEGRATION.md §3: thread-safety callout box. All access must run on the io_context that drives the HSMS connection. Sensor updates from other threads marshal via asio::post(io.get_executor(), ...). Same applies to set_*_change_handler callbacks (they fire on the io_context thread; observers must be thread-safe or hand work off). - README.md §3 (Monitoring & observability): added a paragraph noting that hooks fire on the io_context thread, blocking I/O inside a handler stalls the dispatcher, and metrics exporters must respect the same contract. - tests/test_thread_safety.cpp: two scenarios that exercise the canonical pattern — N producer threads asio::post sensor updates onto a worker-driven io_context; reads marshal back through the io. Catches obvious regressions (e.g. someone adding a "convenience" cross-thread mutator that bypasses the strand). A passing run isn't proof of race-freedom under ThreadSanitizer — it pins down the *pattern* customers should follow. TSan integration is a separate workstream. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+27
-10
@@ -144,23 +144,40 @@ That's the floor. From here, every section below adds capability.
|
||||
## 3. Wiring real sensors to SVIDs
|
||||
|
||||
The YAML's `value:` field is the *initial* value. Your application
|
||||
updates the live value as the tool runs:
|
||||
updates the live value as the tool runs.
|
||||
|
||||
```cpp
|
||||
// In your sensor-poll thread (running on a separate executor):
|
||||
double torr = read_baratron();
|
||||
model->svids.set_value(/*ChamberPressure=*/100, secs2::Item::f4(float(torr)));
|
||||
```
|
||||
|
||||
That's it — the next S1F3 from the host returns the fresh value.
|
||||
> **Thread-safety contract.** Every store in `EquipmentDataModel` is
|
||||
> single-threaded by design: there are no locks. All access — reads
|
||||
> from the dispatcher, writes from your application — must run on the
|
||||
> io_context that drives the HSMS connection. If your sensor polls
|
||||
> live on a different thread (typical), marshal the update via
|
||||
> `asio::post`:
|
||||
>
|
||||
> ```cpp
|
||||
> // Sensor-poll thread (separate from the io_context thread):
|
||||
> double torr = read_baratron();
|
||||
> asio::post(io.get_executor(), [model, torr] {
|
||||
> model->svids.set_value(/*ChamberPressure=*/100,
|
||||
> secs2::Item::f4(float(torr)));
|
||||
> });
|
||||
> ```
|
||||
>
|
||||
> Calling `set_value(...)` directly from the sensor thread is a data
|
||||
> race against the dispatcher reading the same SVID for an inbound
|
||||
> S1F3 — the library has no mutex to defend you. This is also true
|
||||
> for every `set_*_change_handler` callback you register: those fire
|
||||
> on the io_context thread, and any state observers (metrics
|
||||
> exporters, log shippers) must be thread-safe themselves or must
|
||||
> hand the work off.
|
||||
|
||||
Two patterns scale well:
|
||||
|
||||
1. **One updater per sensor, fixed cadence.** Each sensor's driver
|
||||
owns the (vid, set_value) pair.
|
||||
owns the (vid, set_value) pair and `asio::post`s into the io_context.
|
||||
2. **A single refresh tick.** A periodic timer dumps all polled
|
||||
values at once (`refresh()` in `apps/secs_server.cpp` does this
|
||||
for two virtual SVIDs).
|
||||
for two virtual SVIDs). Because the periodic timer runs *on* the
|
||||
io_context, no posting is needed.
|
||||
|
||||
The SECS-II Item shape must match the YAML's `type:`. If the YAML
|
||||
says `F4` and you call `set_value(100, secs2::Item::ascii("..."))`,
|
||||
|
||||
Reference in New Issue
Block a user