Timer#

Join provides high-resolution timers integrated with the reactor/event loop. Timers are built on top of Linux timerfd and expose a modern C++ chrono-based API.

Timers are:

  • non-blocking
  • event-driven
  • reactor-integrated — registered automatically on construction

They are available through two clock policies:

  • Monotonic — unaffected by system time changes
  • RealTime — tracks the wall clock

Timer types#

Monotonic timers#

Monotonic timers use CLOCK_MONOTONIC and are the recommended choice for delays and intervals.

Monotonic::Timer timer;

Real-time timers#

Real-time timers use CLOCK_REALTIME and follow system clock adjustments.

RealTime::Timer timer;

Reactor integration#

Timers inherit from EventHandler and register themselves with the reactor automatically at construction. By default they use the global ReactorThread singleton, but a custom Reactor* can be passed:

// default: uses ReactorThread::reactor()
Monotonic::Timer timer;

// custom reactor
Reactor myReactor;
Monotonic::Timer timer(&myReactor);

The timer unregisters itself from the reactor in its destructor. Timers are neither copyable nor movable.


One-shot timer#

A one-shot timer fires once after a given duration.

#include <join/timer.hpp>

using namespace join;

Monotonic::Timer timer;

timer.setOneShot(std::chrono::seconds(1), []() {
    // called once after 1 second
});

One-shot timer (absolute time)#

A one-shot timer can also be armed at an absolute time point.

RealTime::Timer timer;

auto deadline = std::chrono::system_clock::now() + std::chrono::seconds(5);

timer.setOneShot(deadline, []() {
    // called at the deadline
});

⚠️ The clock type must match the timer policy — enforced at compile time:

PolicyExpected clock
Monotonicstd::chrono::steady_clock
RealTimestd::chrono::system_clock

A mismatch produces a static_assert error.


Periodic timer#

A periodic timer fires repeatedly at a fixed interval.

Monotonic::Timer timer;

timer.setInterval(std::chrono::milliseconds(500), []() {
    // called every 500 ms
});

The callback is invoked once per expiration. If the reactor is delayed and multiple expirations have accumulated, the callback is called once for each missed expiration.


Cancel a timer#

timer.cancel();

After cancellation the timer is disarmed, the callback is cleared, and no further calls are made.


Timer state inspection#

Check if the timer is armed#

if (timer.active())
{
    // timer is running
}

active() queries the kernel via timerfd_gettime — it returns true if either the initial value or the interval is non-zero.

Remaining time before next expiration#

std::chrono::nanoseconds rem = timer.remaining();

Interval of a periodic timer#

std::chrono::nanoseconds iv = timer.interval();

Returns zero for one-shot timers or after cancellation.

Check if the timer is one-shot#

bool os = timer.oneShot();

Clock type#

int clockId = timer.type();  // CLOCK_MONOTONIC or CLOCK_REALTIME

Best practices#

  • Prefer Monotonic timers for delays and intervals — immune to NTP or settimeofday adjustments
  • Use RealTime timers only for wall-clock scheduling
  • Keep callbacks short and non-blocking — they execute on the reactor dispatcher thread
  • Cancel timers explicitly when no longer needed, before their owning object is destroyed

Summary#

FeatureSupported
One-shot timers
Periodic timers
Absolute time point
Clock/policy type check✅ (static_assert)
Reactor integration
Custom reactor
Nanosecond resolution
Missed expiration replay