Answer A: OpenAI GPT-5.6
from future import annotations
import threading
import unittest
from collections import deque
from dataclasses import dataclass, field
from typing import Deque, Dict, Hashable, Optional
@dataclass(frozen=True)
class TenantConfig:
"""Rate limits for one tenant.
client_limit applies independently to each client, while tenant_limit applies
to all admitted requests from the tenant. Both limits use exact rolling
windows. active_timeout_ms bounds how long an absent fairness waiter can
retain its turn. eviction_ms controls client-state retention.
"""
client_limit: int
client_wind...
Show Full Answer ▼
from future import annotations
import threading
import unittest
from collections import deque
from dataclasses import dataclass, field
from typing import Deque, Dict, Hashable, Optional
@dataclass(frozen=True)
class TenantConfig:
"""Rate limits for one tenant.
client_limit applies independently to each client, while tenant_limit applies
to all admitted requests from the tenant. Both limits use exact rolling
windows. active_timeout_ms bounds how long an absent fairness waiter can
retain its turn. eviction_ms controls client-state retention.
"""
client_limit: int
client_window_ms: int
tenant_limit: int
tenant_window_ms: int
active_timeout_ms: int = 60_000
eviction_ms: int = 120_000
def validate(self) -> None:
if self.client_limit <= 0 or self.tenant_limit <= 0:
raise ValueError("limits must be positive")
if self.client_window_ms <= 0 or self.tenant_window_ms <= 0:
raise ValueError("window lengths must be positive")
if self.active_timeout_ms <= 0:
raise ValueError("active_timeout_ms must be positive")
if self.eviction_ms < max(self.client_window_ms, self.tenant_window_ms):
raise ValueError("eviction_ms must be at least as large as both windows")
if self.eviction_ms < self.active_timeout_ms:
raise ValueError("eviction_ms must be at least active_timeout_ms")
@dataclass(frozen=True)
class AllowResult:
allowed: bool
retry_after_ms: int = 0
@dataclass
class _ClientState:
admitted: Deque[int] = field(default_factory=deque)
last_seen_ms: int = 0
@dataclass
class _TenantState:
admitted: Deque[int] = field(default_factory=deque)
clients: Dict[Hashable, _ClientState] = field(default_factory=dict)
waiters: Deque[Hashable] = field(default_factory=deque)
waiter_set: set[Hashable] = field(default_factory=set)
last_now_ms: Optional[int] = None
last_seen_ms: int = 0
class SlidingWindowRateLimiter:
"""Thread-safe, single-process, exact sliding-window rate limiter.
Sliding-window algorithm:
Every admitted request timestamp is stored in a deque. Before evaluating a
request, timestamps t satisfying t <= now - window are removed. Thus a
request exactly one full window old no longer consumes capacity. This is a
true log-based sliding window, so it has no calendar-bucket boundary burst.
The result is exact, with O(1) amortized deque operations and memory
proportional to admitted requests still inside configured windows. This is
more accurate than weighted buckets but uses more memory. Limits provide a
hard bound on live timestamp entries, and stale client objects are evicted.
Fair sharing:
Uncontended tenant capacity is work-conserving and can be used by any
client. Once the tenant ceiling is reached, denied clients enter a FIFO
queue with at most one entry per client. As capacity expires, the head gets
the next admission, then leaves the queue. A continuously busy client must
rejoin behind other contenders, implementing round-robin redistribution
without permanently reserving unused per-client shares. Inactive queue
entries expire after active_timeout_ms so an abandoned turn cannot block
the tenant forever.
Clock behavior:
now_ms should come from a monotonic clock. If a caller supplies a duplicate
or decreasing timestamp, time is clamped to the tenant's greatest observed
timestamp. This prevents expired history from becoming live again.
Concurrency and deployment assumptions:
A single re-entrant lock makes allow, configuration, and sweeping atomic
across threads and async tasks sharing this object. For distributed use,
the same state transition can be implemented atomically in Redis with a
Lua script and sorted sets, or in a transactional strongly consistent
store. FIFO waiter metadata must be updated in the same transaction as the
request logs; local locks alone do not coordinate multiple processes.
"""
def __init__(self, default_config: Optional[TenantConfig] = None) -> None:
if default_config is not None:
default_config.validate()
self._default_config = default_config
self._configs: Dict[Hashable, TenantConfig] = {}
self._tenants: Dict[Hashable, _TenantState] = {}
self._lock = threading.RLock()
def configure_tenant(self, tenant_id: Hashable, config: TenantConfig) -> None:
"""Create or replace a tenant's configuration atomically."""
config.validate()
with self._lock:
self._configs[tenant_id] = config
def allow(self, tenant_id: Hashable, client_id: Hashable, now_ms: int) -> AllowResult:
"""Atomically decide whether to admit one request.
retry_after_ms is exact for a direct client/window or tenant/window
denial. For a FIFO fairness denial it is the earliest temporal retry;
actual admission also depends on earlier active waiters taking or
abandoning their turns.
"""
if not isinstance(now_ms, int):
raise TypeError("now_ms must be an integer")
with self._lock:
cfg = self._config_for(tenant_id)
tenant = self._tenants.get(tenant_id)
if tenant is None:
tenant = _TenantState(last_now_ms=now_ms, last_seen_ms=now_ms)
self._tenants[tenant_id] = tenant
now = self._normalize_time(tenant, now_ms)
tenant.last_seen_ms = now
self._prune(tenant.admitted, now, cfg.tenant_window_ms)
client = tenant.clients.get(client_id)
if client is None:
client = _ClientState(last_seen_ms=now)
tenant.clients[client_id] = client
client.last_seen_ms = now
self._prune(client.admitted, now, cfg.client_window_ms)
self._purge_waiters(tenant, cfg, now)
self._evict_clients(tenant, cfg, now, preserve=client_id)
if len(client.admitted) >= cfg.client_limit:
self._remove_waiter(tenant, client_id)
retry = client.admitted[0] + cfg.client_window_ms - now
return AllowResult(False, max(0, retry))
if len(tenant.admitted) >= cfg.tenant_limit:
self._enqueue_waiter(tenant, client_id)
retry = tenant.admitted[0] + cfg.tenant_window_ms - now
return AllowResult(False, max(0, retry))
# Capacity exists. During contention, only the FIFO head may take it.
if tenant.waiters and tenant.waiters[0] != client_id:
self._enqueue_waiter(tenant, client_id)
return AllowResult(False, 0)
if tenant.waiters and tenant.waiters[0] == client_id:
tenant.waiters.popleft()
tenant.waiter_set.remove(client_id)
client.admitted.append(now)
tenant.admitted.append(now)
return AllowResult(True, 0)
def sweep(self, now_ms: int) -> int:
"""Evict stale clients and empty tenant runtime states.
Applications can call this periodically when traffic is low. Normal
allow calls also clean the tenant being accessed. Returns the number of
client and tenant runtime objects removed. Configurations are retained.
"""
if not isinstance(now_ms, int):
raise TypeError("now_ms must be an integer")
removed = 0
with self._lock:
for tenant_id in list(self._tenants):
cfg = self._config_for(tenant_id)
tenant = self._tenants[tenant_id]
now = self._normalize_time(tenant, now_ms)
self._prune(tenant.admitted, now, cfg.tenant_window_ms)
self._purge_waiters(tenant, cfg, now)
before = len(tenant.clients)
self._evict_clients(tenant, cfg, now, preserve=None)
removed += before - len(tenant.clients)
if (not tenant.clients and not tenant.admitted and not tenant.waiters
and now - tenant.last_seen_ms >= cfg.eviction_ms):
del self._tenants[tenant_id]
removed += 1
return removed
def debug_state(self, tenant_id: Hashable) -> dict[str, int]:
"""Return aggregate state sizes, useful for monitoring and tests."""
with self._lock:
tenant = self._tenants.get(tenant_id)
if tenant is None:
return {"clients": 0, "tenant_events": 0, "waiters": 0,
"client_events": 0}
return {
"clients": len(tenant.clients),
"tenant_events": len(tenant.admitted),
"waiters": len(tenant.waiters),
"client_events": sum(len(c.admitted) for c in tenant.clients.values()),
}
def _config_for(self, tenant_id: Hashable) -> TenantConfig:
cfg = self._configs.get(tenant_id, self._default_config)
if cfg is None:
raise KeyError(f"tenant {tenant_id!r} has no configuration")
return cfg
@staticmethod
def _normalize_time(tenant: _TenantState, supplied: int) -> int:
if tenant.last_now_ms is None or supplied >= tenant.last_now_ms:
tenant.last_now_ms = supplied
return supplied
return tenant.last_now_ms
@staticmethod
def _prune(events: Deque[int], now: int, window_ms: int) -> None:
boundary = now - window_ms
while events and events[0] <= boundary:
events.popleft()
@staticmethod
def _enqueue_waiter(tenant: _TenantState, client_id: Hashable) -> None:
if client_id not in tenant.waiter_set:
tenant.waiters.append(client_id)
tenant.waiter_set.add(client_id)
@staticmethod
def _remove_waiter(tenant: _TenantState, client_id: Hashable) -> None:
if client_id not in tenant.waiter_set:
return
tenant.waiters = deque(x for x in tenant.waiters if x != client_id)
tenant.waiter_set.remove(client_id)
def _purge_waiters(self, tenant: _TenantState, cfg: TenantConfig, now: int) -> None:
kept: Deque[Hashable] = deque()
kept_set: set[Hashable] = set()
for client_id in tenant.waiters:
client = tenant.clients.get(client_id)
if client is None or now - client.last_seen_ms >= cfg.active_timeout_ms:
continue
self._prune(client.admitted, now, cfg.client_window_ms)
# A client blocked by its own quota does not reserve a tenant turn.
if len(client.admitted) >= cfg.client_limit:
continue
kept.append(client_id)
kept_set.add(client_id)
tenant.waiters = kept
tenant.waiter_set = kept_set
@staticmethod
def _evict_clients(tenant: _TenantState, cfg: TenantConfig, now: int,
preserve: Optional[Hashable]) -> None:
for client_id in list(tenant.clients):
if preserve is not None and client_id == preserve:
continue
client = tenant.clients[client_id]
boundary = now - cfg.client_window_ms
while client.admitted and client.admitted[0] <= boundary:
client.admitted.popleft()
if (not client.admitted and client_id not in tenant.waiter_set
and now - client.last_seen_ms >= cfg.eviction_ms):
del tenant.clients[client_id]
class SlidingWindowRateLimiterTests(unittest.TestCase):
def make_limiter(self, **overrides: int) -> SlidingWindowRateLimiter:
values = dict(client_limit=3, client_window_ms=1000,
tenant_limit=20, tenant_window_ms=1000,
active_timeout_ms=1000, eviction_ms=2000)
values.update(overrides)
return SlidingWindowRateLimiter(TenantConfig(**values))
def test_exact_window_boundary_is_expired(self) -> None:
limiter = self.make_limiter(client_limit=1)
self.assertTrue(limiter.allow("t", "c", 0).allowed)
denied = limiter.allow("t", "c", 999)
self.assertFalse(denied.allowed)
self.assertEqual(denied.retry_after_ms, 1)
self.assertTrue(limiter.allow("t", "c", 1000).allowed)
def test_idle_client_returns_after_full_window(self) -> None:
limiter = self.make_limiter(client_limit=2)
self.assertTrue(limiter.allow("t", "c", 10).allowed)
self.assertTrue(limiter.allow("t", "c", 11).allowed)
self.assertFalse(limiter.allow("t", "c", 12).allowed)
self.assertTrue(limiter.allow("t", "c", 1011).allowed)
def test_duplicate_and_backwards_clock_are_clamped(self) -> None:
limiter = self.make_limiter(client_limit=2)
self.assertTrue(limiter.allow("t", "c", 100).allowed)
self.assertTrue(limiter.allow("t", "c", 100).allowed)
denied = limiter.allow("t", "c", 50)
self.assertFalse(denied.allowed)
self.assertEqual(denied.retry_after_ms, 1000)
self.assertTrue(limiter.allow("t", "c", 1100).allowed)
def test_concurrent_requests_are_atomic(self) -> None:
limiter = self.make_limiter(client_limit=10, tenant_limit=100)
barrier = threading.Barrier(40)
results: list[bool] = []
results_lock = threading.Lock()
def worker() -> None:
barrier.wait()
value = limiter.allow("t", "same-client", 500).allowed
with results_lock:
results.append(value)
threads = [threading.Thread(target=worker) for _ in range(40)]
for thread in threads:
thread.start()
for thread in threads:
thread.join()
self.assertEqual(sum(results), 10)
self.assertEqual(len(results), 40)
def test_tenant_cap_combines_all_clients(self) -> None:
limiter = self.make_limiter(client_limit=10, tenant_limit=2)
self.assertTrue(limiter.allow("t", "a", 0).allowed)
self.assertTrue(limiter.allow("t", "b", 1).allowed)
denied = limiter.allow("t", "c", 2)
self.assertFalse(denied.allowed)
self.assertEqual(denied.retry_after_ms, 998)
def test_saturated_tenant_uses_fifo_fair_redistribution(self) -> None:
limiter = self.make_limiter(client_limit=10, tenant_limit=2)
self.assertTrue(limiter.allow("t", "a", 0).allowed)
self.assertTrue(limiter.allow("t", "a", 1).allowed)
self.assertFalse(limiter.allow("t", "b", 2).allowed)
self.assertFalse(limiter.allow("t", "c", 3).allowed)
# Both old slots expire, but a cannot monopolize them: b and c queued.
self.assertFalse(limiter.allow("t", "a", 1001).allowed)
self.assertTrue(limiter.allow("t", "b", 1001).allowed)
self.assertFalse(limiter.allow("t", "b", 1001).allowed)
self.assertTrue(limiter.allow("t", "c", 1001).allowed)
def test_abandoned_fairness_turn_eventually_expires(self) -> None:
limiter = self.make_limiter(client_limit=10, tenant_limit=1,
active_timeout_ms=1000, eviction_ms=2000)
self.assertTrue(limiter.allow("t", "a", 0).allowed)
self.assertFalse(limiter.allow("t", "gone", 1).allowed)
# At 1001 the waiter is inactive, so another client may use capacity.
self.assertTrue(limiter.allow("t", "b", 1001).allowed)
def test_eviction_removes_stale_but_not_active_clients(self) -> None:
limiter = self.make_limiter(client_window_ms=100, tenant_window_ms=100,
active_timeout_ms=100, eviction_ms=200)
self.assertTrue(limiter.allow("t", "stale", 0).allowed)
self.assertTrue(limiter.allow("t", "active", 150).allowed)
limiter.sweep(201)
state = limiter.debug_state("t")
self.assertEqual(state["clients"], 1)
self.assertEqual(state["client_events"], 1)
# The active request is not dropped before its own window expires.
self.assertFalse(limiter.allow("t", "active", 201).allowed
if limiter._default_config.client_limit == 1 else False)
self.assertEqual(limiter.debug_state("t")["clients"], 1)
limiter.sweep(351)
self.assertEqual(limiter.debug_state("t")["clients"], 0)
def test_configuration_is_per_tenant(self) -> None:
limiter = SlidingWindowRateLimiter()
limiter.configure_tenant("small", TenantConfig(1, 100, 1, 100, 100, 100))
limiter.configure_tenant("large", TenantConfig(2, 100, 3, 100, 100, 100))
self.assertTrue(limiter.allow("small", "c", 0).allowed)
self.assertFalse(limiter.allow("small", "c", 1).allowed)
self.assertTrue(limiter.allow("large", "c", 0).allowed)
self.assertTrue(limiter.allow("large", "c", 1).allowed)
def test_memory_is_bounded_by_live_limits_and_sweep(self) -> None:
limiter = self.make_limiter(client_limit=5, tenant_limit=7,
client_window_ms=100, tenant_window_ms=100,
active_timeout_ms=100, eviction_ms=100)
for index in range(100):
limiter.allow("t", f"c{index}", 0)
state = limiter.debug_state("t")
self.assertLessEqual(state["tenant_events"], 7)
self.assertLessEqual(state["client_events"], 7)
limiter.sweep(100)
self.assertEqual(limiter.debug_state("t")["clients"], 0)
if name == "main":
unittest.main()
Result
Winning Votes
3 / 3
Average Score
Total Score
Overall Comments
Answer A provides a comprehensive and robust implementation of the rate limiter, fully addressing all functional requirements, including the complex fair-sharing policy for tenant saturation. The code is well-structured, uses appropriate data structures, and includes thorough documentation and a strong test suite covering all specified edge cases. Its memory management via explicit sweep and configurable eviction is well-designed.
View Score Details ▼
Correctness
Weight 35%Answer A correctly implements all aspects of the rate limiter, including the complex fair-sharing policy for tenant saturation and robust clock handling. All limits and window logic are precise.
Completeness
Weight 20%Answer A is highly complete, addressing all functional requirements, including the nuanced fair-sharing and comprehensive memory eviction. It provides clear explanations, assumptions, and distributed design notes as requested.
Code Quality
Weight 20%The code in Answer A is well-structured, uses `dataclasses` for clear state management, and has good inline documentation. The use of `threading.RLock` is appropriate for re-entrant operations. Methods are logically separated and clean.
Practical Value
Weight 15%Answer A offers high practical value due to its complete feature set, especially the fair-sharing mechanism and controlled memory eviction via the `sweep` method. The `debug_state` method is also a useful addition for monitoring.
Instruction Following
Weight 10%Answer A meticulously follows all instructions, including the complex fair-sharing policy, true sliding window, concurrency safety, memory bounding, and addressing all specified edge cases in both code and tests. All deliverables are provided.
Total Score
Overall Comments
Answer A provides a substantially complete, runnable Python implementation with exact log-based sliding windows, per-tenant configuration, tenant-wide caps, concurrency protection, stale-state cleanup, retry calculations, explicit clock-regression handling, and a broad unittest suite. Its FIFO waiter mechanism is a credible fair-sharing policy under tenant saturation, though retry_after_ms for fairness queue denials can be imprecise and the waiter/client state for many denied unique clients could still grow until timeout or sweeping. Overall it closely matches the requested library behavior and edge cases.
View Score Details ▼
Correctness
Weight 35%Uses exact timestamp logs with correct boundary pruning, enforces both per-client and tenant caps, clamps backward time, and serializes mutations safely. The FIFO fairness logic is mostly correct, but retry_after_ms for non-head fairness denials can be 0 and memory for many waiting denied clients is only bounded over time rather than immediately.
Completeness
Weight 20%Covers the requested API, per-tenant adjustable configuration, exact sliding windows, tenant cap, fairness queue, concurrency safety, eviction/sweep, assumptions, distributed extension notes, and tests for nearly all specified edge cases. Minor gaps include imperfect fairness retry reporting and somewhat awkward eviction test code.
Code Quality
Weight 20%Well-structured dataclasses, clear public methods, validation, inline documentation, type hints, and encapsulated helper methods. The single global lock is simple and safe but limits scalability, and a few internals/tests are somewhat clunky.
Practical Value
Weight 15%Practical as a reusable single-process library with monitoring/debug state, periodic sweeping, configurable tenants, and a credible path to Redis/Lua for distributed use. The global lock and timestamp-log memory cost may limit high-scale deployments, but the tradeoffs are stated.
Instruction Following
Weight 10%Follows the requested deliverables closely: complete code, documented algorithm and tradeoffs, explicit assumptions, distributed extension, and tests for the named edge cases. The API returns an AllowResult instead of a tuple, which is acceptable under the prompt.
Total Score
Overall Comments
Answer A is a near-complete, production-quality solution. It implements an exact log-based sliding window with correct boundary semantics, a genuine fair-redistribution mechanism via a FIFO waiter queue with activity timeouts, tenant-clamped handling of backwards and duplicate clocks, a single RLock for atomicity, and bounded memory through inline eviction plus an explicit sweep API. All six mandated edge cases have targeted tests, and the docstring explains accuracy/memory tradeoffs, assumptions, and a concrete distributed extension. Weaknesses are minor: one test assertion is vacuous due to a confusing ternary, retry_after_ms of 0 on a fairness denial is ambiguous, and per-call eviction scans add overhead for tenants with many clients.
View Score Details ▼
Correctness
Weight 35%The log-based sliding window is exact: pruning uses t <= now - window so a request exactly one full window old expires, and the boundary test verifies retry_after_ms of 1 at t=999 and admission at t=1000. Backwards/duplicate clocks are clamped to the tenant's greatest observed timestamp, preventing expired history from reviving. The FIFO waiter queue provably enforces round-robin redistribution under tenant saturation (verified by tracing the saturation test: client a is blocked while queued clients b and c each get a freed slot). The single RLock makes allow atomic, and the 40-thread test admits exactly 10. Minor flaws: retry_after_ms of 0 for a fairness denial is ambiguous, and one eviction test contains a vacuous conditional assertion that never actually exercises the allow call.
Completeness
Weight 20%Every functional requirement is addressed: per-tenant configuration, exact sliding window, tenant global cap with genuine FIFO fair redistribution, thread safety, eviction via both inline cleanup and an explicit sweep API, plus a debug_state helper. All six mandated edge cases have dedicated tests (exact boundary, idle return, concurrent race, clamped backwards/duplicate clock, saturation with fair redistribution, stale eviction preserving active clients), and it additionally covers abandoned waiter turns and memory bounds. Assumptions and a Redis/Lua distributed extension are documented in the docstring.
Code Quality
Weight 20%Well-structured with frozen dataclasses for config and results, validated configuration, typed signatures, clear separation of pruning, waiter purging, and eviction helpers, and an unusually thorough class docstring covering algorithm, fairness semantics, clock behavior, and distributed extension. Weaknesses: the eviction test contains a confusing vacuous ternary assertion, per-call full-client eviction adds O(clients) overhead to every allow, and _remove_waiter rebuilds the deque in O(n).
Practical Value
Weight 15%Deployable as-is for a single process: deterministic injected timestamps make it testable, debug_state supports monitoring, sweep gives operators explicit memory control, waiter timeouts prevent abandoned clients from starving a tenant, and the distributed migration path (Redis sorted sets plus Lua, with waiter metadata in the same transaction) is concretely described. The per-allow eviction scan is the main performance caveat for tenants with many clients.
Instruction Following
Weight 10%Follows the specification closely: allow(tenant_id, client_id, now_ms) signature, retry_after_ms in the result, per-tenant adjustable config, true log-based sliding window with explicit accuracy/memory tradeoff discussion, fair redistribution under saturation, concurrency safety, bounded memory, all six edge cases in tests, and explicit assumptions plus a distributed extension note. Essentially every deliverable and edge case listed in the prompt is satisfied.