ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
mdl_politeness.c File Reference

Seeded xorshift64 jitter, injectable clock, and the per-host governor. More...

#include "mdl_politeness.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "ra8_attributes.h"
Include dependency graph for mdl_politeness.c:

Go to the source code of this file.

Enumerations

enum  mdl_seed_t : uint64_t { k_seed_fallback = 0x9E3779B97F4A7C15ULL }
 Fallback seed so the xorshift64 state is never 0. More...
enum  mdl_xorshift_t : uint8_t {
  k_xs_shift_a = 13U ,
  k_xs_shift_b = 7U ,
  k_xs_shift_c = 17U
}
 xorshift64 shift triple (Marsaglia's 13/7/17). More...
enum  mdl_time_unit_t : uint32_t {
  k_ms_per_s = 1000U ,
  k_ns_per_ms = 1000000U ,
  k_dec_base = 10U
}
 Time-unit conversions shared by the sleep and governor clocks. More...
enum  mdl_http_throttle_t : uint16_t {
  k_http_too_many_req = 429U ,
  k_http_unavailable = 503U
}
 HTTP status codes the governor treats as a throttle. More...
enum  mdl_gov_defaults_t : uint32_t {
  k_def_rate_per_min = 60U ,
  k_def_burst = 4U ,
  k_def_backoff_base_ms = 1000U ,
  k_def_backoff_max_ms = 60000U
}
 Governor default tunables (conservative; see mdl_gov_cfg_default). More...
enum  mdl_gov_defaults16_t : uint16_t {
  k_def_decay_after = 4U ,
  k_def_max_inflight = 1U
}
 Governor default counts that fit uint16 fields. More...

Functions

void mdl_politeness_init (mdl_politeness_t *p, uint64_t seed)
 Seed the jitter source, using the real host clock for sleeps.
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.
static uint64_t internal_next_rand (uint64_t *state)
 Advance an xorshift64 state in place and return the new value.
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.
static void internal_host_sleep_ms (uint32_t ms)
 Block for ms milliseconds on the host clock.
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 uint32_t internal_ms_from_secs (int64_t secs)
 Saturating conversion of a signed-seconds delay into a ms delay.
static bool internal_all_digits (const char *s)
 True when s is a non-empty run of ASCII digits.
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.
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 int64_t internal_min_i64 (int64_t a, int64_t b)
 Smaller of two signed millisecond values.
static int64_t internal_max_i64 (int64_t a, int64_t b)
 Larger of two signed millisecond values.
static int64_t internal_gov_now (const mdl_governor_t *g)
 Read the governor's clock: injected now_fn, else CLOCK_MONOTONIC.
static void internal_gov_sleep (mdl_governor_t *g, int64_t ms)
 Sleep ms through the injected sleeper, else the host clock.
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 int64_t internal_gov_cap_ms (const mdl_gov_cfg_t *cfg)
 Token-bucket capacity in ms (interval * burst).
static mdl_host_rec_tinternal_gov_find (mdl_governor_t *g, const char *host)
 Find an existing per-host record, or NULL.
static mdl_host_rec_tinternal_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.
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.
mdl_gov_cfg_t mdl_gov_cfg_default (void)
 Conservative default tunables for a site that configures none.
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.
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).
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.
void mdl_governor_release (mdl_governor_t *g, const char *host)
 Release the in-flight slot reserved by a matching mdl_governor_acquire.
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).
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 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_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.
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.
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.

Detailed Description

Seeded xorshift64 jitter, injectable clock, and the per-host governor.

Implements deterministic jitter, fixed-capacity per-host pacing, throttle backoff, and Retry-After parsing behind injectable clock seams. Production uses the host clocks; tests can advance virtual time without sleeping. All governor state remains in caller-owned storage.

Definition in file mdl_politeness.c.

Enumeration Type Documentation

◆ mdl_gov_defaults16_t

enum mdl_gov_defaults16_t : uint16_t

Governor default counts that fit uint16 fields.

Enumerator
k_def_decay_after 

Successes that drop one backoff level.

k_def_max_inflight 

Strictly serial per host by default.

Definition at line 168 of file mdl_politeness.c.

◆ mdl_gov_defaults_t

enum mdl_gov_defaults_t : uint32_t

Governor default tunables (conservative; see mdl_gov_cfg_default).

Enumerator
k_def_rate_per_min 

~1 request/second sustained.

k_def_burst 

Small burst allowance.

k_def_backoff_base_ms 

1 s first backoff window.

k_def_backoff_max_ms 

60 s backoff ceiling.

Definition at line 160 of file mdl_politeness.c.

◆ mdl_http_throttle_t

enum mdl_http_throttle_t : uint16_t

HTTP status codes the governor treats as a throttle.

Enumerator
k_http_too_many_req 

Too Many Requests.

k_http_unavailable 

Service Unavailable.

Definition at line 154 of file mdl_politeness.c.

◆ mdl_seed_t

enum mdl_seed_t : uint64_t

Fallback seed so the xorshift64 state is never 0.

A zero state is the one fixed point of xorshift64 – it would emit zeros forever. The constant is the golden-ratio odd multiplier used by SplitMix64, chosen for good avalanche from a small seed.

Since
0.1.0
Enumerator
k_seed_fallback 

Substituted when the seed is 0.

Definition at line 30 of file mdl_politeness.c.

◆ mdl_time_unit_t

enum mdl_time_unit_t : uint32_t

Time-unit conversions shared by the sleep and governor clocks.

Enumerator
k_ms_per_s 

Milliseconds per second.

k_ns_per_ms 

Nanoseconds per millisecond.

k_dec_base 

Base-10 radix for strtoull.

Definition at line 42 of file mdl_politeness.c.

◆ mdl_xorshift_t

enum mdl_xorshift_t : uint8_t

xorshift64 shift triple (Marsaglia's 13/7/17).

Enumerator
k_xs_shift_a 

First left shift.

k_xs_shift_b 

Right shift.

k_xs_shift_c 

Second left shift.

Definition at line 35 of file mdl_politeness.c.

Function Documentation

◆ internal_all_digits()

bool internal_all_digits ( const char * s)
static

True when s is a non-empty run of ASCII digits.

Rejects an empty string and any byte outside 0 through 9.

Parameters
[in]sNUL-terminated candidate string.
Returns
Whether every byte is an ASCII digit and at least one exists.
Return values
trueA non-empty digit run was found.
falseThe string is empty or contains another byte.
Precondition
s is non-NULL and NUL-terminated.
The caller requires locale-independent ASCII classification.
Postcondition
s is unchanged.
No global state is modified.
Note
Thread-safe: reads only its argument.
Since
0.1.0

Definition at line 212 of file mdl_politeness.c.

References RA8_INTERNAL.

Referenced by mdl_retry_after_parse().

◆ internal_draw_range()

uint32_t internal_draw_range ( uint64_t * state,
uint32_t min_ms,
uint32_t max_ms )
static

Draw a jittered value in [min_ms, max(min_ms, max_ms)] from state.

Advances the PRNG once and maps the result across the inclusive range.

Parameters
[in,out]stateNon-zero PRNG state.
[in]min_msInclusive lower bound.
[in]max_msInclusive upper bound, clamped up to min_ms.
Returns
The selected bounded value.
Return values
otherA value in the documented inclusive range.
Precondition
state points to writable non-zero state.
Both bounds are expressed in milliseconds.
Postcondition
*state has advanced exactly once.
The return is never below min_ms.
Note
Not thread-safe when callers share state.
Since
0.1.0

Definition at line 104 of file mdl_politeness.c.

References internal_next_rand(), and RA8_INTERNAL.

Referenced by internal_gov_on_throttle(), mdl_governor_acquire(), and mdl_politeness_wait().

◆ internal_gov_backoff_window()

int64_t internal_gov_backoff_window ( const mdl_gov_cfg_t * cfg,
uint16_t level )
static

Exponential backoff window for a level: min(base << (level-1), ceil).

Doubles from the configured base with bounded shifts and ceiling saturation.

Parameters
[in]cfgGovernor backoff configuration.
[in]levelOne-based throttle level; zero uses the base window.
Returns
Bounded backoff window in milliseconds.
Return values
otherA value no greater than backoff_max_ms.
Precondition
cfg is non-NULL.
level is bounded by k_mdl_gov_level_max in stored state.
Postcondition
cfg is unchanged.
No state is modified.
Note
Thread-safe: pure arithmetic.
Since
0.1.0

Definition at line 577 of file mdl_politeness.c.

References mdl_gov_cfg_t::backoff_base_ms, mdl_gov_cfg_t::backoff_max_ms, k_mdl_gov_level_max, and RA8_INTERNAL.

Referenced by internal_gov_on_throttle().

◆ internal_gov_cap_ms()

int64_t internal_gov_cap_ms ( const mdl_gov_cfg_t * cfg)
static

Token-bucket capacity in ms (interval * burst).

Expresses the configured burst capacity on the rate-credit timeline.

Parameters
[in]cfgGovernor rate and burst configuration.
Returns
Maximum token credit in milliseconds.
Return values
0Rate limiting is disabled or burst is zero.
otherProduct of interval and burst.
Precondition
cfg is non-NULL.
The configuration was clamped by governor initialisation.
Postcondition
cfg is unchanged.
No state is modified.
Note
Thread-safe: pure arithmetic.
Since
0.1.0

Definition at line 412 of file mdl_politeness.c.

References mdl_gov_cfg_t::burst, internal_gov_interval_ms(), and RA8_INTERNAL.

Referenced by internal_gov_get(), and internal_gov_schedule().

◆ internal_gov_find()

mdl_host_rec_t * internal_gov_find ( mdl_governor_t * g,
const char * host )
static

Find an existing per-host record, or NULL.

Definition at line 418 of file mdl_politeness.c.

References mdl_host_rec_t::host, mdl_governor_t::hosts, k_mdl_gov_max_hosts, RA8_INTERNAL, strcmp(), and mdl_host_rec_t::used.

Referenced by internal_gov_get(), and mdl_governor_release().

◆ internal_gov_get()

mdl_host_rec_t * internal_gov_get ( mdl_governor_t * g,
const char * host,
int64_t now )
static

◆ internal_gov_interval_ms()

int64_t internal_gov_interval_ms ( const mdl_gov_cfg_t * cfg)
static

Token interval (ms per request); 0 when rate limiting is disabled.

Converts the configured requests-per-minute ceiling by integer division.

Parameters
[in]cfgGovernor rate configuration.
Returns
Milliseconds charged per request.
Return values
0Rate limiting is disabled.
otherPositive request interval.
Precondition
cfg is non-NULL.
rate_per_min == 0 denotes disabled pacing.
Postcondition
cfg is unchanged.
No state is modified.
Note
Thread-safe: pure arithmetic.
Since
0.1.0

Definition at line 392 of file mdl_politeness.c.

References k_mdl_gov_ms_per_req, RA8_INTERNAL, and mdl_gov_cfg_t::rate_per_min.

Referenced by internal_gov_cap_ms(), and internal_gov_schedule().

◆ internal_gov_now()

int64_t internal_gov_now ( const mdl_governor_t * g)
static

Read the governor's clock: injected now_fn, else CLOCK_MONOTONIC.

Preserves the arbitrary monotonic epoch used for rate and backoff differences.

Parameters
[in]gInitialised governor containing the optional clock seam.
Returns
Current monotonic time in milliseconds.
Return values
otherA reading on the governor timeline.
Precondition
g is non-NULL and initialised.
An injected clock, when present, does not run backward.
Postcondition
Governor state is unchanged.
At most one clock source is read.
Note
Thread safety follows the injected clock implementation.
Since
0.1.0

Definition at line 335 of file mdl_politeness.c.

References k_ms_per_s, k_ns_per_ms, mdl_governor_t::now_ctx, mdl_governor_t::now_fn, and RA8_INTERNAL.

Referenced by mdl_governor_acquire(), and mdl_governor_observe_at_wall().

◆ internal_gov_on_success()

void internal_gov_on_success ( mdl_governor_t * g,
mdl_host_rec_t * rec,
int64_t now,
bool has_retry,
uint32_t retry_ms )
static

Apply a non-throttle outcome: count success, decay, honour Retry-After.

Advances the success streak, decays one level at threshold, and applies an optional gate.

Parameters
[in]gGovernor providing the decay threshold.
[in,out]recMatching host record.
[in]nowCurrent monotonic time in milliseconds.
[in]has_retryWhether retry_ms came from a valid header.
[in]retry_msParsed Retry-After delay.
Returns
Nothing.
Precondition
g and rec are non-NULL and associated.
now is on the governor timeline.
Postcondition
At most one backoff level is removed.
A valid Retry-After never moves the gate backward.
Note
Not thread-safe: mutates the host record.
Since
0.1.0

Definition at line 634 of file mdl_politeness.c.

References mdl_host_rec_t::backoff_level, mdl_governor_t::cfg, mdl_gov_cfg_t::decay_after, mdl_host_rec_t::earliest_next_ms, internal_max_i64(), RA8_INTERNAL, and mdl_host_rec_t::success_streak.

Referenced by mdl_governor_observe_at_wall().

◆ internal_gov_on_throttle()

void internal_gov_on_throttle ( mdl_governor_t * g,
mdl_host_rec_t * rec,
int64_t now,
uint32_t retry_ms )
static

Apply a throttle: raise the backoff level and push the gate forward.

Applies capped exponential full jitter while allowing a longer Retry-After to win.

Parameters
[in,out]gGovernor whose jitter state advances.
[in,out]recMatching host record.
[in]nowCurrent monotonic time in milliseconds.
[in]retry_msParsed Retry-After delay, or zero.
Returns
Nothing.
Precondition
g and rec are non-NULL and associated.
now is on the governor timeline.
Postcondition
The backoff level is raised at most to its ceiling.
The earliest-next gate never moves backward.
Note
Not thread-safe: mutates governor and host state.
Since
0.1.0

Definition at line 606 of file mdl_politeness.c.

References mdl_host_rec_t::backoff_level, mdl_governor_t::cfg, mdl_host_rec_t::earliest_next_ms, internal_draw_range(), internal_gov_backoff_window(), internal_max_i64(), k_mdl_gov_level_max, mdl_governor_t::rng, and mdl_host_rec_t::success_streak.

Referenced by mdl_governor_observe_at_wall().

◆ internal_gov_schedule()

int64_t internal_gov_schedule ( mdl_governor_t * g,
mdl_host_rec_t * rec,
int64_t now )
static

Refill credit to now, gate on rate + backoff, consume one token.

Caps accrued credit, selects the later rate/backoff gate, and charges one request.

Parameters
[in]gInitialised governor configuration.
[in,out]recHost record to schedule.
[in]nowCurrent monotonic time in milliseconds.
Returns
Required wait before the request may start.
Return values
0The request may start immediately.
otherPositive wait in milliseconds.
Precondition
g and rec are non-NULL and belong to the same governor.
now is on the governor's monotonic timeline.
Postcondition
Credit remains within its configured capacity.
rec records the scheduled start and consumed token.
Note
Not thread-safe: mutates rec.
Since
0.1.0

Definition at line 470 of file mdl_politeness.c.

References mdl_governor_t::cfg, mdl_host_rec_t::credit_ms, mdl_host_rec_t::earliest_next_ms, internal_gov_cap_ms(), internal_gov_interval_ms(), internal_max_i64(), internal_min_i64(), and mdl_host_rec_t::last_ms.

Referenced by mdl_governor_acquire().

◆ internal_gov_sleep()

void internal_gov_sleep ( mdl_governor_t * g,
int64_t ms )
static

Sleep ms through the injected sleeper, else the host clock.

Ignores non-positive delays and saturates positive delays to uint32_t.

Parameters
[in,out]gInitialised governor containing the optional sleeper seam.
[in]msRequested signed delay in milliseconds.
Returns
Nothing.
Precondition
g is non-NULL and initialised.
The caller permits a positive delay to block.
Postcondition
Non-positive input performs no sleep.
Positive input requests exactly one bounded sleep.
Note
Thread safety follows the injected sleeper implementation.
Since
0.1.0

Definition at line 365 of file mdl_politeness.c.

References internal_host_sleep_ms(), RA8_INTERNAL, mdl_governor_t::sleep_ctx, and mdl_governor_t::sleep_fn.

Referenced by mdl_governor_acquire().

◆ internal_host_sleep_ms()

void internal_host_sleep_ms ( uint32_t ms)
static

Block for ms milliseconds on the host clock.

Converts milliseconds to timespec and delegates to nanosleep.

Parameters
[in]msRequested duration in milliseconds.
Returns
Nothing.
Precondition
ms is a finite uint32_t duration.
The caller permits the current thread to block.
Postcondition
One host sleep has been requested.
No caller-owned state is modified.
Note
An interrupted sleep is not resumed.
Since
0.1.0

Definition at line 127 of file mdl_politeness.c.

References k_ms_per_s, k_ns_per_ms, and RA8_INTERNAL.

Referenced by internal_gov_sleep(), and mdl_politeness_wait().

◆ internal_max_i64()

int64_t internal_max_i64 ( int64_t a,
int64_t b )
static

Larger of two signed millisecond values.

Performs a direct signed comparison.

Parameters
[in]aFirst value.
[in]bSecond value.
Returns
The larger value.
Return values
otherEither a or b.
Precondition
Both arguments use the same unit and timeline.
Signed comparison is the intended ordering.
Postcondition
No state is modified.
The result is no less than either argument.
Note
Thread-safe: pure arithmetic.
Since
0.1.0

Definition at line 317 of file mdl_politeness.c.

References RA8_INTERNAL.

Referenced by internal_gov_on_success(), internal_gov_on_throttle(), and internal_gov_schedule().

◆ internal_min_i64()

int64_t internal_min_i64 ( int64_t a,
int64_t b )
static

Smaller of two signed millisecond values.

Performs a direct signed comparison.

Parameters
[in]aFirst value.
[in]bSecond value.
Returns
The smaller value.
Return values
otherEither a or b.
Precondition
Both arguments use the same unit and timeline.
Signed comparison is the intended ordering.
Postcondition
No state is modified.
The result is no greater than either argument.
Note
Thread-safe: pure arithmetic.
Since
0.1.0

Definition at line 298 of file mdl_politeness.c.

References RA8_INTERNAL.

Referenced by internal_gov_schedule().

◆ internal_ms_from_secs()

uint32_t internal_ms_from_secs ( int64_t secs)
static

Saturating conversion of a signed-seconds delay into a ms delay.

Clamps past dates to zero and values above the millisecond range to UINT32_MAX.

Parameters
[in]secsSigned duration in seconds.
Returns
Saturated duration in milliseconds.
Return values
0secs is non-positive.
UINT32_MAXThe converted value would overflow.
Precondition
secs uses the same second scale as the parsed date.
Negative values represent elapsed deadlines.
Postcondition
The return is within the uint32_t range.
No state is modified.
Note
Thread-safe: pure arithmetic.
Since
0.1.0

Definition at line 187 of file mdl_politeness.c.

References k_ms_per_s, and RA8_INTERNAL.

Referenced by internal_parse_http_date(), and mdl_retry_after_parse().

◆ internal_next_rand()

uint64_t internal_next_rand ( uint64_t * state)
static

Advance an xorshift64 state in place and return the new value.

Applies the fixed three-shift recurrence used by every jitter draw.

Parameters
[in,out]stateNon-zero PRNG state.
Returns
The next PRNG value.
Return values
otherUpdated non-zero xorshift64 state.
Precondition
state points to writable storage.
*state is non-zero.
Postcondition
*state equals the returned value.
Exactly one PRNG step has been consumed.
Note
Not thread-safe when callers share state.
Since
0.1.0

Definition at line 79 of file mdl_politeness.c.

References k_xs_shift_a, k_xs_shift_b, k_xs_shift_c, and RA8_INTERNAL.

Referenced by internal_draw_range().

◆ internal_parse_http_date()

bool internal_parse_http_date ( const char * value,
int64_t now_wall_s,
uint32_t * out_ms )
static

Parse an HTTP-date Retry-After (IMF-fixdate or RFC 850) into a delay.

Tries both supported UTC formats and saturates the deadline delta to milliseconds.

Parameters
[in]valueNUL-terminated HTTP-date text.
[in]now_wall_sCurrent Unix wall-clock time in seconds.
[out]out_msParsed non-negative delay.
Returns
Whether a supported date parsed successfully.
Return values
trueout_ms was written.
falseNeither date format was valid.
Precondition
value and out_ms are non-NULL.
now_wall_s and the parsed date share the Unix epoch.
Postcondition
On true, out_ms contains a saturated delay.
On false, out_ms is unchanged.
Note
Thread-safe on platforms providing thread-safe timegm/strptime.
Since
0.1.0

Definition at line 242 of file mdl_politeness.c.

References internal_ms_from_secs().

Referenced by mdl_retry_after_parse().

◆ mdl_gov_cfg_default()

mdl_gov_cfg_t mdl_gov_cfg_default ( void )

Conservative default tunables for a site that configures none.

A cautious ceiling that a careful human reader would not exceed: roughly one request per second sustained with a small burst, one-second base backoff doubling to a one-minute cap, and strictly serial per host. A site descriptor may raise or lower any field.

Returns
The default mdl_gov_cfg_t (60 req/min, burst 4, 1s..60s backoff, decay after 4 successes, 1 in-flight).
Return values
(byvalue) Never fails; always returns the conservative defaults.
Precondition
None.
The caller accepts the documented conservative constants.
Postcondition
burst >= 1 and max_inflight >= 1 in the returned config.
rate_per_min > 0, so rate limiting is on by default.
Note
Thread-safe: returns a value computed from constants.
Since
0.1.0

Definition at line 484 of file mdl_politeness.c.

References k_def_backoff_base_ms, k_def_backoff_max_ms, k_def_burst, k_def_decay_after, k_def_max_inflight, and k_def_rate_per_min.

Referenced by internal_config_set_defaults(), mdl_config_gov_cfg(), and mdl_governor_init_clock().

◆ mdl_governor_acquire()

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.

The gate every request passes before hitting the network. In order it (1) refuses – without sleeping – when host already has max_inflight requests in flight, so a future parallel loop is bounded per host; (2) computes the token-bucket wait so the sustained rate stays under rate_per_min with up to burst back-to-back; (3) raises that to the host's backoff / Retry-After gate if it is later; (4) adds a jittered spacing delay drawn from [jitter_min_ms, jitter_max_ms]; then sleeps the total through the injected clock and marks one request in flight. On success the caller MUST later call mdl_governor_release. A NULL governor is a no-op that succeeds (politeness disabled), matching a NULL jitter source.

Parameters
[in,out]gGovernor, or NULL to disable pacing.
[in]hostHost key (e.g. from mdl_url_host); may be NULL.
[in]jitter_min_msBaseline spacing floor (e.g. the site img delay).
[in]jitter_max_msBaseline spacing ceiling (clamped up to the floor).
Returns
Whether the slot was reserved.
Return values
k_ra8_okReserved; the request may proceed (release later).
k_ra8_err_would_blockhost is at its in-flight cap; try again later.
Precondition
g, when non-NULL, was initialised by a governor init function.
The caller pairs a k_ra8_ok with exactly one mdl_governor_release.
Postcondition
On k_ra8_ok the host's in-flight count is one higher.
On k_ra8_err_would_block no time was slept and no state changed.
Note
Not thread-safe: mutates the per-host table and advances the clock.
See also
mdl_governor_release
mdl_governor_observe
Since
0.1.0

Definition at line 527 of file mdl_politeness.c.

References mdl_governor_t::cfg, mdl_host_rec_t::inflight, internal_draw_range(), internal_gov_get(), internal_gov_now(), internal_gov_schedule(), internal_gov_sleep(), k_ra8_err_would_block, k_ra8_ok, mdl_gov_cfg_t::max_inflight, and mdl_governor_t::rng.

Referenced by internal_discover_fetch(), internal_mdl_fetch_governed_get_body(), and priv_mdl_fetch_cache_get_buf().

◆ mdl_governor_init()

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.

Equivalent to mdl_governor_init_clock with NULL clock seams: time comes from the host monotonic clock and mdl_governor_acquire blocks on nanosleep. Clamps cfg->burst and cfg->max_inflight up to 1 and seeds the jitter PRNG.

Parameters
[out]gGovernor to initialise (non-NULL).
[in]cfgTunables to copy, or NULL for mdl_gov_cfg_default.
[in]seedJitter seed; 0 is remapped to a non-zero constant.
Returns
Nothing.
Precondition
g, when non-NULL, points to writable mdl_governor_t storage.
A NULL g is a tolerated no-op.
Postcondition
The per-host table is empty and g->rng is non-zero.
g->cfg.burst >= 1 and g->cfg.max_inflight >= 1.
Note
Not thread-safe: initialises caller storage.
See also
mdl_governor_init_clock
Since
0.1.0

Definition at line 496 of file mdl_politeness.c.

References mdl_governor_init_clock().

Referenced by internal_run_prepared(), and mdl_app_run_discover().

◆ mdl_governor_init_clock()

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).

The dependency-injection entry point. Wires now_fn as the time source and sleep_fn as the sleeper, so a test drives the whole governor against a virtual clock with no real delay. NULL seams select the host clock/sleep, making this a strict superset of mdl_governor_init.

Parameters
[out]gGovernor to initialise (non-NULL).
[in]cfgTunables to copy, or NULL for mdl_gov_cfg_default.
[in]seedJitter seed; 0 is remapped to a non-zero constant.
[in]now_fnInjected monotonic clock, or NULL for the host clock.
[in]now_ctxContext forwarded to now_fn (may be NULL).
[in]sleep_fnInjected sleeper, or NULL for the host clock.
[in]sleep_ctxContext forwarded to sleep_fn (may be NULL).
Returns
Nothing.
Precondition
g, when non-NULL, points to writable mdl_governor_t storage.
now_ctx / sleep_ctx outlive every governor call on g.
Postcondition
The per-host table is empty and g->rng is non-zero.
g->now_fn/g->sleep_fn equal the arguments (NULL = host default).
Note
Not thread-safe: initialises caller storage.
Since
0.1.0

Definition at line 501 of file mdl_politeness.c.

References mdl_gov_cfg_t::burst, mdl_governor_t::cfg, k_seed_fallback, mdl_gov_cfg_t::max_inflight, mdl_gov_cfg_default(), memset(), mdl_governor_t::now_ctx, mdl_governor_t::now_fn, mdl_governor_t::rng, mdl_governor_t::sleep_ctx, and mdl_governor_t::sleep_fn.

Referenced by mdl_governor_init().

◆ mdl_governor_observe()

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.

Closes the loop. On a throttle (status 429 or 503) it raises backoff_level by one (capped), computes an exponential window min(base << (level-1), ceil), draws a full-jitter delay in [0, window], and sets the host's earliest-next gate to now + max(jittered_backoff, Retry-After) – so Retry-After wins whenever it is longer, in both delta-seconds and HTTP-date forms. On any non-throttle outcome it counts a success and, after decay_after of them, drops one backoff level, easing back toward the base rate; a non-throttle response that still carries Retry-After is honoured as a floor. Call once per request, after the fetch, before the next acquire to the same host observes the new gate.

Parameters
[in,out]gGovernor, or NULL (no-op).
[in]hostHost key of the completed request; may be NULL.
[in]statusHTTP status observed (0 if none / transport error).
[in]retry_afterRaw Retry-After header, or NULL/"" if absent.
Returns
Nothing.
Precondition
g, when non-NULL, was initialised by a governor init function.
status and retry_after come from the just-finished request.
Postcondition
A 429/503 raises backoff_level and pushes earliest_next_ms forward.
A non-throttle outcome advances the success streak and may decay a level.
Note
Not thread-safe: mutates the per-host table and reads the clock.
See also
mdl_governor_acquire
mdl_retry_after_parse
Since
0.1.0

Definition at line 678 of file mdl_politeness.c.

References mdl_governor_observe_at_wall().

Referenced by internal_discover_fetch(), internal_mdl_fetch_governed_get_body(), and priv_mdl_fetch_cache_get_buf().

◆ mdl_governor_observe_at_wall()

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.

This is the deterministic-clock form of mdl_governor_observe. The governor still schedules gates against its monotonic clock, but parses an HTTP-date Retry-After relative to now_wall_s. Production callers should use mdl_governor_observe; tests and platforms with an injected wall clock may use this entry point.

Parameters
[in,out]gGovernor, or NULL (no-op).
[in]hostHost key of the completed request; may be NULL.
[in]statusHTTP status observed.
[in]retry_afterRaw Retry-After header, or NULL/empty.
[in]now_wall_sCurrent Unix wall-clock time in seconds.
Returns
Nothing.
Precondition
g, when non-NULL, was initialised by a governor init function.
status, retry_after, and now_wall_s describe the same completed request.
Postcondition
A 429/503 raises the host backoff and advances its earliest-next gate.
Other outcomes advance the success streak and may decay one backoff level.
Note
Not thread-safe: mutates the per-host table and reads the monotonic clock.
Since
0.1.0

Definition at line 652 of file mdl_politeness.c.

References internal_gov_get(), internal_gov_now(), internal_gov_on_success(), internal_gov_on_throttle(), k_http_too_many_req, k_http_unavailable, and mdl_retry_after_parse().

Referenced by mdl_governor_observe().

◆ mdl_governor_peek()

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.

Read-only introspection into the per-host table, used by the unit tests to assert backoff growth/decay and gate scheduling deterministically, and usable by a caller that wants to log a host's throttle state. Does not create a record: an unknown host reports false and leaves the outputs untouched.

Parameters
[in]gGovernor, or NULL.
[in]hostHost key to look up; may be NULL.
[out]backoff_levelReceives the consecutive-throttle level. May be NULL.
[out]earliest_next_msReceives the earliest-next gate (mono-ms). May be NULL.
Returns
Whether a record exists for host.
Return values
trueA record exists; the requested outputs were written.
falseNo record for host (or NULL argument); outputs untouched.
Precondition
g, when non-NULL, was initialised by a governor init function.
The caller treats a false return as "host not yet seen".
Postcondition
No governor state is modified.
Output pointers are written only when the function returns true.
Note
Not thread-safe: reads the per-host table.
Since
0.1.0

Definition at line 683 of file mdl_politeness.c.

References mdl_host_rec_t::backoff_level, mdl_host_rec_t::earliest_next_ms, mdl_host_rec_t::host, mdl_governor_t::hosts, k_mdl_gov_max_hosts, strcmp(), and mdl_host_rec_t::used.

◆ mdl_governor_release()

void mdl_governor_release ( mdl_governor_t * g,
const char * host )

Release the in-flight slot reserved by a matching mdl_governor_acquire.

Decrements the matching host record's in-flight count when it is non-zero. NULL arguments, unknown hosts, and unmatched releases are tolerated no-ops so cleanup paths can remain unconditional.

Parameters
[in,out]gGovernor, or NULL (no-op).
[in]hostHost key passed to the paired acquire; may be NULL.
Returns
Nothing.
Precondition
Called exactly once per k_ra8_ok from mdl_governor_acquire.
g, when non-NULL, was initialised by a governor init function.
Postcondition
The host's in-flight count is one lower (never below zero).
No time is slept.
Note
Not thread-safe: mutates the per-host table.
See also
mdl_governor_acquire
Since
0.1.0

Definition at line 552 of file mdl_politeness.c.

References mdl_host_rec_t::inflight, and internal_gov_find().

Referenced by internal_discover_fetch(), internal_mdl_fetch_governed_get_body(), and priv_mdl_fetch_cache_get_buf().

◆ mdl_politeness_init()

void mdl_politeness_init ( mdl_politeness_t * p,
uint64_t seed )

Seed the jitter source, using the real host clock for sleeps.

Equivalent to mdl_politeness_init_clock with a NULL sleeper: the jitter is seeded deterministically and mdl_politeness_wait blocks on nanosleep.

Parameters
[in,out]pState to initialise (must be non-NULL).
[in]seedAny value; 0 is remapped to a non-zero constant.
Returns
Nothing.
Precondition
p, when non-NULL, points to writable mdl_politeness_t storage.
A NULL p is a tolerated no-op.
Postcondition
p->state is non-zero and p->sleep_fn is NULL.
The same seed yields the same delay sequence.
Note
Not thread-safe: initialises caller storage.
Since
0.1.0

Definition at line 48 of file mdl_politeness.c.

References mdl_politeness_init_clock().

Referenced by internal_download_page_images().

◆ mdl_politeness_init_clock()

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.

The dependency-injection entry point. Wires sleep_fn / sleep_ctx as the sleeper mdl_politeness_wait calls, so a test drives spacing/backoff timing through a fake clock with no real delay. A NULL sleep_fn selects the host clock, making this a strict superset of mdl_politeness_init.

Parameters
[in,out]pState to initialise (must be non-NULL).
[in]seedAny value; 0 is remapped to a non-zero constant.
[in]sleep_fnInjected sleeper, or NULL for the host clock.
[in]sleep_ctxContext forwarded to sleep_fn (may be NULL).
Returns
Nothing.
Precondition
p, when non-NULL, points to writable mdl_politeness_t storage.
sleep_ctx outlives every mdl_politeness_wait call on p.
Postcondition
p->state is non-zero; p->sleep_fn/p->sleep_ctx equal the args.
The same seed yields the same delay sequence regardless of clock.
Note
Not thread-safe: initialises caller storage.
Since
0.1.0

Definition at line 53 of file mdl_politeness.c.

References k_seed_fallback, mdl_politeness_t::sleep_ctx, mdl_politeness_t::sleep_fn, and mdl_politeness_t::state.

Referenced by mdl_politeness_init().

◆ mdl_politeness_wait()

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.

Draws the next xorshift64 value, maps it into [min_ms, max_ms], then blocks for that long – through the injected mdl_sleep_fn when one was wired, else on the host nanosleep. The returned delay is exactly the one requested of the sleeper, so a test with a recording fake can assert the timing without any wall-clock time elapsing.

Parameters
[in,out]pJitter source (may be NULL).
[in]min_msLower bound, milliseconds.
[in]max_msUpper bound, milliseconds (clamped up to min_ms).
Returns
The delay actually requested, in milliseconds.
Return values
0p is NULL (no sleep is performed).
otherA value in [min_ms, max(min_ms, max_ms)].
Precondition
p, when non-NULL, was seeded by an init function.
A NULL p returns 0 without sleeping.
Postcondition
p->state has advanced by exactly one PRNG step (non-NULL p).
The sleeper (real or injected) was asked for the returned duration.
Note
Not thread-safe: advances p->state.
Since
0.1.0

Definition at line 134 of file mdl_politeness.c.

References internal_draw_range(), internal_host_sleep_ms(), mdl_politeness_t::sleep_ctx, mdl_politeness_t::sleep_fn, and mdl_politeness_t::state.

Referenced by internal_download_page_image().

◆ mdl_retry_after_parse()

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.

Accepts both forms RFC 7231 defines: a non-negative delta-seconds integer ("120"), returned as 120000; and an HTTP-date, whose delay is the date minus now_wall_s (clamped at zero, so a past date yields 0). Both the preferred IMF-fixdate ("Sun, 06 Nov 1994 08:49:37 GMT") and the obsolete RFC 850 form are recognised. A pure function with no clock of its own, so a test drives both forms deterministically by supplying now_wall_s.

Parameters
[in]valueRaw header value (leading spaces tolerated), or NULL.
[in]now_wall_sCurrent wall-clock time in seconds (for the date form).
[out]out_msReceives the delay in milliseconds (non-NULL).
Returns
Whether value parsed as a valid Retry-After.
Return values
trueParsed; *out_ms holds the delay (saturated at UINT32_MAX ms).
falseNULL/empty/unparseable input; *out_ms is left unchanged.
Precondition
out_ms is non-NULL for a result to be written.
now_wall_s is the seconds the date form is measured against.
Postcondition
On false the output is not modified.
On true *out_ms is a non-negative millisecond delay.
Note
Thread-safe: depends only on its arguments (uses timegm, not mktime).
Since
0.1.0

Definition at line 262 of file mdl_politeness.c.

References internal_all_digits(), internal_ms_from_secs(), internal_parse_http_date(), and k_dec_base.

Referenced by mdl_governor_observe_at_wall().