Skip to content

rmw_wait rounds its timeout to whole milliseconds: 1 kHz timers skip periods and short waits time out early #82

Closed

Description

@benaliabderrahmane

Generated by Generative AI

Claude Code (Claude Opus 5.5)

Operating System:

Linux 6.8.0-142-generic #142-Ubuntu SMP PREEMPT_DYNAMIC x86_64 GNU/Linux

ROS 2 distro:

jazzy

rmw_unix_socket_cpp version or commit hash:

devel @ 6d9793c

Client library (if applicable):

rclcpp

Executor (if applicable):

SingleThreadedExecutor

Does the same reproducer work with another RMW?

Yes — it works with another RMW, so the bug looks specific to rmw_unix_socket_cpp

QoS profile (if applicable):

No response

'ros2 doctor --report' output

No response

Steps to reproduce issue

  1. Run a node with a 1 kHz timer (create_wall_timer(1ms)) that counts its callbacks for 30 s, with RMW_IMPLEMENTATION=rmw_unix_socket_cpp.
  2. Or call rmw_wait directly on a guard condition that is never triggered, with a 0.3 ms and then a 1.7 ms timeout, and time each call.

Expected behavior

  • About 30 000 timer callbacks in 30 s, as with Fast DDS, Cyclone DDS and Zenoh (29 998 to 30 001).
  • rmw_wait returns RMW_RET_TIMEOUT no earlier than the timeout, and shortly after it.

Actual behavior

  • 28 174 of 30 000 callbacks: about 6 % of the periods are skipped (29 622 with SCHED_FIFO).
  • A 0.3 ms wait takes about 1.06 ms.
  • A 1.7 ms wait returns RMW_RET_TIMEOUT after about 1.06 ms, before its deadline (20 of 20 tries).
  • Knock-on effects: a 1 kHz request/reply loop between two processes loses 2–7 % of its replies, and the iRobot cli_srv 10 B benchmark completes only 35 310 of 40 000 calls in 20 s.

Additional information

Cause: src/rmw_wait.cpp converts the timeout to whole milliseconds for epoll_wait, in three places:

  • the caller's timeout is truncated (nsec / 1000000), and anything under 1 ms becomes 1 ms ("At least 1ms");
  • the deadline is computed from that rounded value, which is why a 1.7 ms wait ends after 1 ms;
  • each time the wait re-blocks, the remaining time is rounded up to the next millisecond.

rclcpp passes the time until the next timer with nanosecond resolution, so a 1 kHz timer almost always asks for less than 1 ms and wakes up late.

PR #8 changed this code for infinite timeouts and kept the <1ms → 1ms rounding on purpose. This issue is about that rounding.

Proposed fix: keep the timeout in nanoseconds and block with epoll_pwait2() (Linux 5.11, glibc 2.35). Where it isn't available (older glibc, ENOSYS, or EPERM from a seccomp profile), fall back to epoll_wait() with the remaining time rounded up.

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

Metadata

Metadata

Labels

P1NextbugSomething isn't working

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions