Skip to content

#451 guard Fbe::Middleware::RateLimit state with a mutex#470

Open
edmoffo wants to merge 1 commit into
zerocracy:masterfrom
edmoffo:451-rate-limit-mutex
Open

#451 guard Fbe::Middleware::RateLimit state with a mutex#470
edmoffo wants to merge 1 commit into
zerocracy:masterfrom
edmoffo:451-rate-limit-mutex

Conversation

@edmoffo
Copy link
Copy Markdown

@edmoffo edmoffo commented May 20, 2026

Closes #451.

Fbe::Middleware::RateLimit keeps mutable state (@cached, @remaining, @searchleft, @counter) in plain instance variables and updates them with non-atomic compound operations like @counter += 1 and @remaining -= 1. The middleware instance is shared across every request through the same Faraday connection, so any concurrent caller (parallel judges, fiber-based fetches, anything that uses Fbe.octo from threads) would race on these vars and lose decrements, serve stale cached /rate_limit for longer than the 100-request window intended, and let two threads through the cache-miss branch at the same time.

The change adds a Mutex around the cache-refresh path in handle_rate_limit_request and around the counter mutation in track_request, while keeping @app.call(env) outside the lock so a slow downstream request does not serialize unrelated callers. The "NOT thread-safe" header comment that documented the prior limitation is gone.

Local validation against ruby 3.3.6 on linux:

  • bundle exec rake test TEST=test/fbe/middleware/test_rate_limit.rb — 16 tests, 32 assertions, 0 failures, 0 errors, including the two new regression tests test_concurrent_non_rate_limit_requests_do_not_lose_decrements and test_concurrent_rate_limit_requests_refresh_once.
  • bundle exec rake test — 275 tests, 760 assertions, 0 new failures (10 pre-existing errors in test_octo.rb and adjacent quota-dependent tests reproduce on master in this environment and are unrelated to this change).
  • bundle exec rake rubocop — 65 files inspected, no offenses.
  • bundle exec rake yard — 81.82% documented, no new warnings.

Wrap the cache-refresh and counter-decrement paths in a single
Mutex so concurrent callers cannot lose increments on @counter,
decrements on @remaining/@searchleft, or race two threads through
the cache-miss branch of /rate_limit. Drop the "NOT thread-safe"
header comment that documented the prior limitation.

Add two regression tests in test/fbe/middleware/test_rate_limit.rb:
one fires ten threads making mixed /user and /search/issues calls
and asserts both counters drop by the expected amount, and one
fires twenty threads against /rate_limit on a cold cache and
asserts the underlying endpoint is only hit once.
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.

Fbe::Middleware::RateLimit is not thread-safe

2 participants