Antwort 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...
Vollständige Antwort anzeigen ▼
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()
Ergebnis
Siegstimmen
3 / 3
Durchschnittsscore
Gesamtpunktzahl
Gesamtkommentar
Antwort A bietet eine umfassende und robuste Implementierung des Ratenbegrenzers, die alle funktionalen Anforderungen vollständig erfüllt, einschließlich der komplexen Fair-Sharing-Richtlinie für die Mietersättigung. Der Code ist gut strukturiert, verwendet geeignete Datenstrukturen und enthält eine gründliche Dokumentation sowie eine starke Testsuite, die alle spezifizierten Randfälle abdeckt. Das Speichermanagement durch explizites Sweep und konfigurierbare Eviction ist gut durchdacht.
Bewertungsdetails anzeigen ▼
Korrektheit
Gewichtung 35%Antwort A implementiert alle Aspekte des Ratenbegrenzers korrekt, einschließlich der komplexen Fair-Sharing-Richtlinie für die Mietersättigung und der robusten Handhabung von Uhren. Alle Limits und Fensterlogiken sind präzise.
Vollständigkeit
Gewichtung 20%Antwort A ist sehr vollständig und erfüllt alle funktionalen Anforderungen, einschließlich des nuancierten Fair-Sharing und der umfassenden Speicherbereinigung. Sie liefert klare Erklärungen, Annahmen und Hinweise zum verteilten Design, wie gefordert.
Codequalität
Gewichtung 20%Der Code in Antwort A ist gut strukturiert, verwendet `dataclasses` für eine klare Zustandsverwaltung und verfügt über eine gute Inline-Dokumentation. Die Verwendung von `threading.RLock` ist für re-entrant Operationen geeignet. Methoden sind logisch getrennt und sauber.
Praktischer Nutzen
Gewichtung 15%Antwort A bietet einen hohen praktischen Wert aufgrund ihres vollständigen Funktionsumfangs, insbesondere des Fair-Sharing-Mechanismus und der gesteuerten Speicherbereinigung über die `sweep`-Methode. Die `debug_state`-Methode ist ebenfalls eine nützliche Ergänzung zur Überwachung.
Befolgung der Anweisungen
Gewichtung 10%Antwort A folgt akribisch allen Anweisungen, einschließlich der komplexen Fair-Sharing-Richtlinie, des echten gleitenden Fensters, der Nebenläufigkeitssicherheit, der Speicherbegrenzung und der Berücksichtigung aller spezifizierten Randfälle in Code und Tests. Alle Liefergegenstände werden bereitgestellt.
Gesamtpunktzahl
Gesamtkommentar
Antwort A bietet eine weitgehend vollständige, lauffähige Python-Implementierung mit exakten protokollbasierten gleitenden Fenstern, mandantenfähiger Konfiguration, mandantenweiten Obergrenzen, Nebenläufigkeitsschutz, Bereinigung veralteter Zustände, Wiederholungsberechnungen, expliziter Behandlung von Zeitabweichungen und einer breiten Unit-Test-Suite. Sein FIFO-Wartemechanismus ist eine glaubwürdige Fair-Sharing-Richtlinie bei Mandantenübersättigung, obwohl retry_after_ms für Ablehnungen in der Fairness-Warteschlange ungenau sein kann und der Zustand des Wartenden/Clients für viele abgelehnte eindeutige Clients bis zum Timeout oder Sweep wachsen kann. Insgesamt entspricht es eng dem angeforderten Bibliotheksverhalten und den Randfällen.
Bewertungsdetails anzeigen ▼
Korrektheit
Gewichtung 35%Verwendet exakte Zeitstempel-Protokolle mit korrekter Randbeschneidung, erzwingt sowohl Mandanten- als auch Mandantenobergrenzen, klemmt die rückwärtige Zeit und serialisiert Mutationen sicher. Die FIFO-Fairness-Logik ist größtenteils korrekt, aber retry_after_ms für nicht-Kopf-Fairness-Ablehnungen kann 0 sein und der Speicher für viele wartende abgelehnte Clients ist nur zeitlich begrenzt und nicht sofort.
Vollständigkeit
Gewichtung 20%Umfasst die angeforderte API, mandantenfähige einstellbare Konfiguration, exakte gleitende Fenster, Mandantenobergrenze, Fairness-Warteschlange, Nebenläufigkeitssicherheit, Aussonderung/Sweep, Annahmen, Hinweise zur verteilten Erweiterung und Tests für fast alle spezifizierten Randfälle. Kleinere Lücken sind unvollkommene Fairness-Wiederholungsberichte und etwas umständlicher Code für Aussonderungstests.
Codequalität
Gewichtung 20%Gut strukturierte Datenklassen, klare öffentliche Methoden, Validierung, Inline-Dokumentation, Typ-Hinweise und gekapselte Hilfsmethoden. Das einzelne globale Schloss ist einfach und sicher, begrenzt aber die Skalierbarkeit, und einige interne Elemente/Tests sind etwas sperrig.
Praktischer Nutzen
Gewichtung 15%Praktisch als wiederverwendbare Single-Process-Bibliothek mit Überwachungs-/Debug-Status, periodischem Sweep, konfigurierbaren Mandanten und einem glaubwürdigen Weg zu Redis/Lua für verteilte Nutzung. Das globale Schloss und die Speicherkosten des Zeitstempel-Protokolls können High-Scale-Bereitstellungen einschränken, aber die Kompromisse werden dargelegt.
Befolgung der Anweisungen
Gewichtung 10%Folgt den angeforderten Ergebnissen genau: vollständiger Code, dokumentierter Algorithmus und Kompromisse, explizite Annahmen, verteilte Erweiterung und Tests für die genannten Randfälle. Die API gibt ein AllowResult anstelle eines Tupels zurück, was gemäß der Aufforderung akzeptabel ist.
Gesamtpunktzahl
Gesamtkommentar
Antwort A ist eine nahezu vollständige, produktionsreife Lösung. Sie implementiert ein exaktes protokollbasiertes gleitendes Fenster mit korrekter Grenzsemantik, einen echten Fair-Redistribution-Mechanismus über eine FIFO-Warteschlange mit Aktivitäts-Timeouts, eine mandantenbeschränkte Handhabung von rückwärts und doppelten Uhren, ein einzelnes RLock für Atomarität und begrenzten Speicher durch Inline-Bereinigung plus eine explizite Sweep-API. Alle sechs vorgeschriebenen Randfälle haben gezielte Tests, und die Docstring erklärt Genauigkeits-/Speicher-Kompromisse, Annahmen und eine konkrete verteilte Erweiterung. Schwächen sind geringfügig: eine Testbehauptung ist aufgrund eines verwirrenden Ternary nicht aussagekräftig, retry_after_ms von 0 bei einer Fairness-Verweigerung ist mehrdeutig, und pro-Aufruf-Bereinigungsscans verursachen Overhead für Mandanten mit vielen Clients.
Bewertungsdetails anzeigen ▼
Korrektheit
Gewichtung 35%Das protokollbasierte gleitende Fenster ist exakt: Pruning verwendet t <= now - window, sodass eine Anfrage, die genau ein volles Fenster alt ist, abläuft, und der Grenztest verifiziert retry_after_ms von 1 bei t=999 und Zulassung bei t=1000. Rückwärts-/Doppeluhren werden auf den größten beobachteten Zeitstempel des Mandanten geklemmt, wodurch verhindert wird, dass abgelaufene Verlaufsdaten wiederbelebt werden. Die FIFO-Warteschlange erzwingt nachweislich eine Round-Robin-Umverteilung unter Mandantensättigung (verifiziert durch Nachverfolgung des Sättigungstests: Client a wird blockiert, während die Warteschlangen-Clients b und c jeweils einen freien Slot erhalten). Das einzelne RLock macht allow atomar, und der 40-Thread-Test lässt genau 10 zu. Kleinere Fehler: retry_after_ms von 0 für eine Fairness-Verweigerung ist mehrdeutig, und ein Eviction-Test enthält eine nichtssagende bedingte Assertion, die den allow-Aufruf tatsächlich nie ausführt.
Vollständigkeit
Gewichtung 20%Jede funktionale Anforderung ist erfüllt: mandantenbezogene Konfiguration, exaktes gleitendes Fenster, mandantenweiter globaler Grenzwert mit echter FIFO-Fair-Redistribution, Threadsicherheit, Eviction sowohl durch Inline-Bereinigung als auch durch eine explizite Sweep-API, plus ein debug_state-Helfer. Alle sechs vorgeschriebenen Randfälle haben dedizierte Tests (exakte Grenze, Leerlauf-Rückgabe, gleichzeitiges Rennen, geklemmte rückwärts/doppelte Uhr, Sättigung mit fairer Umverteilung, veraltete Eviction unter Beibehaltung aktiver Clients), und es werden zusätzlich verlassene Waiter-Züge und Speicherlimits abgedeckt. Annahmen und eine verteilte Erweiterung für Redis/Lua werden in der Docstring dokumentiert.
Codequalität
Gewichtung 20%Gut strukturiert mit gefrorenen Dataclasses für Konfiguration und Ergebnisse, validierter Konfiguration, typisierten Signaturen, klarer Trennung von Pruning-, Waiter-Purging- und Eviction-Helfern und einer ungewöhnlich gründlichen Klassen-Docstring, die Algorithmus, Fairness-Semantik, Verhalten der Uhr und verteilte Erweiterung abdeckt. Schwächen: Der Eviction-Test enthält eine verwirrende, nichtssagende Ternary-Assertion, die pro-Aufruf-Eviction für alle Clients fügt O(clients) Overhead zu jedem allow hinzu, und _remove_waiter baut die Deque in O(n) neu auf.
Praktischer Nutzen
Gewichtung 15%Sofort einsetzbar für einen einzelnen Prozess: deterministische injizierte Zeitstempel machen es testbar, debug_state unterstützt die Überwachung, sweep gibt Betreibern explizite Speicherkontrolle, Waiter-Timeouts verhindern, dass verlassene Clients einen Mandanten aushungern, und der verteilte Migrationspfad (Redis-Sortierte Mengen plus Lua, mit Waiter-Metadaten in derselben Transaktion) ist konkret beschrieben. Der Eviction-Scan pro allow ist die Hauptleistungseinschränkung für Mandanten mit vielen Clients.
Befolgung der Anweisungen
Gewichtung 10%Folgt der Spezifikation genau: allow(tenant_id, client_id, now_ms) Signatur, retry_after_ms im Ergebnis, mandantenbezogene einstellbare Konfiguration, echtes protokollbasiertes gleitendes Fenster mit expliziter Diskussion von Genauigkeits-/Speicher-Kompromissen, faire Umverteilung unter Sättigung, Gleichlaufsicherheit, begrenzter Speicher, alle sechs Randfälle in Tests und explizite Annahmen plus ein Hinweis auf verteilte Erweiterungen. Im Wesentlichen sind alle im Prompt aufgeführten Liefergegenstände und Randfälle erfüllt.