Skip to content

Finish the rate-limit consolidation: LogThrottle + RefusalBackoff #559

Description

@emooreatx

docs/FSD_RATE_LIMIT.md §5 lists four migrations onto rate_limit::RateLimiter. Three landed in CIRISEdge#558:

  • InviteGate — had two real bugs (unbounded under identity rotation, then an O(cap) scan per rejected invite). Its original abuse tests were the acceptance criterion and failed on first migration, catching two design errors in the shared limiter itself.
  • The missing-signer queue — the fourth appearance of the O(cap)-per-rejection class, which is what prompted extracting SourceLedger.
  • The identifier-lookup budget (new) — the reason the limiter work started.

Two remain, and they are deliberately deferred rather than forgotten.

Why they were held back

LogThrottle and RefusalBackoff are, per the FSD's own table, the two implementations that never shipped a bug — they are the reference implementations for bounded keys and for backoff respectively. Migrating them is uniformity, not defect reduction.

And the FSD's claim that each is "this module with different settings" turns out to be only partly true. Absorbing them requires extending the shared module:

caller what it needs that the limiter lacks
RefusalBackoff per-class backoff parameters (transient 60s/300s vs terminal 1800s/21600s; Policy has one Backoff for all classes) · front-drop eviction — its keys are content hashes with no source attribution, so fair-by-source does not apply at all · record_at returns the window
LogThrottle Instant-based, sub-second capable; the limiter is whole-second Ts

So the shape is: make the shared primitive more complex, to take on two callers that are currently correct — inside a PR that has already absorbed ~35 findings across nine rounds. That trade is worth making deliberately, not by momentum.

One precondition already landed in #558: the limiter is now generic over its key. RefusalBackoff is consulted once per wanted hash per round, so a String-keyed limiter would have cost a heap allocation and a hex encoding per hash per round — the #547 shape exactly. A Copy key now costs nothing.

The work

  1. Move Backoff into Quota, so each class carries its own base/cap.
  2. Add an eviction mode: FairBySource (today's behaviour) | Oldest (front-drop, for source-less key spaces).
  3. Migrate RefusalBackoff internals; keep record_at/suppressed_at signatures so its ~70 call sites do not move.
  4. Migrate LogThrottle internals; keep check/check_at and ThrottleDecision.

Acceptance criterion, same as before: each caller's existing tests stay green untouched. They were written against real bugs, so they are the regression suite — and on InviteGate they were what caught the shared limiter's own errors.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions