Skip to content

nav2_ros_common: add lifecycle-managed Subscription wrapper#5834

Draft
Lotusymt wants to merge 37 commits into
ros-navigation:mainfrom
Lotusymt:feature/nav2_ros_common-lifecycle-interfaces-5298
Draft

nav2_ros_common: add lifecycle-managed Subscription wrapper#5834
Lotusymt wants to merge 37 commits into
ros-navigation:mainfrom
Lotusymt:feature/nav2_ros_common-lifecycle-interfaces-5298

Conversation

@Lotusymt
Copy link
Copy Markdown
Contributor

@Lotusymt Lotusymt commented Jan 3, 2026


Basic Info

Info Please fill out this column
Ticket(s) this addresses #5298
Primary OS tested on Ubuntu
Robotic platform tested on N/A (compile + unit tests only)
Does this PR contain AI generated software? No
Was this PR description generated by AI software? No

Description of contribution in a few bullet points

  • Introduced a lifecycle-managed nav2::Subscription wrapper (similar to nav2::Publisher) using rclcpp_lifecycle::SimpleManagedEntity, so subscriptions can participate in lifecycle transitions.
  • Gated user callbacks on is_activated() and emit a one-time warning when messages are received before activation (messages are dropped until activated).
  • Kept non-lifecycle node compatibility: when constructed with a plain rclcpp::Node (no lifecycle manager to trigger activation), the wrapper auto-activates so behavior matches existing non-lifecycle usage.
  • Updated test_actions slightly to make the callback type/signature match the new subscription wrapper expectations (no new tests added).

Description of documentation updates required from your changes

  • Update after resolving the whole issue.

Description of how this change was tested

  • Built nav2_ros_common and ran its test suite via colcon test (and verified results with colcon test-result).

Future work that may be required in bullet points

  • Repeat the same pattern for services/clients/action servers/action clients incrementally.
  • After those land, follow up with the LifecycleNode::activateInterfaces() ordering work (exporters before consumers; deactivate in reverse), and then remove the manual activation boilerplate in task servers.
  • Finally, add the “autoactivate if node already active when interface is created” behavior across the remaining interfaces (matching nav2::Publisher).

For Maintainers:

  • Check that any new parameters added are updated in docs.nav2.org
  • Check that any significant change is added to the migration guide
  • Check that any new features OR changes to existing behaviors are reflected in the tuning guide
  • Check that any new functions have Doxygen added
  • Check that any new features have test coverage
  • Check that any new plugins is added to the plugins page
  • If BT Node, Additionally: add to BT's XML index of nodes for groot, BT package's readme table, and BT library lists
  • Should this be backported to current distributions? If so, tag with backport-*.

@mini-1235
Copy link
Copy Markdown
Collaborator

@Lotusymt
Copy link
Copy Markdown
Contributor Author

Lotusymt commented Jan 5, 2026

@Lotusymt check CI https://app.circleci.com/pipelines/github/ros-navigation/navigation2/17387/workflows/c2829a5d-9b2d-4eaf-8044-5e5871faee16/jobs/51482, this does not build :(

Thanks for checking! This was just a draft attempt, and I haven’t addressed the CI issues yet. I’ll follow up after discussing the proposed approach for this issue.

@Lotusymt Lotusymt force-pushed the feature/nav2_ros_common-lifecycle-interfaces-5298 branch 3 times, most recently from 4ae2015 to 1be146b Compare January 15, 2026 04:45
Copy link
Copy Markdown
Member

@SteveMacenski SteveMacenski left a comment

Choose a reason for hiding this comment

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

Look good!

Comment thread nav2_costmap_2d/test/unit/speed_filter_test.cpp Outdated
Comment thread nav2_ros_common/include/nav2_ros_common/interface_factories.hpp
Comment thread nav2_ros_common/include/nav2_ros_common/interface_factories.hpp
Comment thread nav2_ros_common/include/nav2_ros_common/interface_factories.hpp Outdated
Comment thread nav2_ros_common/include/nav2_ros_common/interface_factories.hpp Outdated
Comment thread nav2_ros_common/include/nav2_ros_common/lifecycle_node.hpp Outdated
Comment thread nav2_ros_common/include/nav2_ros_common/subscription.hpp Outdated
Comment thread nav2_ros_common/include/nav2_ros_common/subscription.hpp Outdated
Comment thread nav2_ros_common/include/nav2_ros_common/subscription.hpp Outdated
@SteveMacenski
Copy link
Copy Markdown
Member

I think this is ready to be applied to all subscriptions now!

I still have to verify that below is correct, but otherwise LGTM. Can you clarify where you found this + the any_cb_ pattern? If that's established somewhere in ROS 2 core then I can just take that at face value

        // Use the appropriate dispatch method based on whether the message came from
        // intra-process (composition) or inter-process (DDS/serialization)
        if (info.get_rmw_message_info().from_intra_process) {
          any_cb_.dispatch_intra_process(msg, info);
        } else {
          auto non_const_msg = std::make_shared<MessageT>(*msg);
          any_cb_.dispatch(non_const_msg, info);
        }

@Lotusymt
Copy link
Copy Markdown
Contributor Author

Lotusymt commented Jan 23, 2026

Hi @SteveMacenski, this is following ROS 2 core (rclcpp) behavior/pattern:

@SteveMacenski
Copy link
Copy Markdown
Member

OK sounds good! I think the remaining bit is to update for all the servers :-)

@SteveMacenski
Copy link
Copy Markdown
Member

@Lotusymt any update? I would love to get this moving

@Lotusymt
Copy link
Copy Markdown
Contributor Author

update for all the servers

@SteveMacenski sorry for the slow update, I misinterpreted “apply to all servers” as “move on to the remaining server interfaces.

I believe the “apply everywhere” step is already done, as remaining rclcpp::Subscription usages across Nav2 servers have been migrated to nav2::Subscription. Please correct me if I missed something.

@SteveMacenski
Copy link
Copy Markdown
Member

Don't we need to activate all the subscriptions in on_activate() and deactivate respectively?

@Lotusymt
Copy link
Copy Markdown
Contributor Author

Don't we need to activate all the subscriptions in on_activate() and deactivate respectively?

Yes, for now they(all interfaces) are activated/deactivated at call sites. Per the #5298 issue description:

“Step 3
Once we have all of them being lifecycle-enabled, we can then update the Nav2 Lifecycle Node implementation to use a nav2::LifecycleNode::activateInterfaces() whereas we create all publishers, then clients, then action clients, then subscriptions, services, and action servers, respectively. That way all 'processing' tasks are activated only once all 'information' tasks are provided. At that point, we can remove all of the activate() / on_activate() calls from the Nav2 lifecycle node transition functions which would make that code much more concise and handled behind the scenes.”

So my understanding is:

  1. Interfaces (starting with nav2::Subscription) should support lifecycle transitions, but the activation routing should ultimately be centralized (via activateInterfaces() + ordering), rather than every server manually calling on_activate() / on_deactivate() for each interface.
  2. I haven’t removed existing manual activation/deactivation at call sites yet because that cleanup depends on Step 3 (having all interfaces lifecycle-enabled + ordered activation/deactivation).

@Lotusymt
Copy link
Copy Markdown
Contributor Author

Lotusymt commented Feb 9, 2026

Hi @SteveMacenski, just wanted to gently follow up here in case this got buried. Please let me know if I missed anything.

@SteveMacenski
Copy link
Copy Markdown
Member

SteveMacenski commented Feb 12, 2026

Sorry, I've been really slammed and haven't been able to clear all the github comments I need to respond to each day. Sorry that this was one that was temporarily deprioritized. I was traveling and this one was hard to review on my small laptop screen.

My bad that it was just Q&A, I thought it was going to be another full review of many files :-) Ooops.

Interfaces (starting with nav2::Subscription) should support lifecycle transitions, but the activation routing should ultimately be centralized (via activateInterfaces() + ordering), rather than every server manually calling on_activate() / on_deactivate() for each interface.

Eventually, to start, lets manually do it like we do for publishers and action servers. The ordering here is really important for bringup stability or segfaults, so I want that to be thoughtfully done

I haven’t removed existing manual activation/deactivation at call sites yet because that cleanup depends on Step 3 (having all interfaces lifecycle-enabled + ordered activation/deactivation).

I would want to change to use the auto activate part once we have pub/sub/service/service clients/action clients all (maybe action server; maybe not yet since that's a special child).

@Lotusymt
Copy link
Copy Markdown
Contributor Author

Hi Steve , just want to let you know I am actively working on it and here's a little update. I manage to pass all tests expect for some in nav2_system_test. Seems like some activation deadlock issue? Hopefully I can solve it soon : )

@SteveMacenski
Copy link
Copy Markdown
Member

Ready for me now? :-)

Comment on lines +57 to +65
void on_activate() override
{
rclcpp_lifecycle::SimpleManagedEntity::on_activate();
}

void on_deactivate() override
{
rclcpp_lifecycle::SimpleManagedEntity::on_deactivate();
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

These don't need to be overrided if just calling the base class, no? :-)

@Lotusymt
Copy link
Copy Markdown
Contributor Author

Ready for me now? :-)

Not yet, unfortunately qwq. I ran into a few more issues while testing this.

For now I’m trying a workaround: for any Transient Local topic, I avoid having the subscription exist in an “inactive” state at all. Concretely, I activate the managed-entity wrapper right before creating the underlying rclcpp::Subscription, so the subscription is effectively “active” for its entire lifespan and we don’t risk losing the latched/transient sample while the wrapper is inactive.

My reasoning:

  1. I couldn’t find an API that truly “disconnects” a subscription from DDS during init (i.e., not just dropping messages, but actually detaching at the middleware level).

  2. Even if 1 is possible, Some (transient local)subscriptions likely need to be processed during configure, not activate. For example map_sub_ can involve heavier work, and if that happens in on_activate() it can make activation slow and potentially starve itself (or other components/tests that depend on activation finishing promptly).

Even with this change, I’m still seeing failures in some system tests. That makes me suspect there may be other subscriptions that also need to be processed earlier, but I’m still tracking down exactly which ones and why.

Does this workaround sound reasonable to you? Any suggestions would be really appreciated!!

@SteveMacenski
Copy link
Copy Markdown
Member

@fujitatomoya a question for you based on the last comment - you don't need to read the whole thread:

Is there a way for the subscription callback to reject a message for a later delivery or create a subscriber object on the network but not add it to be serviced by the executor yet? We're trying to implement lifecycle versions of the subscriptions/services/clients since they don't exist in rclcpp_lifecycle and we're having a problem with how to indicate "No! I'm not active, don't even try it" for services and subscriptions.

Services will just happily accept a response of a default constructed result if we exit the callback when the node is not active, and we have problems with subscriptions that are transient local since the delivery will happen only once. Streaming data being dropped (like a sensor) is fine, but this is a case where its not fine.

We have long been able to do this with our Simple Action Server since there's an explicit "reject goal" API for actions so the client knows that its request was thrown out. I don't think that is sensible for services/subscriptions, but I feel like there should be a way to construct them and connect them on bringup without servicing them until active. This seems like a gap in the available API that after some digging I couldn't see how to address. But you're more in these details so I'm curious about your thoughts :-)

As always, I don't really want to maintain my own version of things that have general purpose; happy to have these all donated to rclcpp_lifecycle (and simple action server to rclcpp_actions) :-)

@SteveMacenski
Copy link
Copy Markdown
Member

@Lotusymt all things should be processed in active state, with the sole exception of TF which is required to be running beforehand so we can check TF transformations as existing as part of the activation phase.

That makes me suspect there may be other subscriptions that also need to be processed earlier, but I’m still tracking down exactly which ones and why.

That is strange and should generally not be true. The nav2 lifecycle manager should bring all nodes into configure before all nodes into active. So by the time something is being activated, they're all configured but should not be processing data (except those already activated beforehand). There should not be dependency on anything processing messages in configure -- and if there are that's a bug we need to fix (again with the exception of TF). Could be ordering of bringup -- but also could be some interaction we should address. Do you know what node(s) or what topic(s) are the offenders?

What are the failures?

@fujitatomoya
Copy link
Copy Markdown

@SteveMacenski

1st thing's 1st to answer your question.

Is there a way for the subscription callback to reject a message for a later delivery or create a subscriber object on the network but not add it to be serviced by the executor yet?

AFAIK, no... unfortunately, there is no such things yet...

probably something you guys need is something like ros2/rclcpp#2715?

IMO LifecycleEntity makes sense for some use cases, there is a few primary states that we need to consider by design.

  • unconfigured state (not configured just yet): i believe entities should not be discovered by ROS 2 network graph, in other words we cannot see this endpoint via ros2 service list or ros2 topic list. i believe this is the expected behavior from user's perspective. but it is hard to do this, because currently once we create the entity, underlying rmw implementation creates the corresponding rmw object (e.g DDS DataWriter and DataReader). after all, those entities will be discovered in ROS 2 (RMW) network graph anyway. IMO this could be TODO for future enhancement with another PR, because this can take some time and probably new interfaces between ROS 2 and RMW state control.
  • inactive state: the entity will be discovered by ROS 2 RMW network graph, but not serviced just yet. i think this is the state that you guys need here, if i am correctly reading the discussion. in this state, the subscription (entity) is discovered by other remote nodes, so that we can see this subscription on the topic in the network. but this subscription will not take out the data from the RMW queue. again this also needs a basic design and more consideration, but i think a couple things that we can do here. the one is that when the data ready notification comes from the RMW, the subscription skip taking out the data from the queue. the other is that, adding the new state to RMW entity to keep the data in the queue, and does not interrupt at all. (the queue in RMW is managed by configured QoS setting underneath.)
  • active state: it is now time to take the data out of the RMW queue, and call the user callback to process the data as normal endpoint. once it becomes into this state, there could be multiple data in the RMW queue based on the QoS configuration such as depth and so on, so that it needs to process all queued data to call the user callback multiple times at the beginning of this state.

i think that this is doable... but i am not sure if DDS or zenoh have these managed states internally to be mapped to lifecycle entities of ROS 2. even if they dont, maybe we can land somewhere on "skip to take out the data in inactive state". i may be missing some things here, we obviously need to take some time to consider more details and design before implementation.

@Lotusymt
Copy link
Copy Markdown
Contributor Author

Thanks for the correction! I was initially suspicious that AMCL’s map_sub can’t be handled in the node's on_activate. But I looked into it again, it should be fine. Maybe I missed something, and the timeout error happened when I added buffering logic for transient-local in the subscription wrapper at that time. I’m currently not able to reliably reproduce the exact same timeout anymore.

So I think the next step would be to try again when

inactive state: the entity will be discovered by ROS 2 RMW network graph, but not serviced just yet. i think this is the state that you guys need here, if i am correctly reading the discussion. in this state, the subscription (entity) is discovered by other remote nodes, so that we can see this subscription on the topic in the network. but this subscription will not take out the data from the RMW queue. again this also needs a basic design and more consideration, but i think a couple things that we can do here. the one is that when the data ready notification comes from the RMW, the subscription skip taking out the data from the queue. the other is that, adding the new state to RMW entity to keep the data in the queue, and does not interrupt at all. (the queue in RMW is managed by configured QoS setting underneath.)

is available.

@SteveMacenski
Copy link
Copy Markdown
Member

probably something you guys need is something like ros2/rclcpp#2715?

This suffers from the same issues, no? :( It creates the subscription on creation -- so if you send a message when the node is in the inactive state, it'll be received by the callback (even though its rejected for processing, if its transient local it will not be reattempted later resulting in meaningful loss).

Though @Lotusymt note the use of rclcpp::Subscription<MessageT, AllocatorT>::handle_message(message, message_info); -- maybe this is a cleaner way?


I think you pointed out the exact thing we need in inactive. I would like to have all the interfaces exposed in inactive, but if we create the subscription, then they are then serviceable when they should not be yet. We can instead create subscriptions in the transition to active, but then we lose interospectability at inactive when I'd like to have all the interfaces created but not yet doing anything.

This doesn't just impact subscriptions, but also services. Clients and publishers are easy because they can just come up and then just check a flag that if someone asks them to do something to simply say "no". subscriptions and services need something to say "no" before we try to execute a callback.

I think as such there should be something to create an interface, have it discoverable, but not serviceable until said its ready.

the one is that when the data ready notification comes from the RMW, the subscription skip taking out the data from the queue. the other is that, adding the new state to RMW entity to keep the data in the queue, and does not interrupt at all

Either of these (and I imagine more) would work.

@fujitatomoya
Copy link
Copy Markdown

IMO (i believe everybody agrees...) this feature should be implemented in the core base classes like LifecycleSubscription, not in the Nav2. but i do not have bandwidth for this, and being based in Tokyo makes it harder to join the PMC meeting... (2AM starts... 😓 ) i think creating the thread in the discourse would be nice to get more feedback and attention from the community, there could be someone who is willing to make contribution for this. maybe GSoC project?

@SteveMacenski
Copy link
Copy Markdown
Member

OK - for now @Lotusymt I think we should create it on the active state to get past the issue (even if not ideal) for now. Do you also mind starting a thread in discourse with this? I could but I want you to get credit for uncovering this gap :-)

@Lotusymt
Copy link
Copy Markdown
Contributor Author

Lotusymt commented Mar 2, 2026

Thanks Steve! really appreciate you encouraging me to start the Discourse thread.

I submitted the topic in Discourse, but it’s currently pending moderator approval. I’ll post the link here as soon as it’s visible. If there’s anything you’d like me to adjust, please let me know.

On the PR side: I also switched the workaround to the rclcpp::Subscription<...>::handle_message(msg, info) approach you pointed out. Almost all tests are passing now. I’m down to just one remaining failure in nav2_system_tests. Hopefully I can figure that out soon!

@SteveMacenski
Copy link
Copy Markdown
Member

Great - thanks!

Lotusymt and others added 20 commits May 2, 2026 14:32
Signed-off-by: lotusymt <mengtiy5@uci.edu>

Signed-off-by: lotusymt <mengtiy5@uci.edu>
Signed-off-by: lotusymt luiseyang36@gmail.com

Signed-off-by: lotusymt <mengtiy5@uci.edu>
Signed-off-by: lotusymt <mengtiy5@uci.edu>
Signed-off-by: lotusymt <mengtiy5@uci.edu>
Removed unnecessary CMake module path and include statement.

Signed-off-by: Mengting Yang <87471734+Lotusymt@users.noreply.github.com>
Co-authored-by: Steve Macenski <stevenmacenski@gmail.com>
Signed-off-by: Mengting Yang <87471734+Lotusymt@users.noreply.github.com>
Co-authored-by: Steve Macenski <stevenmacenski@gmail.com>
Signed-off-by: Mengting Yang <87471734+Lotusymt@users.noreply.github.com>
Co-authored-by: Steve Macenski <stevenmacenski@gmail.com>
Signed-off-by: Mengting Yang <87471734+Lotusymt@users.noreply.github.com>
Signed-off-by: lotusymt <mengtiy5@uci.edu>
Signed-off-by: lotusymt <mengtiy5@uci.edu>
Signed-off-by: lotusymt <mengtiy5@uci.edu>
Signed-off-by: lotusymt <mengtiy5@uci.edu>
Signed-off-by: lotusymt <mengtiy5@uci.edu>
Signed-off-by: lotusymt <mengtiy5@uci.edu>
Signed-off-by: lotusymt <mengtiy5@uci.edu>
Signed-off-by: lotusymt <mengtiy5@uci.edu>
Per maintainer feedback on PR ros-navigation#5834 (SteveMacenski, 2026-03-13: "I'd say
all"), redesign nav2::Subscription so the underlying rclcpp::Subscription
is constructed in on_activate() and released in on_deactivate(), for all
subscriptions (not just transient_local). The previous LifecycleSubscription
override that gated handle_message() and the transient_local auto-activate
hack are removed: while inactive, no DDS endpoint exists and no callback
can fire.

- subscription.hpp: drop LifecycleSubscription, store callback/qos/options
  and a weak ref to the topics interface; build the rclcpp::Subscription
  via SubscriptionFactory inside on_activate(); reset it in on_deactivate().
- test_subscription_latched: explicitly activate the subscription before
  publishing (the old "transient_local auto-activates at init" guarantee
  is gone by design).
- Revert geojson churn in nav2_route/graphs/{aws_graph,sample_graph}.geojson
  (unrelated to this PR per maintainer review).

Signed-off-by: lotusymt <mengtiy5@uci.edu>
PR ros-navigation#5834 redesigned nav2::Subscription to construct the underlying
rclcpp::Subscription only at on_activate() and release it at
on_deactivate() (no auto-activate-on-init for transient_local). Two
production call sites still depended on the old behavior:

* StaticLayer::activate() skipped on_activate() for transient_local map
  subscriptions, relying on them being created already-active. Now they
  are not, so the topic endpoint never appeared and Costmap2DROS hung
  in tests like test_collision_checker / inflation_tests / plugin_container_tests
  waiting for the latched /map message.

* MapSaver::saveMapTopicToFile() had the same conditional skip for
  transient_local map subscriptions, which silently never delivered.

Signed-off-by: lotusymt <mengtiy5@uci.edu>
…igation#5834

Tests that built nav2::Subscriptions via test fixtures were depending on
the pre-redesign behavior where the underlying rclcpp::Subscription
existed at construction. Now subscriptions exist only after on_activate(),
which the test fixtures bypassed because they invoked the on_configure()/
on_activate() override callbacks directly rather than using the lifecycle
state machine -- so the auto-activate-if-active branch in
nav2::LifecycleNode::create_subscription never fired.

* test_costmap_filter_info_server, test_vector_object_server: explicit
  subscription_->on_activate() after construction so the tester actually
  receives the latched message the server publishes on activation.

* test_costmap_subscriber: drive the lifecycle node through
  configure() / activate() so its managed entities -- including those
  added internally by CostmapSubscriber via lc_node->create_subscription
  -- are activated together. Removes the redundant per-sub on_activate
  calls.

* test_costmap_2d_publisher: defer LayerSubscriber's executor thread
  start until after the underlying subscription has been built in
  on_activate(); also activate the LayerSubscriber before activating the
  costmap, so the subscription is discovered before mapUpdateLoop publishes
  its first latched costmap message (avoids relying on transient_local
  replay, which is mid-rewrite in rmw_fastrtps_cpp 9.4.7).

Signed-off-by: lotusymt <mengtiy5@uci.edu>
LifecycleServiceClient's constructor blocks on wait_for_service() in a
loop. If the rcl context is invalidated mid-construction (e.g. the
process receives SIGINT before the dependent service comes up), both
wait_for_service() and Rate::sleep() return immediately, which spins
the loop tight and prevents the process from exiting.

Surfaced by test_costmap_subscriber_exec: after the gtest binary now
exits in ~50ms (post-PR-ros-navigation#5834 fixes), the launch wrapper SIGINTs a
lifecycle_manager whose constructor was still in this loop, and the
ctest timeout fires while the manager refuses to die.

Add an rclcpp::ok() check to the loop condition so context shutdown
breaks construction cleanly.

Signed-off-by: lotusymt <mengtiy5@uci.edu>
@Lotusymt Lotusymt force-pushed the feature/nav2_ros_common-lifecycle-interfaces-5298 branch from 2cffbe8 to 990927f Compare May 2, 2026 21:59
Lotusymt added 3 commits May 2, 2026 17:02
Previous algorithm_build hit CircleCI's 60-minute job timeout while
still making progress (5/15 packages complete, 4 still building). All
other CI signals — core_build, jazzy, kilted, pre-commit, linters, DCO
— passed on the same code.

Signed-off-by: lotusymt <mengtiy5@uci.edu>
Round 1 (#57229): algorithm_build hit 60-min timeout at 5/15 packages,
likely cold caches.
Round 2 (#57233): algorithm_build hit 60-min timeout at 13/15 packages
(2 still building when killed) — caching from #57229 helped.
This round expects to finish: caching from #57233 should push us under
the limit.

Also retries build-docker (jazzy)/(kilted), which both failed with
GitHub-runner→archive.ubuntu.com connection timeouts (pure network
infrastructure issue, unrelated to this PR's code).

Signed-off-by: lotusymt <mengtiy5@uci.edu>
Signed-off-by: lotusymt <mengtiy5@uci.edu>
@Lotusymt Lotusymt force-pushed the feature/nav2_ros_common-lifecycle-interfaces-5298 branch from 836bf0f to 73f7ff0 Compare May 3, 2026 02:54
Lotusymt added 4 commits May 2, 2026 21:40
…uring ACTIVATING

Two related bugs surfaced in CI system tests after switching to the
create-on-activate / destroy-on-deactivate Subscription wrapper:

1. nav2::LifecycleNode::create_{subscription,publisher} only auto-activated
   when the node was already in PRIMARY_STATE_ACTIVE. Costmap filters
   create their mask subscription lazily inside filterInfoCallback, which
   fires from the parent costmap node's internal executor *during* the
   on_activate transition (state == TRANSITION_STATE_ACTIVATING). The
   wrapper was registered as a managed entity but never received
   on_activate(), so the underlying rclcpp subscription was never created
   and the filter mask never arrived — failing test_keepout_filter,
   test_speed_filter_global, test_speed_filter_local.

   Extend the auto-activate predicate to also fire while the node is
   ACTIVATING. The wrapper's own on_activate() is idempotent so a later
   explicit activation by user code remains a no-op.

2. nav2_costmap_2d::CostmapSubscriber owns two nav2::Subscription wrappers
   that are added as managed entities of its LifecycleNode parent at
   construction time, but it exposed no way to activate them. RouteServer
   constructs a CostmapSubscriber in on_configure and then never wired
   it up — the costmap topic was never subscribed, leaving "No costmap
   yet received!" in test_route.

   Add on_activate()/on_deactivate() to CostmapSubscriber that fan out to
   the wrapped subscriptions, and call them from RouteServer's lifecycle
   callbacks.

Signed-off-by: lotusymt <mengtiy5@uci.edu>
…pSubscribers

After the create-on-activate Subscription wrapper, two more places were
silently leaving lifecycle subscriptions unactivated:

1. nav2_behaviors::BehaviorServer creates local_costmap_sub_ and
   global_costmap_sub_ in on_configure (CostmapSubscribers wrapping
   nav2::Subscription) but only activated the *footprint* subscriptions
   in on_activate. Test assisted_teleop_behavior surfaced this as a
   stream of "Costmap is not available" errors. Activate (and
   symmetrically deactivate) the costmap subscribers.

2. nav2_route route_server's plugin-owned CostmapSubscribers (the ones
   CollisionMonitor and CostmapScorer allocate themselves when their
   costmap topic differs from the server topic) had no path to be
   activated — RouteServer only owns the shared subscriber. Surfaced as
   test_route's "Collision Monitor could not obtain a costmap from topic:
   local_costmap/costmap_raw".

   Add virtual activate()/deactivate() hooks to RouteOperation and
   EdgeCostFunction (default no-op), override them in CollisionMonitor
   and CostmapScorer to fan out to the subscriber they own (tracked via
   owns_costmap_subscriber_), and forward through OperationsManager,
   EdgeScorer, RouteTracker, and RoutePlanner so that
   RouteServer::on_activate/on_deactivate wakes the whole plugin chain.

Signed-off-by: lotusymt <mengtiy5@uci.edu>
ament_uncrustify flagged the single-line for-loop bodies introduced in
the previous commit. Expand them to a multi-line form.

Signed-off-by: lotusymt <mengtiy5@uci.edu>
… setup

The create-on-activate Subscription wrapper left LoopbackSimulator's
initial_pose_sub_ and cmd_vel_sub_ dormant: both are created in
on_configure (state CONFIGURING) so the framework auto-activate doesn't
fire, and on_activate didn't call on_activate() on them explicitly.
Symptom: test_loopback_simulator's InitialPoseSetsMapToOdom /
CmdVelMovesRobot / OdometryContainsTwist / RotationUpdatesYaw failed
because the simulator never received either /initialpose or /cmd_vel.
Add the missing on_activate/on_deactivate fan-out.

ClockPublisherTest used a raw nav2::LifecycleNode that was never
configured/activated, so subscriptions created via
node_->create_subscription() now stay inactive and miss every /clock
message. Drive the test node to ACTIVE in SetUp() so subscriptions
auto-activate as designed.

Signed-off-by: lotusymt <mengtiy5@uci.edu>
@Lotusymt
Copy link
Copy Markdown
Contributor Author

Lotusymt commented May 4, 2026

@SteveMacenski I am very sorry for the delay, it was more complicated than I expected and I was kind of occupied by something else. Please take a look when you have time and let me know if there's anything wrong. Thanks :D

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants