Linux timerfd After a Clock Step: Handle Cancelled Absolute Timers Explicitly
For an absolute wall-clock timer, treat ECANCELED as a request to reconsider the calendar deadline after a discontinuous clock change. It is not an expiration count and should not trigger the scheduled action automatically. Record the scheduling generation, obtain the current clock context, apply the missed-deadline policy and rearm deliberately.
This guide uses Linux man-pages 6.12 and the timerfd interfaces on modern Linux. The cancellation mechanism applies when TFD_TIMER_CANCEL_ON_SET accompanies TFD_TIMER_ABSTIME on CLOCK_REALTIME or CLOCK_REALTIME_ALARM. It is not a generic notification for every clock adjustment or every timer type. No clock was changed and no timerfd test was executed for this article.
Write down what the deadline means
A deadline such as “run at 03:00 UTC” has different semantics from “retry after 30 elapsed seconds.” Before choosing timer flags, decide which meaning the application promises and what should happen if the clock jumps past a calendar event or backward across one already performed.
The timerfd documentation distinguishes CLOCK_MONOTONIC, which excludes suspended time, from CLOCK_BOOTTIME, which includes it. CLOCK_REALTIME can change discontinuously. Alarm variants additionally support waking a suspended system and require the documented capability; merely choosing an ordinary boottime timer does not establish a wake-from-suspend guarantee.
Give each timer a generation and intended action identity. Keep “last action committed” separate from “timer last armed.” If time moves backward, an old calendar deadline may become future again, but that does not authorize repeating an irreversible action without the product’s explicit duplicate policy.

Review clock and arming flags together
| Timer configuration | Clock-change interpretation |
|---|---|
| Monotonic elapsed deadline | Wall-clock steps do not redefine its clock domain; separately decide whether suspend should count. |
| Boottime elapsed deadline | Elapsed scheduling includes suspend; wake capability is a separate choice. |
| Absolute realtime, cancellation enabled | A discontinuous realtime change can make a current or future read fail with ECANCELED. |
| Absolute realtime, cancellation omitted | The deadline remains tied to realtime; special backward-step read behavior requires handling. |
TFD_NONBLOCK and TFD_CLOEXEC are creation flags. TFD_TIMER_ABSTIME and TFD_TIMER_CANCEL_ON_SET belong to timerfd_settime. Mixing these flag domains obscures both review and error diagnosis. A zero it_value disarms the timer even when absolute mode is selected; it is not an immediate absolute deadline at the epoch.
The first expiration and periodic interval are separate fields. Decide whether missed periodic occurrences should be aggregated, skipped or replayed under an explicit bound. The kernel’s expiration count does not decide the application’s recovery workload or whether multiple device actions are safe.
Use a complete read-result decoder
A successful timerfd read returns an eight-byte unsigned expiration count in native byte order. Read into a uint64_t-sized object and verify the byte count before using it. The count covers expirations since the relevant prior read or timer setting; it is not an exact timestamp for each occurrence.
- Eight bytes: validate the count and apply the current generation’s expiration policy.
- EAGAIN on a nonblocking descriptor: no expiration is available now; retain the scheduled state.
- EINTR: follow the interruption policy without interpreting stale count storage.
- ECANCELED: invalidate the prior wall-clock scheduling assumption and enter reconciliation.
- Zero bytes: handle the documented absolute-realtime backward-step case rather than declaring a peer-style EOF.
- Other errors or an invalid buffer size: diagnose the API usage or descriptor lifecycle explicitly.
The zero-byte case is narrow but important. Without cancellation enabled, a discontinuous negative realtime change after expiration but before read may make read return zero. A generic descriptor wrapper that treats every zero-length read as permanent closure can therefore destroy a still-valid timer. Keep timer descriptors typed in the dispatcher.
Reconcile cancellation without hiding its cause
For a hypothetical daily maintenance reminder, cancellation should first freeze the old callback generation. Read current realtime and the scheduler’s time-quality state, then decide whether today’s reminder is still future, overdue, already performed or unresolved. The policy might perform an overdue harmless reminder once or skip it; that is an application decision, not a timerfd requirement.
After choosing the next deadline, rearm with the intended flags and record the new generation. If a queued event references the retired generation, discard its authority to start work. A wall-clock correction and a configuration change can race, so serialize the policy update with timer replacement rather than letting two callbacks independently rearm the same descriptor.
Do not describe cancellation as proof of a particular correction magnitude or source. It indicates the documented discontinuous-change condition. A daemon’s gradual rate adjustment is a different mechanism; cancellation is not a complete clock-quality monitor.
Handle the surprising rearm result
The timerfd notes document a specific sequence: an absolute cancelable realtime timer experiences a discontinuous change, then the caller invokes timerfd_settime again without first reading the cancellation. That call can return ECANCELED while successfully applying the new timer settings.
This means a blanket “every error leaves the previous timer unchanged” rule is false for that case. Record that the new settings were applied while the earlier schedule was invalidated, and reconcile the application state accordingly. Avoid an uncontrolled retry loop that repeatedly shifts a relative deadline. timerfd_gettime reports remaining time, always relatively, so its output must not be mistaken for the original absolute calendar timestamp.
Test policy and kernel behavior in separate layers
First use an injected clock/read adapter to test forward and backward discontinuities, ECANCELED, zero-byte reads, multiple expirations, EAGAIN and stale generations. Assert the chosen action count and next deadline. Such a model tests the application decoder; it cannot prove the kernel produced those observations.
Kernel clock-step tests need an approved disposable environment isolated from production timekeeping. Do not change the development host’s clock to test this article, and do not assume an ordinary process container isolates CLOCK_REALTIME changes. The proposed integration matrix should include both cancellation-enabled and cancellation-disabled absolute timers, plus the rearm-before-read sequence.
The deliverable is a clock/flag/result matrix linked to a calendar policy. It should establish why a timer fired, was canceled, remained idle or was rearmed despite an error. That prevents a time correction from becoming either a silent missed action or an accidental duplicate execution.