Skip to content

Keep the RT loop period on Windows when it saves power, and warn when it falls #163

Description

@markosankovic

User story

As an engineer running Motion Master on a Windows PC,
I want the RT loop to keep its period when Windows saves power, and to be told when it does not,
so that a slow loop cannot fail file reads, abort a procedure or trip the drive's watchdog without a trace.

Context

A loop that waits with timeSetEvent depends on the timer resolution the process asks for with timeBeginPeriod. Since Windows 11, Windows does not guarantee that resolution to a window-owning process that is minimised, covered or otherwise unseen (timeBeginPeriod). Such a loop then falls to the default timer tick of 15.625 ms, about 64 cycles per second.

Motion Master waits differently. libs/core/cyclic_timer_windows.cc arms a one-shot waitable timer created with CREATE_WAITABLE_TIMER_HIGH_RESOLUTION (Windows 10 1803 or newer) each cycle, on a grid kept with QueryPerformanceCounter, and never calls timeBeginPeriod. Such a timer has its own resolution (CreateWaitableTimerExW), so that cause most likely does not apply. No Microsoft documentation says how this timer behaves under power throttling, and nothing has measured it.

Five gaps remain:

  1. The timer is not checked. CreateWaitableTimerExW returns NULL on failure, for example on Windows before 10 1803, and CyclicTimer keeps the NULL handle. SetWaitableTimer then fails and WaitForSingleObject returns at once, so the RT loop runs with no wait at all.
  2. No power-throttling opt-out. Nothing calls SetProcessInformation(ProcessPowerThrottling, ...). EcoQoS lowers the CPU frequency or moves the process to efficiency cores, whatever timer it uses. With PROCESS_POWER_THROTTLING_EXECUTION_SPEED in ControlMask and a StateMask of 0 a process asks for HighQoS, and with PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION it asks Windows to always honour its timer resolution requests.
  3. A low cycle rate is silent. GameLoop counts skipped cycles and GET /api/game-loop reports them, but nothing logs or notifies. A loop running at a fraction of its rate shows nowhere unless somebody opens that page.
  4. Not measured on Windows 11 with the window minimised or covered, on battery saver, or in Efficiency mode.
  5. An outdated comment. cyclic_timer_windows.cc says timeBeginPeriod is "a system-wide change that affects all processes". Since Windows 10, version 2004, it no longer changes the global timer resolution (timeBeginPeriod). The reason to avoid it is now the Windows 11 rule above.

Acceptance criteria

  • CyclicTimer on Windows checks the handle CreateWaitableTimerExW returns. On failure it does not run the loop without a wait: it fails at startup with a message that names the Windows version requirement, or falls back to a timer that waits.
  • On Windows, startup calls SetProcessInformation(ProcessPowerThrottling, ...) with PROCESS_POWER_THROTTLING_EXECUTION_SPEED and PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION in ControlMask and a StateMask of 0, and logs a warning when the call fails.
  • A NotificationBus source warns when the loop skips cycles at a sustained rate, with the measured cycle rate, and says again when the rate is back. It logs as well as notifies, like the short working counter source in apps/motion_master/bus_health_source.{h,cc}. The threshold is stated, and a short burst at startup or after a period change does not trigger it.
  • The comment in cyclic_timer_windows.cc says why timeBeginPeriod is avoided as Windows 10 2004 and Windows 11 have it.
  • On a Windows 11 laptop, the cycle rate is measured with the window of the Console minimised and covered, on battery saver, and with Efficiency mode set on the process in Task Manager, before and after the opt-out. The results go in this issue.

Out of scope

  • Real-time priority on Windows. libs/core/realtime.{h,cc} has no Windows path on purpose.

References

  • libs/core/cyclic_timer_windows.cc, libs/core/cyclic_timer.h, libs/core/realtime.{h,cc}
  • apps/motion_master/game_loop.h, apps/motion_master/bus_health_source.{h,cc}

Activity

  1. markosankovic commented on Oct 29, 2026

    @markosankovic
    ContributorAuthor

    Decisions (2026-10-29)

    1. A timer that cannot be created stops the start. No fallback, because the only fallback is a coarse timer that brings back the slow cycle. The message names the requirement, Windows 10 1803 or newer.
    2. The power-throttling opt-out goes beside setRealtimePriority in libs/core/realtime.cc. A failed call is a warning, not fatal.
    3. A low cycle rate is logged, not notified. No NotificationBus event: the warning and the line when the rate is back go to the log only. The acceptance criterion that names a NotificationBus source is replaced by this.
    4. The comment in cyclic_timer_windows.cc is corrected.
    5. The measurement on Windows 11 is for later.
  2. added a commit that references this issue on Oct 29, 2026
    207aab3
  3. markosankovic commented on Oct 29, 2026

    @markosankovic
    ContributorAuthor

    Reopened: the measurement on a Windows 11 laptop is still open. #164 closed it through a closing keyword in a commit of its branch.

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

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions