feat(dataman): handle zero-size cache and clear pending responses on new request#27552
Conversation
| if (!_items) { | ||
| return false; | ||
| } |
There was a problem hiding this comment.
With a zero-size cache (a cache made with 0, or resized to 0), _items can be nullptr. The old if (!_items) return false; made loadWait() give up right away for an empty cache, so it never reached the readSync fallback even when the caller passed a timeout. That meant read-through stopped working whenever caching was turned off.
Instead of the early return, an empty cache now skips the cache lookup (there is nothing stored) but still continues to the readSync fallback, so callers with a zero-size cache still get their data from a direct read. The step that stores the result is also guarded by _items, so we never write into a null array.
🔎 FLASH Analysispx4_fmu-v5x [Total VM Diff: 192 byte (0.01 %)]px4_fmu-v6x [Total VM Diff: 192 byte (0.01 %)]Updated: 2026-06-15T19:50:08 |
|
Good evening @bkueng, could you please have a look at this PR? |
bkueng
left a comment
There was a problem hiding this comment.
Nice work, this looks correct.
Sorry my response time is quite slow these days.
| if (!_dataman_client1.readAsync(item, index, scratch, length)) { | ||
| PX4_ERR("first readAsync failed"); | ||
| return false; | ||
| } | ||
|
|
||
| // Give the dataman task time to answer while we deliberately skip update(). | ||
| px4_usleep(100_ms); | ||
|
|
||
| // Abandon the operation. The queued reply is now stale. | ||
| _dataman_client1.abortCurrentOperation(); | ||
|
|
||
| // A fresh async read must drain the stale reply and return the current value. | ||
| memset(_buffer_read, 0, sizeof(_buffer_read)); | ||
|
|
||
| if (!_dataman_client1.readAsync(item, index, _buffer_read, length)) { | ||
| PX4_ERR("second readAsync failed"); | ||
| return false; | ||
| } |
There was a problem hiding this comment.
Shouldn't these 2 reads operate on different indexes with different stored data, to ensure the right data is read?
There was a problem hiding this comment.
Hey @bkueng, you're correct, if we re-read the same idx with the same data we don't know if clearPendingResponse worked as expected. Test updated here 01b93e2 to have different values and indexes.
Note that the new version still does not prove that clearPendingResponse works as expected. If clearPendingResponse breaks and the stale data is still there, once DatamanClient::update() is called for the new idx, this condition is not true: (response.index == _active_request.index) and therefore the stale response is skipped. On the next iteration, the new response will be used and the test will pass. (Confirmed by commenting out clearPendingResponse(); in readAsync, test still passes)
The proper test would be:
- Write
stale_valueto index 7. readAsyncindex 7 (queues the stale reply).- Abort.
- Change index 7 data to
new_value. readAsyncindex 7, if it fails to clear the queue, it reads thestale_value
The problem is that I don't know how to perform step 4. I thought about using _dataman_client2 to change the backend but this would queue to the same uorb topic.
There was a problem hiding this comment.
I'm wondering if the best option is to just remove this specific test. We could also keep it as it tests that stale responses are discarded when the response item does not match the active request (response.index == _active_request.index)
There was a problem hiding this comment.
I've confirmed that clearPendingResponse works as expected by removing the check (response.index == _active_request.index) inside of update()
- Test passes with
clearPendingResponseinside ofreadAsync - Test fails when
clearPendingResponseis commented out:
ERROR [tests] reissued read returned wrong data at 0
ERROR [tests] TEST FAILED: testAsyncAbortAndReissue
There was a problem hiding this comment.
I think the test makes sense either way. It should not matter which internal mechanism prevents a stale response, as long as the overall result is as expected.
Initially implemented for #26973 and split the large PR into smaller ones.
Changes:
Handle cache with zero size:
_dataman_cache{"geofence_dm_cache_miss", 0}) creates its cache with size 0, and themission/RTL caches resize to a count that can be 0 (e.g.
_dataman_cache_safepoint.resize(_stats.num_items);).resize(0)frees the buffer and resets stateStale responses: before sending a request, clear any queued reply on the response topic, so a response from an aborted or timed-out operation can't be matched to the next request.
Tested with unit tests in
test_dataman.cpp: