Skip to content

fix: atomic token-bucket refill with race-condition fixes and jitter - #57

Merged
MaryammAli merged 1 commit into
Proof-Stell:mainfrom
RUKAYAT-CODER:fix/56-atomic-rate-limit-refill
Aug 20, 2026
Merged

fix: atomic token-bucket refill with race-condition fixes and jitter#57
MaryammAli merged 1 commit into
Proof-Stell:mainfrom
RUKAYAT-CODER:fix/56-atomic-rate-limit-refill

Conversation

@RUKAYAT-CODER

Copy link
Copy Markdown
Contributor

Closes #56

Problem

The per-issuer rate limiter tracked remaining tokens with a best-effort counter that only ever decremented and never refilled, so status()/reset_at drifted from reality and concurrent .check() calls raced on the shared counter. There was no atomic read-modify-write for the bucket state and no jitter, leaving refills vulnerable to synchronized thundering-herd retries.

Change

Replace the counter with a lock-free AtomicTokenBucket:

  • Atomic CAS refill/consume. Token count (in milli-tokens) and the last-refill timestamp are packed into a single AtomicU64 and mutated with a compare_exchange retry loop, so every refill+consume is one atomic read-modify-write. Concurrent callers on the same issuer either succeed on disjoint CAS iterations or retry on a lost race — no token is lost or double-spent. Elapsed time is folded into the stored timestamp even when a call is rejected, so accrued refill is never dropped.
  • Deterministic, drift-free math. Refill is exact integer arithmetic on milli-tokens (no floating point), and the per-issuer reset time is computed deterministically.
  • Jitter. A deterministic per-issuer FNV-1a offset (no RNG, no wall-clock input, reproducible across restarts) is added to reported reset times to spread synchronized refills and break up thundering-herd retries. Configurable and validated via PER_ISSUER_RATE_LIMIT_JITTER_SECONDS.

Files

  • src/rate_limit.rsAtomicTokenBucket + deterministic_jitter; the two-tier limiter now backs tier 2 (per-issuer) with the atomic bucket. Adds concurrency and 10,000-issuer stress tests proving no token loss, plus jitter/reset-determinism and refill-after-rejection tests.
  • src/config.rs — new validated PER_ISSUER_RATE_LIMIT_JITTER_SECONDS setting (must be a valid u64 and < ISSUER_RATE_LIMIT_TTL_SECONDS).
  • src/metrics.rs — new rate_limit_tokens_refilled_total counter for time-based refills.

Acceptance criteria

  • Token refill uses atomic compare-and-swap (CAS) semantics
  • Race condition in concurrent .check() calls eliminated with no data loss
  • Jitter added to refill/reset times to prevent synchronized resets
  • Per-issuer reset time calculated deterministically (integer math, no clock drift)
  • Unit tests verify no token loss under concurrent high-frequency calls
  • Stress test with 10,000 concurrent issuers passes without token loss

Verification

Ran the repository's CI steps locally: cargo test passes (232 tests, including the new atomic/concurrency/stress tests — the 10,000-issuer stress test completes in well under a second) and cargo build --target wasm32-unknown-unknown --release succeeds; cargo check is clean across the lib and binary.

@RUKAYAT-CODER
RUKAYAT-CODER force-pushed the fix/56-atomic-rate-limit-refill branch from 07b2ed3 to 2f1378c Compare August 20, 2026 04:52
The per-issuer bucket tracked remaining tokens with a best-effort counter
that only ever decremented (never refilled), so status()/reset_at drifted
and concurrent check() calls could race on the shared counter.

Replace it with a lock-free AtomicTokenBucket: token count and last-refill
timestamp are packed into one AtomicU64 and mutated via a compare-and-swap
retry loop, so refill+consume is a single atomic read-modify-write. Racing
callers on the same issuer either succeed on disjoint CAS iterations or
retry — no token is lost or double-spent — and elapsed time is folded into
the stored timestamp even on rejection so accrued refill is never dropped.
Refill is exact integer math on milli-tokens (no float, no clock drift),
and reset times are computed deterministically with a per-issuer FNV-1a
jitter offset to spread synchronized retries (thundering herd).

- rate_limit.rs: AtomicTokenBucket, deterministic_jitter, two-tier limiter
  now backs tier 2 with the atomic bucket; adds concurrency + 10,000-issuer
  stress tests proving no token loss, plus jitter/reset determinism tests.
- config.rs: add validated PER_ISSUER_RATE_LIMIT_JITTER_SECONDS setting.
- metrics.rs: add rate_limit_tokens_refilled_total counter.

@MaryammAli MaryammAli left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@MaryammAli
MaryammAli merged commit 025fcd3 into Proof-Stell:main Aug 20, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enhance Rate Limiting with Atomic Token Refill and Race Condition Fixes

2 participants