Skip to content

feat(logger): Minor cleanup / improvement to impl for rate limiting - #685

Merged
finger563 merged 2 commits into
mainfrom
feat/logger-cleanup
Aug 1, 2026
Merged

feat(logger): Minor cleanup / improvement to impl for rate limiting#685
finger563 merged 2 commits into
mainfrom
feat/logger-cleanup

Conversation

@finger563

@finger563 finger563 commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Description

Reworks the Logger rate-limiting implementation:

  • Monotonic clock. Switches the rate-limit clock from std::chrono::high_resolution_clock to std::chrono::steady_clock, exposed as a new Logger::clock_t alias. steady_clock is guaranteed monotonic, so rate limiting no longer misbehaves if the underlying clock jumps (high_resolution_clock is allowed to alias system_clock).
  • Thread-safe rate-limit state. Stores last_print_ as a std::atomic<clock_t::rep> timestamp, so the check-and-update inside the *_rate_limited() helpers is free of the data race / UB that occurred when a logger was used from multiple tasks.
  • Native durations. Stores rate_limit_ in the clock's native clock_t::duration (converted once in the constructor and set_rate_limit()) and compares in that type, removing the per-call duration<float> casts and their precision loss. get_rate_limit() still returns std::chrono::duration<float>, so the public API is unchanged.

Motivation and Context

The rate-limited log helpers computed elapsed time with high_resolution_clock (not guaranteed monotonic) and read/updated last_print_ without synchronization — a data race when logging from multiple tasks that could also miss or duplicate prints. Each call additionally re-cast durations to duration<float>. These changes make the rate limiter correct under concurrency, robust against non-monotonic clocks, and free of unnecessary conversions, without changing the public API.

How has this been tested?

  • idf.py build on the logger example builds clean on ESP32 (also confirms the 64-bit std::atomic<clock_t::rep> links on the 32-bit target).
  • Repo static-analysis (cppcheck) is clean on logger.hpp.
  • The existing logger example exercises the *_rate_limited() paths.

Screenshots (if appropriate, e.g. schematic, board, console logs, lab pictures):

N/A

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation Update
  • Hardware (schematic, board, system design) change
  • Software change

Checklist:

  • My change requires a change to the documentation.
  • I have added / updated the documentation related to this change via either README or WIKI

Software

  • I have added tests to cover my changes.
  • I have updated the .github/workflows/build.yml file to add my new test to the automated cloud build github action.
  • All new and existing tests passed.
  • My code follows the code style of this project.

Copilot AI review requested due to automatic review settings August 1, 2026 03:04
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

✅Static analysis result - no issues found! ✅

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Updates the logger’s rate-limiting implementation to use a monotonic clock and make time comparisons explicit/consistent.

Changes:

  • Introduces a steady_clock alias for rate-limiting (clock_t).
  • Switches rate-limit timing from high_resolution_clock to steady_clock.
  • Casts elapsed time to std::chrono::duration<float> before comparing with rate_limit_.

Comment thread components/logger/include/logger.hpp Outdated
Comment thread components/logger/include/logger.hpp Outdated
Comment thread components/logger/include/logger.hpp Outdated
Address review feedback on the rate-limiting impl:
- Store last_print_ as a std::atomic<clock_t::rep> timestamp so the
  check-and-update in the *_rate_limited() methods is free of the data race /
  UB that occurred when logging from multiple threads.
- Store rate_limit_ in the clock's native clock_t::duration (converted once in
  the constructor / set_rate_limit) and compare in that type, removing the
  per-call duration<float> casts and their precision loss.
- Drop the redundant Logger:: qualifier on clock_t inside the class.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 1, 2026 05:08
@finger563
finger563 merged commit b77378b into main Aug 1, 2026
136 of 137 checks passed
@finger563
finger563 deleted the feat/logger-cleanup branch August 1, 2026 05:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Suppressed comments (4)

components/logger/include/logger.hpp:366

  • With last_print_ defaulting to 0, the first call after startup can be suppressed for up to rate_limit_ (since now - 0 may be < rate_limit_). If the intent is “first call prints, subsequent calls are limited”, treat 0 as a sentinel meaning “never printed yet” and bypass the duration check in that case.
    if (rate_limit_ > clock_t::duration::zero()) {
      const auto now = clock_t::now().time_since_epoch().count();
      // last_print_ is an atomic timestamp (clock_t rep) so the check/update is
      // free of data races when logging from multiple threads.
      if (clock_t::duration(now - last_print_.load()) < rate_limit_)

components/logger/include/logger.hpp:390

  • With last_print_ defaulting to 0, the first call after startup can be suppressed for up to rate_limit_ (since now - 0 may be < rate_limit_). If the intent is “first call prints, subsequent calls are limited”, treat 0 as a sentinel meaning “never printed yet” and bypass the duration check in that case.
    if (rate_limit_ > clock_t::duration::zero()) {
      const auto now = clock_t::now().time_since_epoch().count();
      // last_print_ is an atomic timestamp (clock_t rep) so the check/update is
      // free of data races when logging from multiple threads.
      if (clock_t::duration(now - last_print_.load()) < rate_limit_)

components/logger/include/logger.hpp:414

  • With last_print_ defaulting to 0, the first call after startup can be suppressed for up to rate_limit_ (since now - 0 may be < rate_limit_). If the intent is “first call prints, subsequent calls are limited”, treat 0 as a sentinel meaning “never printed yet” and bypass the duration check in that case.
    if (rate_limit_ > clock_t::duration::zero()) {
      const auto now = clock_t::now().time_since_epoch().count();
      // last_print_ is an atomic timestamp (clock_t rep) so the check/update is
      // free of data races when logging from multiple threads.
      if (clock_t::duration(now - last_print_.load()) < rate_limit_)

components/logger/include/logger.hpp:342

  • With last_print_ defaulting to 0, the first call after startup can be suppressed for up to rate_limit_ (since now - 0 may be < rate_limit_). If the intent is “first call prints, subsequent calls are limited”, treat 0 as a sentinel meaning “never printed yet” and bypass the duration check in that case.

This issue also appears in the following locations of the same file:

  • line 362
  • line 386
  • line 410
    if (rate_limit_ > clock_t::duration::zero()) {
      const auto now = clock_t::now().time_since_epoch().count();
      // last_print_ is an atomic timestamp (clock_t rep) so the check/update is
      // free of data races when logging from multiple threads.
      if (clock_t::duration(now - last_print_.load()) < rate_limit_)

Comment on lines +222 to +224
void set_rate_limit(const std::chrono::duration<float> rate_limit) {
rate_limit_ = std::chrono::duration_cast<clock_t::duration>(rate_limit);
}
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.

2 participants