Zephyr Timer Status: Decide Whether Expirations Are Counted, Consumed or Missed

Zephyr timer status is a consumable expiration count. Reading it clears it. If a worker, diagnostic command and expiry callback all read the same timer’s status, they divide or erase each other’s evidence. Assign one status consumer, then decide how that consumer converts a count into application work. A value greater than one is useful backlog evidence; it is not automatically a command to replay every missed physical action.

This guide covers upstream Zephyr 3.7.0 kernel timers with CONFIG_MULTITHREADING=y, ordinary system-clock-driven expiration and one designated status consumer. It excludes no-multithreading implementation branches and does not claim a hardware timing bound. The test sequences are proposed; no Zephyr target runtime was executed.

Identify the destructive observations

k_timer_status_get() returns the accumulated status and resets it. k_timer_status_sync() also consumes the status, but can first block until an expiration or stop. The versioned timer guide consequently recommends one status reader and only one synchronizing thread per timer.

Remaining time is a different observation. A timer can have zero unread status while still running toward its next expiration. It can also be stopped while retaining a nonzero status from an earlier expiration. Neither “status zero” nor “remaining time zero” alone tells the entire lifecycle story.

Three expirations increment status from zero to three; a single owner reads three and clears to zero, then exports an application snapshot to diagnostics and applies a bounded catch-up, coalescing or fault policy.
Read-and-clear has one owner. Diagnostics observe the owner’s exported information instead of consuming the timer’s count again.

Use the lifecycle table during diagnosis

Event Status consequence Interpretation
Initialize Zero No expiration history exists yet
Start or restart normally Reset to zero Old unread count is discarded
Expiration handled Increment One more kernel expiration has been recorded
Status get Return count, then clear A destructive sample
Status sync returns Return count, then clear May follow expiration or stop
Stop a running timer Existing status retained Stopping does not synthesize an expiration

The tagged API documents the synchronization boundary: if status is already nonzero or the timer is already stopped, the caller need not wait. A zero synchronization result can therefore mean that the timer stopped without an unread expiration. It is not equivalent to one completed periodic interval. Interrupt handlers must not call the blocking synchronization API.

Keep the start/restart operation in the same ownership review. Restarting to adjust the schedule can erase a count that the worker has not yet consumed. If that history matters, coordinate schedule changes with the status owner and preserve the required application information before restarting. Do not assume the kernel maintains a separate lifetime expiration total.

Find hidden readers before changing the period

The 3.7.0 implementation increments status before invoking the expiry function. A callback that reads status can consume the count before a waiting worker resumes. A shell command that “just displays status” has the same destructive effect. Search for every get and sync call on the timer, including callbacks and monitoring helpers.

Have the designated owner export separate application counters: count returned by the most recent read, cumulative consumed expirations, work accepted, work completed and expirations intentionally coalesced or rejected. Synchronize access to that exported data using the application’s appropriate mechanism. Do not expose the raw status-reading API as a supposedly passive monitoring endpoint.

The status field and return value are 32-bit unsigned quantities in this release. It is not an unbounded historical counter. Choose a sampling and fault policy that prevents a plausible observation gap from wrapping the count, and use an adequately sized application total if long-term accounting is needed. Counter arithmetic alone still cannot reveal what happened before a restart cleared history.

Choose what a backlog means

  • Bounded catch-up: when each expiration represents independent logical work that remains meaningful, accept a limited number of jobs and explicitly account for the remainder. Do not let a large count monopolize the worker indefinitely.
  • Coalescing: when the job refreshes current state, perform one refresh and record the other expirations as coalesced. Repeating a current sensor read cannot reconstruct samples that were never captured.
  • Fault response: when any backlog violates the service requirement, retain the count and timing context, then enter the application’s defined degraded or recovery path.

These are application policies, not timer options. Record which policy applies before testing. A count of three does not establish exactly how late the worker was, because clock precision, interrupt handling, schedule changes and other readers influence the observation. Use independently captured timestamps when assessing latency; keep count evidence and elapsed-time evidence distinct.

A hypothetical owner test

Suppose a maintenance worker uses a periodic timer to request a status refresh. In a controlled test, allow three expiration handlers to run before the sole worker reads status. Expect a returned count of three, followed by zero on an immediate second read when no further expiration intervenes. Under a coalescing policy, the worker should report one refresh and two intentionally coalesced intervals.

Next introduce a diagnostic read before the worker. The worker’s changed count demonstrates why this is not a passive observer. Remove that read and instead inspect the exported application counters. Repeat with an expiry callback read to exercise the increment-before-callback ordering.

Complete the matrix with stop before first expiration, stop after an unread expiration, restart with unread status and one-shot expiration. Explicitly establish event order in the harness rather than guessing from sleeps. Check synchronization results and application actions independently: a stopped timer returning zero must not trigger a fabricated periodic job.

Finally, measure the real worker under target load, without adding blocking work to the expiry callback. The kernel expiry callback runs in system-clock interrupt context; stopping callbacks follow the stopping call’s context. Keep their execution requirements separate. A useful qualification report explains who consumed each count and what work policy followed, while reserving latency claims for actual target measurements.

Similar Posts