blob: 443d2c29bc1d6d0d5ed57dad290e72d430c2df59 [file] [view]
## C++ Best Practices
### State Machine for Asynchronous Queries
When dealing with asynchronous D-Bus queries that can be triggered by external
events (like `InterfacesAdded`), use a state machine to handle race conditions
and defer events:
- Use a `querying` boolean flag to protect the critical section during an active
query.
- Use an enum state (e.g., `None`, `Add`, `Remove`) to record events that arrive
_while_ a query is in flight.
- When the query completes, check the recorded state and trigger a deferred
query if needed (e.g., if an `Add` event arrived).
- Centralize the setting and clearing of the `querying` flag inside the query
function to avoid scattered state management.
### Compare, Don't Subtract for Sentinel Time Points
Because of the guaranteed overflow, you should never perform arithmetic
operations on `time_point::min()` or `time_point::max()`. They are strictly
meant to be used as sentinel (flag) values.
If you need to know if an interval is valid, use equality operators (`==` or
`!=`) to check the state of the variable before doing any math:
```cpp
auto last_event = std::chrono::steady_clock::time_point::min();
auto current_time = std::chrono::steady_clock::now();
// ❌ WRONG: This will overflow and cause undefined behavior!
// auto elapsed = current_time - last_event;
// ✅ CORRECT: Check the sentinel value first
if (last_event != std::chrono::steady_clock::time_point::min()) {
auto elapsed = current_time - last_event;
// Process the elapsed time...
}
### Coroutine Reference Safety Across Suspension Points
When using coroutines (e.g., with `boost::asio::yield_context`), do not hold references or pointers to objects in global or shared state across suspension points (`yield`).
Because the coroutine suspends and yields control back to the event loop, other asynchronous tasks or destructors (such as test teardowns) can run and modify or delete the global state before the coroutine resumes or is unwound.
* **❌ Unsafe Pattern:** Capturing a reference to a map element or global variable and accessing it in a deferred cleanup trap (deleter) or after a `yield` call. If the map is cleared during suspension, the reference becomes dangling.
* **✅ Safe Pattern:** Capture the lookup key (e.g., a string ID) **by value**, and perform a fresh lookup in the global/shared container after resuming or inside the cleanup trap. If the container was cleared, the lookup will safely fail (e.g., returning `end()`) instead of dereferencing dangling memory.
## Testing & Debugging
### Verify Mock Before Questioning Infrastructure
Before questioning Object Mapper (mapperx) performance or behavior in a testing environment, use `dbus-monitor` and `busctl` to examine the mock server implementation. The mock might be static or missing dynamic behaviors expected by the test.
### Use A/B Testing for Regressions
Use A/B testing (running specific tests with and without your changes) to narrow down which change broke what test, before claiming the test was already failing before your changes.
```