30typedef enum : uint64_t {
35typedef enum : uint8_t {
42typedef enum : uint32_t {
106 if (max_ms < min_ms) {
111 const uint64_t span = (uint64_t)(max_ms - min_ms) + 1U;
129 struct timespec ts = {.tv_sec = (time_t)(ms /
k_ms_per_s),
131 (void)nanosleep(&ts,
nullptr);
154typedef enum : uint16_t {
160typedef enum : uint32_t {
168typedef enum : uint16_t {
192 if (secs > (int64_t)(UINT32_MAX /
k_ms_per_s)) {
195 return (uint32_t)(secs * (int64_t)
k_ms_per_s);
217 for (
const char* c = s; *c !=
'\0'; ++c) {
218 if ((*c <
'0') || (*c >
'9')) {
244 static const char*
const k_fmts[] = {
245 "%a, %d %b %Y %H:%M:%S GMT",
246 "%A, %d-%b-%y %H:%M:%S GMT",
248 for (
size_t i = 0U; i < (
sizeof(k_fmts) /
sizeof(k_fmts[0])); ++i) {
250 if (strptime(value, k_fmts[i], &tmv) !=
nullptr) {
251 const time_t t = timegm(&tmv);
252 if (t == (time_t)-1) {
264 if ((value ==
nullptr) || (out_ms ==
nullptr)) {
267 while ((*value ==
' ') || (*value ==
'\t')) {
270 if (*value ==
'\0') {
300 return (a < b) ? a : b;
319 return (a > b) ? a : b;
337 if (g->
now_fn !=
nullptr) {
347 struct timespec ts = {};
348 (void)clock_gettime(CLOCK_MONOTONIC, &ts);
370 const uint32_t d = (ms > (int64_t)UINT32_MAX) ? UINT32_MAX : (uint32_t)ms;
420 if (host ==
nullptr) {
436 if ((rec !=
nullptr) || (host ==
nullptr)) {
476 const int64_t rate_wait = (rec->
credit_ms < interval) ? (interval - rec->
credit_ms) : 0;
478 const int64_t wait = target - now;
529 uint32_t jitter_min_ms,
530 uint32_t jitter_max_ms)
537 if (rec ==
nullptr) {
558 if ((rec !=
nullptr) && (rec->
inflight > 0U)) {
580 const uint16_t shifts = (level > 1U) ? (uint16_t)(level - 1U) : 0U;
655 const char* retry_after,
663 if (rec ==
nullptr) {
666 uint32_t retry_ms = 0U;
667 const bool has_retry =
669 const bool throttled =
685 uint16_t* backoff_level,
686 int64_t* earliest_next_ms)
688 if ((g ==
nullptr) || (host ==
nullptr)) {
693 if (backoff_level !=
nullptr) {
696 if (earliest_next_ms !=
nullptr) {
@ 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.
@ k_ra8_ok
Success – operation completed with all postconditions satisfied.
ra8_err_codes_t ra8_err_t
Canonical error-return type used by every ra8-firmware API.
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.
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.