ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
mdl_politeness.c
Go to the documentation of this file.
1
12#include "mdl_politeness.h"
13
14#include <stdint.h>
15#include <stdio.h>
16#include <stdlib.h>
17#include <string.h>
18#include <time.h>
19
20#include "ra8_attributes.h"
21
30typedef enum : uint64_t {
31 k_seed_fallback = 0x9E3779B97F4A7C15ULL,
33
35typedef enum : uint8_t {
40
42typedef enum : uint32_t {
43 k_ms_per_s = 1000U,
44 k_ns_per_ms = 1000000U,
45 k_dec_base = 10U,
47
49{
50 mdl_politeness_init_clock(p, seed, nullptr, nullptr);
51}
52
54 uint64_t seed,
55 mdl_sleep_fn sleep_fn,
56 void* sleep_ctx)
57{
58 if (p == nullptr) {
59 return;
60 }
61 p->state = (seed == 0U) ? (uint64_t)k_seed_fallback : seed;
62 p->sleep_fn = sleep_fn;
63 p->sleep_ctx = sleep_ctx;
64}
65
79RA8_INTERNAL static uint64_t internal_next_rand(uint64_t* state)
80{
81 uint64_t x = *state;
82 x ^= x << (uint64_t)k_xs_shift_a;
83 x ^= x >> (uint64_t)k_xs_shift_b;
84 x ^= x << (uint64_t)k_xs_shift_c;
85 *state = x;
86 return x;
87}
88
104RA8_INTERNAL static uint32_t internal_draw_range(uint64_t* state, uint32_t min_ms, uint32_t max_ms)
105{
106 if (max_ms < min_ms) {
107 max_ms = min_ms;
108 }
109 /* 64-bit span keeps the full-range (min=0, max=UINT32_MAX) case from wrapping
110 * while preserving the exact modulo of the original jitter for smaller spans. */
111 const uint64_t span = (uint64_t)(max_ms - min_ms) + 1U;
112 return min_ms + (uint32_t)(internal_next_rand(state) % span);
113}
114
127RA8_INTERNAL static void internal_host_sleep_ms(uint32_t ms)
128{
129 struct timespec ts = {.tv_sec = (time_t)(ms / k_ms_per_s),
130 .tv_nsec = (long)((ms % k_ms_per_s) * k_ns_per_ms)};
131 (void)nanosleep(&ts, nullptr);
132}
133
134uint32_t mdl_politeness_wait(mdl_politeness_t* p, uint32_t min_ms, uint32_t max_ms)
135{
136 if (p == nullptr) {
137 return 0U;
138 }
139 const uint32_t delayms = internal_draw_range(&p->state, min_ms, max_ms);
140
141 if (p->sleep_fn != nullptr) {
142 p->sleep_fn(p->sleep_ctx, delayms);
143 } else {
144 internal_host_sleep_ms(delayms);
145 }
146 return delayms;
147}
148
149/* ======================================================================== *
150 * Retry-After parsing (#301) *
151 * ======================================================================== */
152
154typedef enum : uint16_t {
158
166
168typedef enum : uint16_t {
172
187RA8_INTERNAL static uint32_t internal_ms_from_secs(int64_t secs)
188{
189 if (secs <= 0) {
190 return 0U;
191 }
192 if (secs > (int64_t)(UINT32_MAX / k_ms_per_s)) {
193 return UINT32_MAX;
194 }
195 return (uint32_t)(secs * (int64_t)k_ms_per_s);
196}
197
212RA8_INTERNAL static bool internal_all_digits(const char* s)
213{
214 if (*s == '\0') {
215 return false;
216 }
217 for (const char* c = s; *c != '\0'; ++c) {
218 if ((*c < '0') || (*c > '9')) {
219 return false;
220 }
221 }
222 return true;
223}
224
241RA8_INTERNAL static bool
242internal_parse_http_date(const char* value, int64_t now_wall_s, uint32_t* out_ms)
243{
244 static const char* const k_fmts[] = {
245 "%a, %d %b %Y %H:%M:%S GMT", /* IMF-fixdate: Sun, 06 Nov 1994 08:49:37 GMT */
246 "%A, %d-%b-%y %H:%M:%S GMT", /* RFC 850: Sunday, 06-Nov-94 08:49:37 GMT */
247 };
248 for (size_t i = 0U; i < (sizeof(k_fmts) / sizeof(k_fmts[0])); ++i) {
249 struct tm tmv = {};
250 if (strptime(value, k_fmts[i], &tmv) != nullptr) {
251 const time_t t = timegm(&tmv);
252 if (t == (time_t)-1) {
253 continue;
254 }
255 *out_ms = internal_ms_from_secs((int64_t)t - now_wall_s);
256 return true;
257 }
258 }
259 return false;
260}
261
262bool mdl_retry_after_parse(const char* value, int64_t now_wall_s, uint32_t* out_ms)
263{
264 if ((value == nullptr) || (out_ms == nullptr)) {
265 return false;
266 }
267 while ((*value == ' ') || (*value == '\t')) {
268 ++value;
269 }
270 if (*value == '\0') {
271 return false;
272 }
273 if (internal_all_digits(value)) {
274 *out_ms = internal_ms_from_secs((int64_t)strtoull(value, nullptr, k_dec_base));
275 return true;
276 }
277 return internal_parse_http_date(value, now_wall_s, out_ms);
278}
279
280/* ======================================================================== *
281 * Per-host politeness governor (#301) *
282 * ======================================================================== */
283
298RA8_INTERNAL static int64_t internal_min_i64(int64_t a, int64_t b)
299{
300 return (a < b) ? a : b;
301}
302
317RA8_INTERNAL static int64_t internal_max_i64(int64_t a, int64_t b)
318{
319 return (a > b) ? a : b;
320}
321
336{
337 if (g->now_fn != nullptr) {
338 return g->now_fn(g->now_ctx);
339 }
340 /* CLOCK_MONOTONIC, not CLOCK_REALTIME (#509). The governor only ever
341 * subtracts two readings -- token refill, request spacing, the backoff
342 * gate -- and CLOCK_REALTIME is steppable: an NTP correction forward makes
343 * the governor believe the spacing has elapsed and hammer the remote host,
344 * and one backward makes it wait out the step. A ~4 minute backward step
345 * was measured happening repeatedly on a fleet host, so this is an observed
346 * hazard rather than a theoretical one. */
347 struct timespec ts = {};
348 (void)clock_gettime(CLOCK_MONOTONIC, &ts);
349 return ((int64_t)ts.tv_sec * (int64_t)k_ms_per_s) + ((int64_t)ts.tv_nsec / (int64_t)k_ns_per_ms);
350}
351
366{
367 if (ms <= 0) {
368 return;
369 }
370 const uint32_t d = (ms > (int64_t)UINT32_MAX) ? UINT32_MAX : (uint32_t)ms;
371 if (g->sleep_fn != nullptr) {
372 g->sleep_fn(g->sleep_ctx, d);
373 } else {
375 }
376}
377
393{
394 return (cfg->rate_per_min > 0U) ? ((int64_t)k_mdl_gov_ms_per_req / (int64_t)cfg->rate_per_min)
395 : 0;
396}
397
413{
414 return internal_gov_interval_ms(cfg) * (int64_t)cfg->burst;
415}
416
419{
420 if (host == nullptr) {
421 return nullptr;
422 }
423 for (uint16_t i = 0U; i < (uint16_t)k_mdl_gov_max_hosts; ++i) {
424 if (g->hosts[i].used && (strcmp(g->hosts[i].host, host) == 0)) {
425 return &g->hosts[i];
426 }
427 }
428 return nullptr;
429}
430
433internal_gov_get(mdl_governor_t* g, const char* host, int64_t now)
434{
435 mdl_host_rec_t* rec = internal_gov_find(g, host);
436 if ((rec != nullptr) || (host == nullptr)) {
437 return rec;
438 }
439 for (uint16_t i = 0U; i < (uint16_t)k_mdl_gov_max_hosts; ++i) {
440 if (!g->hosts[i].used) {
441 g->hosts[i] = (mdl_host_rec_t){};
442 g->hosts[i].used = true;
443 g->hosts[i].credit_ms = internal_gov_cap_ms(&g->cfg); /* start full: allow a burst */
444 g->hosts[i].last_ms = now;
445 g->hosts[i].earliest_next_ms = now;
446 (void)snprintf(g->hosts[i].host, sizeof(g->hosts[i].host), "%s", host);
447 return &g->hosts[i];
448 }
449 }
450 return nullptr;
451}
452
469RA8_INTERNAL static int64_t
471{
472 const int64_t interval = internal_gov_interval_ms(&g->cfg);
473 const int64_t cap = internal_gov_cap_ms(&g->cfg);
474 const int64_t elapsed = internal_max_i64(now - rec->last_ms, 0);
475 rec->credit_ms = internal_min_i64(rec->credit_ms + elapsed, cap);
476 const int64_t rate_wait = (rec->credit_ms < interval) ? (interval - rec->credit_ms) : 0;
477 const int64_t target = internal_max_i64(now + rate_wait, rec->earliest_next_ms);
478 const int64_t wait = target - now;
479 rec->credit_ms = internal_max_i64(internal_min_i64(rec->credit_ms + wait, cap) - interval, 0);
480 rec->last_ms = target;
481 return wait;
482}
483
485{
486 return (mdl_gov_cfg_t){
487 .rate_per_min = (uint32_t)k_def_rate_per_min,
488 .burst = (uint32_t)k_def_burst,
489 .backoff_base_ms = (uint32_t)k_def_backoff_base_ms,
490 .backoff_max_ms = (uint32_t)k_def_backoff_max_ms,
491 .decay_after = (uint16_t)k_def_decay_after,
492 .max_inflight = (uint16_t)k_def_max_inflight,
493 };
494}
495
496void mdl_governor_init(mdl_governor_t* g, const mdl_gov_cfg_t* cfg, uint64_t seed)
497{
498 mdl_governor_init_clock(g, cfg, seed, nullptr, nullptr, nullptr, nullptr);
499}
500
502 const mdl_gov_cfg_t* cfg,
503 uint64_t seed,
504 mdl_now_fn now_fn,
505 void* now_ctx,
506 mdl_sleep_fn sleep_fn,
507 void* sleep_ctx)
508{
509 if (g == nullptr) {
510 return;
511 }
512 memset(g, 0, sizeof(*g));
513 g->cfg = (cfg != nullptr) ? *cfg : mdl_gov_cfg_default();
514 if (g->cfg.burst == 0U) {
515 g->cfg.burst = 1U;
516 }
517 if (g->cfg.max_inflight == 0U) {
518 g->cfg.max_inflight = 1U;
519 }
520 g->rng = (seed == 0U) ? (uint64_t)k_seed_fallback : seed;
521 g->now_fn = now_fn;
522 g->now_ctx = now_ctx;
523 g->sleep_fn = sleep_fn;
524 g->sleep_ctx = sleep_ctx;
525}
526
528 const char* host,
529 uint32_t jitter_min_ms,
530 uint32_t jitter_max_ms)
531{
532 if (g == nullptr) {
533 return k_ra8_ok; /* pacing disabled -- matches a NULL jitter source */
534 }
535 const int64_t now = internal_gov_now(g);
536 mdl_host_rec_t* rec = internal_gov_get(g, host, now);
537 if (rec == nullptr) {
538 /* NULL host or table full: still space requests with jitter, no tracking. */
539 internal_gov_sleep(g, (int64_t)internal_draw_range(&g->rng, jitter_min_ms, jitter_max_ms));
540 return k_ra8_ok;
541 }
542 if (rec->inflight >= g->cfg.max_inflight) {
544 }
545 const int64_t wait = internal_gov_schedule(g, rec, now);
546 const int64_t jitter = (int64_t)internal_draw_range(&g->rng, jitter_min_ms, jitter_max_ms);
547 internal_gov_sleep(g, wait + jitter);
548 rec->inflight += 1U;
549 return k_ra8_ok;
550}
551
552void mdl_governor_release(mdl_governor_t* g, const char* host)
553{
554 if (g == nullptr) {
555 return;
556 }
557 mdl_host_rec_t* rec = internal_gov_find(g, host);
558 if ((rec != nullptr) && (rec->inflight > 0U)) {
559 rec->inflight -= 1U;
560 }
561}
562
577RA8_INTERNAL static int64_t internal_gov_backoff_window(const mdl_gov_cfg_t* cfg, uint16_t level)
578{
579 int64_t window = (int64_t)cfg->backoff_base_ms;
580 const uint16_t shifts = (level > 1U) ? (uint16_t)(level - 1U) : 0U;
581 for (uint16_t i = 0U; (i < shifts) && (i < (uint16_t)k_mdl_gov_level_max); ++i) {
582 window <<= 1;
583 }
584 if ((window < 0) || (window > (int64_t)cfg->backoff_max_ms)) {
585 window = (int64_t)cfg->backoff_max_ms;
586 }
587 return window;
588}
589
605RA8_INTERNAL static void
606internal_gov_on_throttle(mdl_governor_t* g, mdl_host_rec_t* rec, int64_t now, uint32_t retry_ms)
607{
608 if (rec->backoff_level < (uint16_t)k_mdl_gov_level_max) {
609 rec->backoff_level += 1U;
610 }
611 rec->success_streak = 0U;
612 const int64_t window = internal_gov_backoff_window(&g->cfg, rec->backoff_level);
613 const int64_t backoff = (int64_t)internal_draw_range(&g->rng, 0U, (uint32_t)window);
614 const int64_t gate = now + internal_max_i64(backoff, (int64_t)retry_ms);
616}
617
635 mdl_host_rec_t* rec,
636 int64_t now,
637 bool has_retry,
638 uint32_t retry_ms)
639{
640 rec->success_streak += 1U;
641 if ((g->cfg.decay_after > 0U) && (rec->success_streak >= g->cfg.decay_after)) {
642 if (rec->backoff_level > 0U) {
643 rec->backoff_level -= 1U;
644 }
645 rec->success_streak = 0U;
646 }
647 if (has_retry) {
648 rec->earliest_next_ms = internal_max_i64(rec->earliest_next_ms, now + (int64_t)retry_ms);
649 }
650}
651
653 const char* host,
654 long status,
655 const char* retry_after,
656 int64_t now_wall_s)
657{
658 if (g == nullptr) {
659 return;
660 }
661 const int64_t now = internal_gov_now(g);
662 mdl_host_rec_t* rec = internal_gov_get(g, host, now);
663 if (rec == nullptr) {
664 return; /* NULL host or table full */
665 }
666 uint32_t retry_ms = 0U;
667 const bool has_retry =
668 (retry_after != nullptr) && mdl_retry_after_parse(retry_after, now_wall_s, &retry_ms);
669 const bool throttled =
670 (status == (long)k_http_too_many_req) || (status == (long)k_http_unavailable);
671 if (throttled) {
672 internal_gov_on_throttle(g, rec, now, retry_ms);
673 } else {
674 internal_gov_on_success(g, rec, now, has_retry, retry_ms);
675 }
676}
677
678void mdl_governor_observe(mdl_governor_t* g, const char* host, long status, const char* retry_after)
679{
680 mdl_governor_observe_at_wall(g, host, status, retry_after, (int64_t)time(nullptr));
681}
682
684 const char* host,
685 uint16_t* backoff_level,
686 int64_t* earliest_next_ms)
687{
688 if ((g == nullptr) || (host == nullptr)) {
689 return false;
690 }
691 for (uint16_t i = 0U; i < (uint16_t)k_mdl_gov_max_hosts; ++i) {
692 if (g->hosts[i].used && (strcmp(g->hosts[i].host, host) == 0)) {
693 if (backoff_level != nullptr) {
694 *backoff_level = g->hosts[i].backoff_level;
695 }
696 if (earliest_next_ms != nullptr) {
697 *earliest_next_ms = g->hosts[i].earliest_next_ms;
698 }
699 return true;
700 }
701 }
702 return false;
703}
@ k_ns_per_ms
Nanoseconds per millisecond.
@ k_http_too_many_req
Too Many Requests (throttle).
@ k_http_unavailable
Service Unavailable (throttle).
mdl_seed_t
Fallback seed so the xorshift64 state is never 0.
@ k_seed_fallback
Substituted when the seed is 0.
static uint64_t internal_next_rand(uint64_t *state)
Advance an xorshift64 state in place and return the new value.
static void internal_gov_sleep(mdl_governor_t *g, int64_t ms)
Sleep ms through the injected sleeper, else the host clock.
void mdl_governor_init(mdl_governor_t *g, const mdl_gov_cfg_t *cfg, uint64_t seed)
Initialise a governor on the real host clock and blocking sleep.
bool mdl_retry_after_parse(const char *value, int64_t now_wall_s, uint32_t *out_ms)
Parse an HTTP Retry-After header value into a delay in milliseconds.
static void internal_gov_on_success(mdl_governor_t *g, mdl_host_rec_t *rec, int64_t now, bool has_retry, uint32_t retry_ms)
Apply a non-throttle outcome: count success, decay, honour Retry-After.
void mdl_governor_observe(mdl_governor_t *g, const char *host, long status, const char *retry_after)
Feed a completed request's outcome back into the host's governor state.
void mdl_politeness_init(mdl_politeness_t *p, uint64_t seed)
Seed the jitter source, using the real host clock for sleeps.
static int64_t internal_gov_schedule(mdl_governor_t *g, mdl_host_rec_t *rec, int64_t now)
Refill credit to now, gate on rate + backoff, consume one token.
static int64_t internal_gov_now(const mdl_governor_t *g)
Read the governor's clock: injected now_fn, else CLOCK_MONOTONIC.
static int64_t internal_gov_cap_ms(const mdl_gov_cfg_t *cfg)
Token-bucket capacity in ms (interval * burst).
static mdl_host_rec_t * internal_gov_find(mdl_governor_t *g, const char *host)
Find an existing per-host record, or NULL.
static int64_t internal_gov_interval_ms(const mdl_gov_cfg_t *cfg)
Token interval (ms per request); 0 when rate limiting is disabled.
static bool internal_all_digits(const char *s)
True when s is a non-empty run of ASCII digits.
static int64_t internal_max_i64(int64_t a, int64_t b)
Larger of two signed millisecond values.
mdl_gov_defaults_t
Governor default tunables (conservative; see mdl_gov_cfg_default).
@ k_def_backoff_base_ms
1 s first backoff window.
@ k_def_backoff_max_ms
60 s backoff ceiling.
@ k_def_burst
Small burst allowance.
@ k_def_rate_per_min
~1 request/second sustained.
static bool internal_parse_http_date(const char *value, int64_t now_wall_s, uint32_t *out_ms)
Parse an HTTP-date Retry-After (IMF-fixdate or RFC 850) into a delay.
static mdl_host_rec_t * internal_gov_get(mdl_governor_t *g, const char *host, int64_t now)
Find-or-create a per-host record; NULL if the table is full or host NULL.
void mdl_politeness_init_clock(mdl_politeness_t *p, uint64_t seed, mdl_sleep_fn sleep_fn, void *sleep_ctx)
Seed the jitter source and inject a clock for the blocking sleep.
mdl_gov_cfg_t mdl_gov_cfg_default(void)
Conservative default tunables for a site that configures none.
mdl_gov_defaults16_t
Governor default counts that fit uint16 fields.
@ k_def_decay_after
Successes that drop one backoff level.
@ k_def_max_inflight
Strictly serial per host by default.
mdl_http_throttle_t
HTTP status codes the governor treats as a throttle.
static int64_t internal_min_i64(int64_t a, int64_t b)
Smaller of two signed millisecond values.
mdl_xorshift_t
xorshift64 shift triple (Marsaglia's 13/7/17).
@ k_xs_shift_c
Second left shift.
@ k_xs_shift_b
Right shift.
@ k_xs_shift_a
First left shift.
mdl_time_unit_t
Time-unit conversions shared by the sleep and governor clocks.
@ k_ms_per_s
Milliseconds per second.
@ k_dec_base
Base-10 radix for strtoull.
ra8_err_t mdl_governor_acquire(mdl_governor_t *g, const char *host, uint32_t jitter_min_ms, uint32_t jitter_max_ms)
Reserve an in-flight slot for a request to host, pacing as required.
static void internal_gov_on_throttle(mdl_governor_t *g, mdl_host_rec_t *rec, int64_t now, uint32_t retry_ms)
Apply a throttle: raise the backoff level and push the gate forward.
static uint32_t internal_draw_range(uint64_t *state, uint32_t min_ms, uint32_t max_ms)
Draw a jittered value in [min_ms, max(min_ms, max_ms)] from state.
void mdl_governor_observe_at_wall(mdl_governor_t *g, const char *host, long status, const char *retry_after, int64_t now_wall_s)
Observe a response using an explicit wall-clock timestamp.
static void internal_host_sleep_ms(uint32_t ms)
Block for ms milliseconds on the host clock.
bool mdl_governor_peek(const mdl_governor_t *g, const char *host, uint16_t *backoff_level, int64_t *earliest_next_ms)
Read a host's current backoff level and earliest-next gate.
void mdl_governor_release(mdl_governor_t *g, const char *host)
Release the in-flight slot reserved by a matching mdl_governor_acquire.
void mdl_governor_init_clock(mdl_governor_t *g, const mdl_gov_cfg_t *cfg, uint64_t seed, mdl_now_fn now_fn, void *now_ctx, mdl_sleep_fn sleep_fn, void *sleep_ctx)
Initialise a governor with injected clock and sleep seams (DI).
static uint32_t internal_ms_from_secs(int64_t secs)
Saturating conversion of a signed-seconds delay into a ms delay.
uint32_t mdl_politeness_wait(mdl_politeness_t *p, uint32_t min_ms, uint32_t max_ms)
Sleep a jittered delay in [min_ms, max_ms] and return it.
static int64_t internal_gov_backoff_window(const mdl_gov_cfg_t *cfg, uint16_t level)
Exponential backoff window for a level: min(base << (level-1), ceil).
Jittered inter-request delay plus the per-host politeness governor.
@ k_mdl_gov_ms_per_req
Milliseconds per minute (rate -> interval).
@ k_mdl_gov_level_max
Backoff-exponent ceiling (overflow guard).
@ k_mdl_gov_max_hosts
Per-host record slots (origin + CDNs).
int64_t(* mdl_now_fn)(void *ctx)
Injected monotonic clock: milliseconds since an arbitrary fixed epoch.
void(* mdl_sleep_fn)(void *ctx, uint32_t ms)
Injected blocking sleep: pause the caller for ms milliseconds.
Annotation-attribute framework macros for ra8-firmware.
#define RA8_INTERNAL
Marker that a function is intended to be static (file-local).
@ k_ra8_err_would_block
Non-blocking operation would have blocked.
Definition ra8_err.h:210
@ k_ra8_ok
Success – operation completed with all postconditions satisfied.
Definition ra8_err.h:119
ra8_err_codes_t ra8_err_t
Canonical error-return type used by every ra8-firmware API.
Definition ra8_err.h:546
int strcmp(const char *s1, const char *s2)
Compare two null-terminated strings.
void * memset(void *dst, int value, size_t n)
Fill memory with a constant byte value.
Per-site politeness tunables the governor is initialised with.
uint32_t burst
Token-bucket capacity, in requests (>= 1).
uint32_t rate_per_min
Sustained per-host request ceiling (0 = off).
uint16_t decay_after
Consecutive successes that drop one backoff level.
uint16_t max_inflight
Per-host in-flight request cap (>= 1).
uint32_t backoff_max_ms
Backoff-window ceiling.
uint32_t backoff_base_ms
First backoff window on a 429/503.
Closed-loop per-host politeness governor (rate + backoff + concurrency).
mdl_now_fn now_fn
Injected monotonic clock (ms).
uint64_t rng
Seeded xorshift64 jitter state.
void * now_ctx
Context for now_fn.
mdl_gov_cfg_t cfg
Politeness tunables.
void * sleep_ctx
Context for sleep_fn.
mdl_sleep_fn sleep_fn
Injected sleeper, NULL = host.
mdl_host_rec_t hosts[k_mdl_gov_max_hosts]
Per-host records.
One host's live governor state (a slot in the fixed per-host table).
uint16_t inflight
Requests currently in flight.
uint16_t success_streak
Consecutive successes since last drop.
uint16_t backoff_level
Consecutive-throttle exponent.
int64_t credit_ms
Token-bucket credit, ms of rate.
int64_t last_ms
Wall-ms of the previous scheduled start.
bool used
Slot occupied.
char host[k_mdl_gov_host_max]
Host key; "" when the slot is free.
int64_t earliest_next_ms
Backoff / Retry-After gate (mono-ms).
Deterministic jitter source plus its (optional) injected clock.
uint64_t state
PRNG state; never 0 after init.
void * sleep_ctx
Context passed to sleep_fn.
mdl_sleep_fn sleep_fn
Injected sleeper; NULL uses the host clock.