Skip to content

feat(navigator): Implement a mission route cache #27707

Open
JonasPerolini wants to merge 7 commits into
PX4:mainfrom
JonasPerolini:pr-mission-route-cache
Open

feat(navigator): Implement a mission route cache #27707
JonasPerolini wants to merge 7 commits into
PX4:mainfrom
JonasPerolini:pr-mission-route-cache

Conversation

@JonasPerolini

Copy link
Copy Markdown
Contributor

This PR was split from #27687 as a first step to simplify the review. This commit: 2e29d4f performed the split. It can be useful to understand the overall structure e.g. mission_route_types.h is very small in this PR, but it grows with the follow up.

Route Cache

Normal mission execution only keeps a small sliding window of mission items in RAM, but route planning needs random, non-blocking access to the whole mission to project points against every segment.

The cache provides that: it caches the full mission, the safe points, and the mission-land item into RAM,

MissionRouteCache
|-- full mission route cache      [0 ... CONFIG_RTL_MISSION_CACHE_SIZE - 1]
|-- safe-point cache              [all uploaded safe-point dataman items]
|-- mission-land item cache       [published mission land index]
`-- safe-point stats reader       [DM_KEY_SAFE_POINTS_STATE async state machine]

Planner reads use a zero wait timeout, so a cache miss returns failure instead of blocking Navigator while dataman or the SD card catches up.

The cache tracks mission identity and reloads when the mission changes; if an item is missing it schedules a retry with bounded exponential backoff.

Safe points have their own asynchronous state machine, are reloaded when their source identity changes, and are never exposed as stale data while a reload is in progress.

Cache Size

CONFIG_RTL_MISSION_CACHE_SIZE sets the maximum number of mission items reserved for full-route planning.
The default is 100 for BOARD_TESTING builds and 0 otherwise.

A value of 0 disables the cache and reserves no entries, so boards that enable a route-planning consumer must size it for their RAM budget. Each reserved item uses roughly 76 bytes of heap for the lifetime of Navigator.

Note that CONFIG_RTL_MISSION_CACHE_SIZE only affects _dataman_cache_mission so the RTL behavior is not affected

Cache Coherency

Navigator can hold more than one cache view of the same mission item:

E.g. mission_route_cache holds:

  • _dataman_cache_mission
  • _dataman_cache_safepoint
  • _dataman_cache_land_item

DatamanCache::updateCachedItem() lets a caller copy an already-written, stable value into another cache without issuing a second dataman request.
MissionRouteCache::syncMissionItem() uses it to keep the planner-facing caches coherent after a mission item was written through another path.

@github-actions github-actions Bot added kind:feature Request or change that adds new functionality. kind:test Adds or improves tests. scope:build-system CMake, Kconfig, board config, or build tooling. scope:navigation Missions, RTL, geofence, takeoff, landing, or navigator behavior. scope:testing Unit, integration, fuzzing, or test data. scope:boards Board-specific changes or hardware definitions. labels Jun 21, 2026
@JonasPerolini

JonasPerolini commented Jun 21, 2026

Copy link
Copy Markdown
Contributor Author

Hey @bkueng, would you have time to review this PR please?

@JonasPerolini JonasPerolini requested a review from bkueng June 21, 2026 15:09
@github-actions

github-actions Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

🔎 FLASH Analysis

px4_fmu-v5x [Total VM Diff: 3600 byte (0.18 %)]
    FILE SIZE        VM SIZE    
 --------------  -------------- 
  +0.2% +3.52Ki  +0.2% +3.52Ki    .text
    +0.6%    +968  +0.6%    +968    [section .text]
   -92.2%    +928 -92.2%    +928    [59 Others]
    [NEW]    +488  [NEW]    +488    MissionRouteCache::updateSafePointCache()
    [NEW]    +360  [NEW]    +360    MissionRouteCache::updateMissionCache()
    [NEW]    +320  [NEW]    +320    MissionRouteCache::updateMissionLandItemCache()
    [NEW]    +236  [NEW]    +236    mission_route::Provider::scanVtolLandApproachBlock()
    [NEW]    +196  [NEW]    +196    mission_route::Provider::findAssociatedSafePointIndex()
    [NEW]    +164  [NEW]    +164    mission_route::Position::valid()
    +8.1%    +156  +8.1%    +156    Navigator::Navigator()
    [NEW]    +136  [NEW]    +136    mission_route::Provider::anySafePointHasVtolLandApproach()
    [NEW]    +136  [NEW]    +136    mission_route::Provider::getMissionTakeoffItem()
    [NEW]    +134  [NEW]    +134    MissionRouteCache::getMissionLandItem()
    [NEW]    +134  [NEW]    +134    mission_route::Provider::getVtolLandApproachesNearLocation()
    [NEW]    +128  [NEW]    +128    mission_route::extractSafePointPosition()
    [NEW]    +100  [NEW]    +100    MissionRouteCache::missionCacheFullyLoaded()
    [NEW]     +88  [NEW]     +88    MissionRouteCache::queueMissionCacheLoads()
    [DEL]    -140  [DEL]    -140    RTL::getVtolLandApproachesNearLocation()
    [DEL]    -192  [DEL]    -192    RTL::scanVtolLandApproachBlock()
    [DEL]    -224  [DEL]    -224    RTL::findAssociatedSafePointIndex()
    [DEL]    -248  [DEL]    -248    RTL::extractValidSafePointPosition()
   -94.4%    -268 -94.4%    -268    RTL::updateDatamanCache()
  +0.3% +5.32Ki  [ = ]       0    .debug_abbrev
  +0.2%    +336  [ = ]       0    .debug_aranges
  +0.2% +1.03Ki  [ = ]       0    .debug_frame
  +0.2% +46.6Ki  [ = ]       0    .debug_info
  +0.2% +7.91Ki  [ = ]       0    .debug_line
    +150%      +3  [ = ]       0    [Unmapped]
    +0.2% +7.91Ki  [ = ]       0    [section .debug_line]
  +0.1% +3.52Ki  [ = ]       0    .debug_loclists
  +0.1%    +668  [ = ]       0    .debug_rnglists
    [NEW]      +1  [ = ]       0    [Unmapped]
    +0.1%    +667  [ = ]       0    [section .debug_rnglists]
  +0.1% +4.39Ki  [ = ]       0    .debug_str
  +0.2% +1.64Ki  [ = ]       0    .strtab
    [NEW]     +44  [ = ]       0    DatamanClient::abortCurrentOperation()
    [NEW]     +24  [ = ]       0    MissionRouteCache
    [NEW]     +54  [ = ]       0    MissionRouteCache::RetryBackoff::scheduleRetry()
    [NEW]     +64  [ = ]       0    MissionRouteCache::getMissionLandItem()
    [NEW]     +60  [ = ]       0    MissionRouteCache::loadMissionItem()
    [NEW]     +62  [ = ]       0    MissionRouteCache::loadSafePointItem()
    [NEW]     +62  [ = ]       0    MissionRouteCache::missionCacheFullyLoaded()
    [NEW]     +40  [ = ]       0    MissionRouteCache::missionCount()
    [NEW]     +59  [ = ]       0    MissionRouteCache::missionLandItemCacheFullyLoaded()
    [NEW]     +58  [ = ]       0    MissionRouteCache::missionMatchesCache()
    [NEW]     +48  [ = ]       0    MissionRouteCache::publishSafePointCache()
    [NEW]     +60  [ = ]       0    MissionRouteCache::queueMissionCacheLoads()
    [NEW]     +47  [ = ]       0    MissionRouteCache::queueMissionLandItem()
    [NEW]     +51  [ = ]       0    MissionRouteCache::resetSafePointCacheState()
    [NEW]     +53  [ = ]       0    MissionRouteCache::safePointCacheFullyLoaded()
    [NEW]     +58  [ = ]       0    MissionRouteCache::safePointCacheMatchesReadStats()
    [NEW]     +42  [ = ]       0    MissionRouteCache::safePointCount()
    [NEW]     +43  [ = ]       0    MissionRouteCache::update()
    [NEW]     +56  [ = ]       0    MissionRouteCache::updateMissionCache()
    [NEW]     +64  [ = ]       0    MissionRouteCache::updateMissionLandItemCache()
   -97.6%    +635  [ = ]       0    [26 Others]
  +0.2% +1.09Ki  [ = ]       0    .symtab
   -50.0%     -16  [ = ]       0    DatamanCache::changeUpdateIndex()
     +50%     +16  [ = ]       0    DatamanCache::invalidate()
    [NEW]     +16  [ = ]       0    DatamanClient::abortCurrentOperation()
     +50%     +16  [ = ]       0    DatamanClient::lastOperationCompleted()
   -50.0%     -16  [ = ]       0    MissionBase::on_inactivation()
     +50%     +16  [ = ]       0    MissionBase::updateDatamanCache()
    [NEW]     +32  [ = ]       0    MissionRouteCache
    [NEW]     +48  [ = ]       0    MissionRouteCache::RetryBackoff::scheduleRetry()
    [NEW]     +32  [ = ]       0    MissionRouteCache::getMissionLandItem()
    [NEW]     +32  [ = ]       0    MissionRouteCache::loadMissionItem()
    [NEW]     +16  [ = ]       0    MissionRouteCache::loadSafePointItem()
    [NEW]     +16  [ = ]       0    MissionRouteCache::missionCacheFullyLoaded()
    [NEW]     +48  [ = ]       0    MissionRouteCache::missionCount()
    [NEW]     +32  [ = ]       0    MissionRouteCache::missionLandItemCacheFullyLoaded()
    [NEW]     +48  [ = ]       0    MissionRouteCache::missionMatchesCache()
    [NEW]     +48  [ = ]       0    MissionRouteCache::publishSafePointCache()
    [NEW]     +48  [ = ]       0    MissionRouteCache::queueMissionCacheLoads()
    [NEW]     +48  [ = ]       0    MissionRouteCache::queueMissionLandItem()
    [NEW]     +16  [ = ]       0    MissionRouteCache::resetSafePointCacheState()
    [NEW]     +32  [ = ]       0    MissionRouteCache::safePointCacheFullyLoaded()
   -96.0%    +592  [ = ]       0    [47 Others]
  +5.9%    +496  [ = ]       0    [Unmapped]
  +0.2% +76.5Ki  +0.2% +3.52Ki    TOTAL

px4_fmu-v6x [Total VM Diff: 3584 byte (0.19 %)]
    FILE SIZE        VM SIZE    
 --------------  -------------- 
  +0.2% +3.50Ki  +0.2% +3.50Ki    .text
    +0.6%    +960  +0.6%    +960    [section .text]
   -92.3%    +920 -92.3%    +920    [59 Others]
    [NEW]    +488  [NEW]    +488    MissionRouteCache::updateSafePointCache()
    [NEW]    +360  [NEW]    +360    MissionRouteCache::updateMissionCache()
    [NEW]    +320  [NEW]    +320    MissionRouteCache::updateMissionLandItemCache()
    [NEW]    +236  [NEW]    +236    mission_route::Provider::scanVtolLandApproachBlock()
    [NEW]    +196  [NEW]    +196    mission_route::Provider::findAssociatedSafePointIndex()
    [NEW]    +164  [NEW]    +164    mission_route::Position::valid()
    +8.1%    +156  +8.1%    +156    Navigator::Navigator()
    [NEW]    +136  [NEW]    +136    mission_route::Provider::anySafePointHasVtolLandApproach()
    [NEW]    +136  [NEW]    +136    mission_route::Provider::getMissionTakeoffItem()
    [NEW]    +134  [NEW]    +134    MissionRouteCache::getMissionLandItem()
    [NEW]    +134  [NEW]    +134    mission_route::Provider::getVtolLandApproachesNearLocation()
    [NEW]    +128  [NEW]    +128    mission_route::extractSafePointPosition()
    [NEW]    +100  [NEW]    +100    MissionRouteCache::missionCacheFullyLoaded()
    [NEW]     +88  [NEW]     +88    MissionRouteCache::queueMissionCacheLoads()
    [DEL]    -140  [DEL]    -140    RTL::getVtolLandApproachesNearLocation()
    [DEL]    -192  [DEL]    -192    RTL::scanVtolLandApproachBlock()
    [DEL]    -224  [DEL]    -224    RTL::findAssociatedSafePointIndex()
    [DEL]    -248  [DEL]    -248    RTL::extractValidSafePointPosition()
   -94.4%    -268 -94.4%    -268    RTL::updateDatamanCache()
  +0.3% +5.32Ki  [ = ]       0    .debug_abbrev
  +0.2%    +336  [ = ]       0    .debug_aranges
  +0.2% +1.03Ki  [ = ]       0    .debug_frame
  +0.2% +46.6Ki  [ = ]       0    .debug_info
  +0.2% +7.90Ki  [ = ]       0    .debug_line
   -83.3%      -5  [ = ]       0    [Unmapped]
    +0.2% +7.91Ki  [ = ]       0    [section .debug_line]
  +0.1% +3.52Ki  [ = ]       0    .debug_loclists
  +0.1%    +664  [ = ]       0    .debug_rnglists
    [DEL]      -3  [ = ]       0    [Unmapped]
    +0.1%    +667  [ = ]       0    [section .debug_rnglists]
  +0.1% +4.39Ki  [ = ]       0    .debug_str
  +0.3% +1.64Ki  [ = ]       0    .strtab
    [NEW]     +44  [ = ]       0    DatamanClient::abortCurrentOperation()
    [NEW]     +24  [ = ]       0    MissionRouteCache
    [NEW]     +54  [ = ]       0    MissionRouteCache::RetryBackoff::scheduleRetry()
    [NEW]     +64  [ = ]       0    MissionRouteCache::getMissionLandItem()
    [NEW]     +60  [ = ]       0    MissionRouteCache::loadMissionItem()
    [NEW]     +62  [ = ]       0    MissionRouteCache::loadSafePointItem()
    [NEW]     +62  [ = ]       0    MissionRouteCache::missionCacheFullyLoaded()
    [NEW]     +40  [ = ]       0    MissionRouteCache::missionCount()
    [NEW]     +59  [ = ]       0    MissionRouteCache::missionLandItemCacheFullyLoaded()
    [NEW]     +58  [ = ]       0    MissionRouteCache::missionMatchesCache()
    [NEW]     +48  [ = ]       0    MissionRouteCache::publishSafePointCache()
    [NEW]     +60  [ = ]       0    MissionRouteCache::queueMissionCacheLoads()
    [NEW]     +47  [ = ]       0    MissionRouteCache::queueMissionLandItem()
    [NEW]     +51  [ = ]       0    MissionRouteCache::resetSafePointCacheState()
    [NEW]     +53  [ = ]       0    MissionRouteCache::safePointCacheFullyLoaded()
    [NEW]     +58  [ = ]       0    MissionRouteCache::safePointCacheMatchesReadStats()
    [NEW]     +42  [ = ]       0    MissionRouteCache::safePointCount()
    [NEW]     +43  [ = ]       0    MissionRouteCache::update()
    [NEW]     +56  [ = ]       0    MissionRouteCache::updateMissionCache()
    [NEW]     +64  [ = ]       0    MissionRouteCache::updateMissionLandItemCache()
   -97.5%    +635  [ = ]       0    [24 Others]
  +0.2% +1.09Ki  [ = ]       0    .symtab
   -50.0%     -16  [ = ]       0    DatamanCache::changeUpdateIndex()
     +50%     +16  [ = ]       0    DatamanCache::invalidate()
    [NEW]     +16  [ = ]       0    DatamanClient::abortCurrentOperation()
     +50%     +16  [ = ]       0    DatamanClient::lastOperationCompleted()
   -50.0%     -16  [ = ]       0    MissionBase::on_inactivation()
     +50%     +16  [ = ]       0    MissionBase::updateDatamanCache()
    [NEW]     +32  [ = ]       0    MissionRouteCache
    [NEW]     +48  [ = ]       0    MissionRouteCache::RetryBackoff::scheduleRetry()
    [NEW]     +32  [ = ]       0    MissionRouteCache::getMissionLandItem()
    [NEW]     +32  [ = ]       0    MissionRouteCache::loadMissionItem()
    [NEW]     +16  [ = ]       0    MissionRouteCache::loadSafePointItem()
    [NEW]     +16  [ = ]       0    MissionRouteCache::missionCacheFullyLoaded()
    [NEW]     +48  [ = ]       0    MissionRouteCache::missionCount()
    [NEW]     +32  [ = ]       0    MissionRouteCache::missionLandItemCacheFullyLoaded()
    [NEW]     +48  [ = ]       0    MissionRouteCache::missionMatchesCache()
    [NEW]     +48  [ = ]       0    MissionRouteCache::publishSafePointCache()
    [NEW]     +48  [ = ]       0    MissionRouteCache::queueMissionCacheLoads()
    [NEW]     +48  [ = ]       0    MissionRouteCache::queueMissionLandItem()
    [NEW]     +16  [ = ]       0    MissionRouteCache::resetSafePointCacheState()
    [NEW]     +32  [ = ]       0    MissionRouteCache::safePointCacheFullyLoaded()
   -96.0%    +592  [ = ]       0    [45 Others]
  +9.6%    +512  [ = ]       0    [Unmapped]
  +0.2% +76.5Ki  +0.2% +3.50Ki    TOTAL

Updated: 2026-07-10T06:37:12

@JonasPerolini

Copy link
Copy Markdown
Contributor Author

State machine summary:

RTL::updateDatamanCache() calls MissionRouteCache::update(_mission_sub.get()).
This happens while RTL is inactive and active. The important path is inactive, before setRtlTypeAndDestination() is periodically recomputed.

On mission identity change, updateMissionCache() invalidates the RAM cache, queues all mission items with DatamanCache::load(), sets validation_pending = true.

DatamanCache::update() advances async dataman reads. The loaded items: DatamanCache::_items[].response.data are in RAM once each item reaches ResponseReceived.

Once loading stops, missionCacheFullyLoaded() validates every queued item using loadWait(..., kCacheOnlyLoadWait), with timeout 0. At this point a miss means that the async preload did not work so it should retry, not block.

Only after that validation does _mission.ready = true.

Planner/provider reads call loadMissionItem() / getMissionLandItem() / loadSafePointItem(). These use timeout 0 because they are reading from an already validated RAM cache, not calling SD card reads.

Comment thread src/modules/navigator/rtl.cpp Outdated
Comment on lines +459 to +468
if (!success) {
/* not supposed to happen unless the datamanager can't access the SD card, etc. */
mavlink_log_critical(_navigator->get_mavlink_log_pub(), "Mission land item could not be read.\t");
events::send(events::ID("rtl_failed_to_read_land_item"), events::Log::Error,
"Mission land item could not be read");
}
const bool land_item_pending = mission_route_cache != nullptr
&& mission_route_cache->missionLandItemUpdatePending();

if (!land_item_pending) {
/* Not supposed to happen unless the datamanager can't access the SD card, etc. */
mavlink_log_critical(_navigator->get_mavlink_log_pub(), "Mission land item could not be read.\t");
events::send(events::ID("rtl_failed_to_read_land_item"), events::Log::Error,
"Mission land item could not be read");
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @bkueng,

I'm not sure how to handle the case where the land item cache is still pending. In that case it's not a read error so sending the critical warning can be misleading.

On the other hand, re-evaluating setRtlTypeAndDestination does not seem right because we've already fell back to home / safepoint.

Any thoughts on how to handle this corner case?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My preferred option is to remove: if (!land_item_pending) { and have pending equivalent to SD card read failure. This is the simplest option and in practice the land item should not be pending since on_inactive() we call updateDatamanCache.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed if (!land_item_pending) { here 38ccd0d

@bkueng

bkueng commented Jul 6, 2026

Copy link
Copy Markdown
Member

I'll try to find some time for a review.

@JonasPerolini

Copy link
Copy Markdown
Contributor Author

I'll try to find some time for a review.

Thanks @bkueng, please note that I have created a simpler PR as a first step: #27804. That PR does not contain the mission route cache (which is the trickier part of the PR)

@JonasPerolini JonasPerolini force-pushed the pr-mission-route-cache branch from 2cc49d9 to ff46fd6 Compare July 10, 2026 06:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind:feature Request or change that adds new functionality. kind:test Adds or improves tests. scope:boards Board-specific changes or hardware definitions. scope:build-system CMake, Kconfig, board config, or build tooling. scope:navigation Missions, RTL, geofence, takeoff, landing, or navigator behavior. scope:testing Unit, integration, fuzzing, or test data.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants