Skip to content

refactor(timers): unify the duplicated ClockConditionalVariable implementations - #3401

Open
skyegalaxy wants to merge 9 commits into
rollingfrom
skyegalaxy/dedup-clock-conditional-variable
Open

skyegalaxy wants to merge 9 commits into
rollingfrom
skyegalaxy/dedup-clock-conditional-variable

Conversation

@skyegalaxy

@skyegalaxy skyegalaxy commented Nov 6, 2026 •

Copy link
Copy Markdown
Member

Description

The timer manager in #3097 introduced a variant of ClockWaiter and ClockConditionalVariable which could accept a clock on its wait_until calls instead of at construction. This PR unifies both ClockWaiter and ClockConditionalVariable under one set which can do both, and removes the duplicates in timer_manager.

  • ClockWaiter and ClockConditionalVariable can now accept a null clock at construction, and both gain a wait_until(lock, clock, until, pred) overload. The existing overloads without a clock are unchanged.
  • The clock given to a wait is only used for that call and is never stored, so the timer queue doesn't keep a timer's clock alive.
  • ClockConditionalVariable keeps one ClockWaiter for its whole lifetime, so notify_one() takes no lock and a wait performs no allocation.
  • TimerQueue uses rclcpp::ClockConditionalVariable directly, now constructed with a null clock.
  • A wait with no clock (none given at construction or on the call), or a null clock passed to the new overload, throws std::runtime_error. Before, waiting without a clock dereferenced a null pointer.
  • If the timer thread's wait throws while the context is still valid, it now logs an error before stopping that queue's timers. Before, it stopped them silently. During shutdown it stays quiet, as before.

Is this user-facing behavior change?

rclcpp::ClockConditionalVariable::wait_until() used to return true unconditionally. It now returns false if the context was shut down before the call. Callers that ignored the return value need no change.

A wait whose until doesn't match the clock's type now throws before it starts, on every clock type.

Did you use Generative AI?

Yes, Claude Opus 5.5

Additional Information

Skyler Medeiros added 4 commits November 6, 2026 10:06
Signed-off-by: Skyler Medeiros <skye@polymathrobotics.com>
Signed-off-by: Skyler Medeiros <skye@polymathrobotics.com>
Signed-off-by: Skyler Medeiros <skye@polymathrobotics.com>
…alVariable

Signed-off-by: Skyler Medeiros <skye@polymathrobotics.com>
@github-actions

github-actions Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

ABI Compliance Check

✅ Verdict: compatible (additions only)

Library Verdict Summary
libcomponent_manager.so ✅ compatible No ABI changes detected.
librclcpp.so ✅ additions-only ABI changed but only with additions (backward-compatible).
librclcpp_action.so ✅ compatible No ABI changes detected.
librclcpp_lifecycle.so ✅ compatible No ABI changes detected.
✅ libcomponent_manager.so — full abidiff report

Compared:

  • Base: lib-base/libcomponent_manager.so
  • Head: lib-pr/libcomponent_manager.so @ 626453d
(empty report — no differences printed by abidiff)
✅ librclcpp.so — full abidiff report

Compared:

  • Base: lib-base/librclcpp.so
  • Head: lib-pr/librclcpp.so @ 626453d
Functions changes summary: 0 Removed (30 filtered out), 0 Changed (134 filtered out), 2 Added (13 filtered out) functions
Variables changes summary: 0 Removed, 0 Changed, 0 Added variable

2 Added functions:

  [A] 'method bool rclcpp::ClockConditionalVariable::wait_until(std::unique_lock<std::mutex>&, const rclcpp::Clock::SharedPtr&, const rclcpp::Time&, const std::function<bool()>&)'    {_ZN6rclcpp24ClockConditionalVariable10wait_untilERSt11unique_lockISt5mutexERKSt10shared_ptrINS_5ClockEERKNS_4TimeERKSt8functionIFbvEE}
  [A] 'method bool rclcpp::ClockWaiter::wait_until(std::unique_lock<std::mutex>&, const rclcpp::Clock::SharedPtr&, const rclcpp::Time&, const std::function<bool()>&)'    {_ZN6rclcpp11ClockWaiter10wait_untilERSt11unique_lockISt5mutexERKSt10shared_ptrINS_5ClockEERKNS_4TimeERKSt8functionIFbvEE}


✅ librclcpp_action.so — full abidiff report

Compared:

  • Base: lib-base/librclcpp_action.so
  • Head: lib-pr/librclcpp_action.so @ 626453d
(empty report — no differences printed by abidiff)
✅ librclcpp_lifecycle.so — full abidiff report

Compared:

  • Base: lib-base/librclcpp_lifecycle.so
  • Head: lib-pr/librclcpp_lifecycle.so @ 626453d
(empty report — no differences printed by abidiff)

Updated for commit 626453d · check run · suppressions: /home/runner/work/_temp/ros2-abi-suppressions.txt

@skyegalaxy

Copy link
Copy Markdown
Member Author

Pulls: #3401
Gist: https://gist.githubusercontent.com/skyegalaxy/321c7098da935e031b380b66ed453c47/raw/c23e34775d5f4767cc08708d4867337f30471401/ros2.repos
BUILD args: --packages-above-and-dependencies rclcpp
TEST args: --packages-above rclcpp
ROS Distro: rolling
Job: ci_launcher
ci_launcher ran: https://ci.ros2.org/job/ci_launcher/21046

  • Linux Build Status
  • Linux-aarch64 Build Status
  • Linux-rhel Build Status
  • Windows Build Status

Comment thread rclcpp/src/rclcpp/clock.cpp Outdated
const rclcpp::Time & abs_time, const std::function<bool ()> & pred)
{
switch(clock_->get_clock_type()) {
if (abs_time.get_clock_type() != clock->get_clock_type()) {

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.

With a RCL_ROS_TIME clock and sim time off (and with a RCL_SYSTEM_TIME clock), the old code never compared abs_time to the clock, so wait_until(lk, rclcpp::Time(sec, nsec), pred) worked. rclcpp::Time defaults to system time, so that call now throws. Only the steady path and the active-ROS path threw before, through Time::operator- and operator>=.

Could we keep the old behavior for those paths, or call this out as a breaking change?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

reverted to the old behavior in 708ac97

Comment thread rclcpp/include/rclcpp/clock.hpp Outdated
* the clocks involved may be destroyed between waits.
*/
RCLCPP_PUBLIC
explicit ClockConditionalVariable(const rclcpp::Context::SharedPtr & context);

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.

This overload makes ClockConditionalVariable cv(nullptr); and std::make_unique<ClockConditionalVariable>(nullptr) ambiguous, because nullptr converts to both shared_ptr types.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

rclcpp::Clock::SharedPtr used_clock_for_timers;

ClockConditionalVariable clock_waiter;
rclcpp::ClockConditionalVariable clock_waiter;

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.

Switching to rclcpp::ClockConditionalVariable means wait_until can now throw for reasons other than the shutdown race: a clock type mismatch, or a null clock. The existing catch (const std::runtime_error &) in the timer thread (around L392) sets running = false without logging, so either of these would stop every timer in the queue without a message. Worth narrowing that catch, or at least logging.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

added logging for when a wait fails af98349

Skyler Medeiros added 3 commits November 9, 2026 12:11
…system time waits

Signed-off-by: Skyler Medeiros <skye@polymathrobotics.com>
… constructor

Signed-off-by: Skyler Medeiros <skye@polymathrobotics.com>
Signed-off-by: Skyler Medeiros <skye@polymathrobotics.com>
@skyegalaxy
skyegalaxy requested a review from mjcarroll November 9, 2026 20:29
Comment thread rclcpp/include/rclcpp/clock.hpp Outdated
Comment on lines +280 to +281
* to wait on, for example because the clocks involved may be destroyed
* between waits.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

add one space for alignment?

Suggested change
* to wait on, for example because the clocks involved may be destroyed
* between waits.
* to wait on, for example because the clocks involved may be destroyed
* between waits.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

fixed in 14fe72c

} catch (const std::runtime_error & e) {
// there is a race on shutdown, were the context may
// become invalid, while we call sleep_until
if (rclcpp::ok()) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this means this must be always with default context? imo it shoule be more like the following with using the stored context in TimerQueue? otherwise, using explicit context in the application will fail here.

Suggested change
if (rclcpp::ok()) {
if (rclcpp::ok(context)) {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Skyler Medeiros added 2 commits November 10, 2026 09:27
Signed-off-by: Skyler Medeiros <skye@polymathrobotics.com>
…timer wait failure

Signed-off-by: Skyler Medeiros <skye@polymathrobotics.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants