Haiku system_time(): Elapsed Clocks, Deadlines, and Timebase Boundaries
Use Haiku's boot-relative system_time() for intervals, form absolute deadlines in its timebase, and keep durable calendar timestamps separate.
Time values are only meaningful when their clock domain is known. Haiku’s system_time() is an elapsed-time reading: the public kernel header documents it as time since boot, in microseconds. It is not a Unix timestamp, a local date, or a value that can safely be carried across a reboot. The same header declares system_time_nsecs() for nanoseconds and exposes sleep functions that accept an explicit time base. These interfaces make it possible to build clear interval and deadline logic, provided code keeps units and clock domains visible.
This is a focused guide to that boundary, not a replacement for the broader Haiku BStopWatch measurement guide or the BMessageRunner timer guide. A stopwatch measures an interval and a message runner schedules delivery; the underlying clock still needs a deliberate contract when an operation has a deadline, when a wait must not drift, or when a timestamp will be persisted.
Read the clock contract before doing arithmetic
At the pinned Haiku source revision, OS.h declares system_time() and comments that its result is microseconds since boot. It declares system_time_nsecs() with the corresponding nanosecond unit. SupportDefs.h defines both bigtime_t and nanotime_t as signed 64-bit integers. That representation provides a large range, but it does not make values interchangeable: a duration in microseconds, a nanosecond counter, a media performance timestamp, and a civil-time timestamp remain different kinds of data.
For a short measurement, take two samples and subtract them in the same domain:
bigtime_t started = system_time();
status_t status = perform_operation();
bigtime_t elapsedUsecs = system_time() - started;
if (status != B_OK)
report_operation_failure(status, elapsedUsecs);
The subtraction is useful for elapsed duration during the current boot. Do not label the raw started value as a date in logs or send it as an event timestamp to another machine. It has no portable calendar origin. If a record must survive restart or be compared across hosts, capture an appropriate wall-clock/UTC timestamp separately and state how clock corrections and time-zone presentation are handled.
The API also distinguishes a system boot-relative value from the boot time stored in system_info, whose header comment describes microseconds since January 1, 1970. That is a useful reminder that even two bigtime_t fields can have different origins. A typedef does not encode the origin, unit conversion, validity period, or intended use. Name variables accordingly: deadlineSystemUsecs communicates more than timestamp.
Prefer one absolute deadline to repeated relative sleeps
Repeatedly sleeping for a fixed interval after work completes adds the work duration to every cycle. If a task should run on a cadence, compute its next target once in the system time base and wait against that target. Haiku’s public header defines B_SYSTEM_TIMEBASE for snooze_*() and declares snooze_until(bigtime_t time, int timeBase). The time-base argument is part of the contract; an absolute value from another clock is not a valid substitute.
The following is API-shaped pseudocode for a worker that retries an interrupted wait against the same absolute deadline, checks additions before forming deadlines, and skips missed refreshes in constant time instead of running an unbounded catch-up burst:
// Illustrative Haiku-style pseudocode; helpers are application-defined.
#include <limits>
status_t run_periodic_worker()
{
const bigtime_t period = 250000; // 250 ms, in microseconds
const bigtime_t max = std::numeric_limits<bigtime_t>::max();
const bigtime_t start = system_time();
if (period <= 0 || start > max - period)
return B_BAD_VALUE;
bigtime_t next = start + period;
while (!should_stop()) {
status_t waitStatus = snooze_until(next, B_SYSTEM_TIMEBASE);
if (should_stop())
break;
if (waitStatus == B_INTERRUPTED)
continue; // Retry the same deadline; do not refresh early.
if (waitStatus != B_OK)
return waitStatus;
bigtime_t now = system_time();
if (now < next)
continue; // Be defensive if the wait reports success early.
update_snapshot();
now = system_time();
const bigtime_t elapsed = now - next;
const bigtime_t missedPeriods = elapsed / period;
if (missedPeriods == max)
return B_BAD_VALUE;
const bigtime_t steps = missedPeriods + 1;
if (steps > (max - next) / period)
return B_BAD_VALUE;
next += steps * period;
}
return B_OK;
}
This illustrates the clock relationship, not a promise of hard real-time scheduling or a complete cancellation protocol. B_INTERRUPTED retries the unchanged target; other wait errors are returned, and the refresh runs only after a successful wait has reached its deadline. The division computes how many cadence slots to skip without a loop proportional to the time spent paused; checked bounds prevent signed overflow when forming either the initial deadline or the next slot. Production code should adapt error reporting and shutdown wakeups to its worker design. An absolute wait prevents ordinary work duration from accumulating as schedule drift; it does not guarantee that a thread will run at the exact target instant.
For a one-off timeout, calculate an absolute deadline and check remaining time after every operation that can block. Passing the full relative timeout to each retry can accidentally multiply the caller’s intended budget:
#include <limits>
bigtime_t now = system_time();
if (timeoutUsecs < 0 ||
timeoutUsecs > std::numeric_limits<bigtime_t>::max() - now)
return B_BAD_VALUE; // Reject a negative or overflowing relative timeout.
bigtime_t deadline = now + timeoutUsecs;
while (!complete()) {
bigtime_t now = system_time();
if (now >= deadline)
return B_TIMED_OUT;
bigtime_t remaining = deadline - now;
status_t status = wait_for_next_piece(remaining);
if (status != B_OK && status != B_INTERRUPTED)
return status;
}
The helper’s timeout unit and interrupt behavior must be checked in its own API. Convert only at that boundary. If it accepts milliseconds, round deliberately and ensure a positive remainder does not become a zero-duration poll due to truncation.
Keep units and overflow visible
Haiku APIs commonly express bigtime_t intervals in microseconds, while some interfaces use milliseconds or a distinct media time domain. Do not copy a numeric literal between calls just because both parameters are integers. Give constants unit-bearing names, use checked conversions, and avoid floating-point seconds for long-lived deadline arithmetic.
For a millisecond value received from configuration, validate the input before multiplying by 1,000. A signed 64-bit destination still overflows if an untrusted duration is large enough. Bound configuration to the supported product range, reject negative values unless the specific API documents them as meaningful, and detect overflow before forming now + interval. If a deadline can approach the representable limit, use checked addition rather than allowing signed overflow, which is undefined behavior in C++.
The nanosecond interface is not automatically more accurate or more precise than the microsecond interface. The declaration documents its unit, not a guarantee about scheduler wakeup, hardware timer resolution, or end-to-end measurement error. For a benchmark, measure a sufficiently long interval, record the Haiku build and machine, repeat the experiment, and report the distribution instead of presenting one small sample as a stable performance fact.
Do not persist an uptime value as an event time
A boot-relative number is useful inside one running system because it is detached from time-zone formatting and wall-clock edits. That also makes it unsuitable as a durable event timestamp by itself. After reboot, a later system_time() value starts in a new boot epoch; comparing it with a value saved before shutdown gives a meaningless result. Store a separate UTC/calendar timestamp when a record must be ordered across restarts, and preserve the monotonic/elapsed reading only as supplemental diagnostic context with a boot/session identifier.
The same distinction matters in user interfaces. Use elapsed-time arithmetic for a countdown or timeout, but render a local date from a proper calendar-time value and a documented time-zone policy. If the displayed task is “expire in 30 minutes,” save the product’s intended durable expiration policy rather than saving one system_time() reading and expecting it to remain meaningful tomorrow.
Verification and failure cases
Test clock logic with injected values where possible: a wait that returns early, work that exceeds a period, an interrupted wait, a negative or excessive configured timeout, and a deadline already in the past. Confirm the policy for each case: retry with remaining budget, skip missed refreshes, or fail. On Haiku, separately observe an interval before and after a deliberate sleep/resume test and document what the target build does; do not infer suspend behavior from the header’s “since boot” comment alone. Reboot testing should confirm that no persisted uptime value is treated as an epoch.
When diagnosing a timeout, log the remaining budget, operation phase, return status, and the clock domain used. Avoid logging only “elapsed 5000”: state whether that is microseconds, milliseconds, or an external wall-clock difference. A deadline should bound the whole operation, including retries and cleanup, rather than resetting every time an intermediate read makes small progress.
system_time() is a small API with a consequential boundary: it reports elapsed time within a boot, while durable dates and media scheduling have separate meanings. Keeping the origin, unit, time base, and missed-deadline policy explicit prevents clock corrections, restarts, and slow work from silently changing application behavior.
Related:
- Haiku BStopWatch: Measurement Scope, Laps, and Benchmark Limits
- Haiku BMessageRunner: Periodic Messages Without a Polling Thread
Sources: