Skip to content

Setting the system clock backwards silently stops the action for that long (interval timed with wall-clock DateTimeOffset.Now) #54

Description

@matt-edmondson

What's wrong

The polling loop decides whether to run with this check (IntervalAction/IntervalAction.cs:228):

DateTimeOffset.Now - lastRunTime > ActionInterval

LastRunTime is set from DateTimeOffset.Now (lines 236 and 246). Wall-clock time is not monotonic: NTP corrections, manual clock changes and VM resume can all move it backwards.

Failure scenario

  1. ActionInterval = 1s, and the action has just run.
  2. The system clock is set back 1 hour.
  3. Now - LastRunTime is now about −1h, which is never > 1s. The action does not run for about an hour, and nothing is thrown or logged.

A forward jump has the opposite effect: one run happens early. DST changes are not affected, because DateTimeOffset compares UTC instants; only real system clock changes are.

Why it matters

The library exists to run an action every N seconds, for example polling, heartbeats or autosave. A long silent gap after a clock sync is a hard-to-diagnose outage.

Suggested fix

Time intervals with a monotonic source: store Stopwatch.GetTimestamp() (or Environment.TickCount64) for the last run and compare elapsed ticks against ActionInterval. LastRunTime can remain as a wall-clock value for display if it is part of the public surface. It just shouldn't drive scheduling.

Acceptance criteria

The scheduling decision no longer depends on DateTimeOffset.Now. A test that injects a clock (or the monotonic abstraction, e.g. TimeProvider) shows that a backwards wall-clock jump does not delay the next run.

Activity

  1. matt-edmondson commented on Sep 26, 2026

    @matt-edmondson
    ContributorAuthor

    Triage

    Next steps: Drive scheduling from TimeProvider.GetTimestamp()/GetElapsedTime(), using Microsoft.Bcl.TimeProvider on older TFMs. This gives a monotonic clock, and tests can inject a FakeTimeProvider that jumps the wall clock backwards. Keep LastRunTime as a wall-clock value for display only.


    Generated by Claude Code

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

Metadata

Metadata

Labels

bugSomething isn't workingreadyFully specified; implement as written

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions