nx_retry_policy.nx
buildroot/runtime/nx_retry_policy.nx
about
nx_retry_policy.nx -- exponential backoff + jitter + max-attempts.
module: nishi-core.ingest.retry_policy
depends: nishi-core.io.syscalls, nishi-core.io.iso8601
disk_kb: 5
capability: CORE_IO
license_tier: PUBLIC_NISHI_SUBSTRATE
genealogy_id: aws_architecture_blog_exponential_backoff_jitter_2015 +
polly_microsoft_resilience_library +
google_sre_book_retry_amplification +
nishi_ingestion_s_class_cardinal_2026
Retry primitive with exponential backoff + jitter + max-attempts.
Composes against nx_circuit_breaker (trips when retries exhausted).
Substrate Cardinal 14 graceful-degradation: every NX-INGEST adapter
invokes this primitive to decide whether to retry a failed
upstream call + how long to wait.
===== Why exponential backoff + jitter ===========================
AWS Architectural Blog 2015: when many clients retry simultaneously
(thundering herd), they amplify the upstream load just as upstream
is recovering. Exponential backoff alone DOESN'T fix this — all
clients still retry at the same exponentially-spaced times.
JITTER (random offset within the backoff window) breaks the
synchronization.
Google SRE book: retry amplification is the #1 cause of cascading
outages. Bounded max-attempts + jittered backoff prevents
substrate from contributing to upstream failure.
===== Backoff formula ============================================
attempt_n: wait base * 2^n seconds, capped at max_wait_seconds
jitter: multiply by random(0.5, 1.5) — "full jitter" variant
total: sum across attempts capped at attempts_cap
Defaults:
base_ms = 250
dependencies 2 imports · 0 importers
imports: nx_syscalls.nxnx_iso8601.nx
imported by: nobody (leaf or entry point)
structs
| 113 | struct RetryPolicy |
consts
| 55 | const NX_RETRY_PROCEED: i64 = 1 // try the upstream call |
| 56 | const NX_RETRY_WAIT_AND_RETRY: i64 = 2 // wait then retry |
| 57 | const NX_RETRY_EXHAUSTED: i64 = 3 // max attempts reached; give up |
| 58 | const NX_RETRY_NON_RETRYABLE: i64 = 4 // error class not retryable (auth fail etc.) |
| 59 | const NX_RETRY_CIRCUIT_OPEN: i64 = 5 // breaker says short-circuit |
| 76 | const NX_RETRY_ERROR_TIMEOUT: i64 = 1 // retryable |
| 77 | const NX_RETRY_ERROR_CONN_REFUSED: i64 = 2 // retryable |
| 78 | const NX_RETRY_ERROR_5XX_SERVER: i64 = 3 // retryable (server-side) |
| 79 | const NX_RETRY_ERROR_429_RATE_LIMIT: i64 = 4 // retryable with Retry-After |
| 80 | const NX_RETRY_ERROR_4XX_CLIENT: i64 = 5 // NOT retryable (our request bad) |
| 81 | const NX_RETRY_ERROR_401_AUTH: i64 = 6 // NOT retryable (need new creds) |
| 82 | const NX_RETRY_ERROR_403_FORBIDDEN: i64 = 7 // NOT retryable |
| 83 | const NX_RETRY_ERROR_404_NOT_FOUND: i64 = 8 // NOT retryable (doesn't exist) |
| 84 | const NX_RETRY_ERROR_TLS_FAIL: i64 = 9 // retryable (transient TLS issue) |
| 85 | const NX_RETRY_ERROR_DNS_FAIL: i64 = 10 // retryable |
| 86 | const NX_RETRY_ERROR_NETWORK_OTHER: i64 = 11 // retryable conservatively |
| 132 | const NX_RETRY_POLICY_BYTES: i64 = 96 // 12 fields * 8 bytes |
| 136 | const NX_RETRY_DEFAULT_BASE_WAIT_MS: i64 = 250 |
| 137 | const NX_RETRY_DEFAULT_MAX_WAIT_SEC: i64 = 60 |
| 138 | const NX_RETRY_DEFAULT_MAX_ATTEMPTS: i64 = 7 |
functions
| 61 | func nx_retry_verdict_name(v: i64) -> *u8 |
| 88 | func nx_retry_error_class_name(c: i64) -> *u8 |
| 103 | func nx_retry_error_is_retryable(c: i64) -> i64 called by 1: nx_retry_policy_decide |
| 142 | func nx_retry_policy_new(descriptor_hk: i64) -> *RetryPolicy calls 1: sys_mmap |
| 169 | func nx_retry_compute_wait_seconds(p: *RetryPolicy) -> i64 called by 1: nx_retry_policy_decide |
| 189 | func nx_retry_policy_decide( |
| 224 | func nx_retry_policy_record_success(p: *RetryPolicy) -> i64 |
| 238 | func nx_retry_apply_retry_after(p: *RetryPolicy, retry_after_seconds: i64) -> i64 |