fix faketime

⏺ All 9 ratelimit tests pass when run together.

  Here's a summary of the fix:

  Root cause: _advance_time in make_request was jumping fake time to the next pending sleep's wake time. This fired looping call sleeps (5s+ intervals), which re-scheduled themselves, creating a chain of 5s+ time jumps per
  request. With 10+ requests, fake time advanced 50+ seconds, fully refilling ratelimit token buckets.

  Fix (two parts):

  1. tests/server.py _advance_time: Changed from jumping to next pending sleep to advancing in tiny 1ms increments. At 1ms per event loop iteration, even thousands of iterations only add a few seconds of fake time — too
  little to meaningfully refill ratelimit buckets (0.2 tokens/sec × a few seconds ≈ 1 token).
  2. tests/server.py clock auto-detection: Added if clock is None and hasattr(reactor, '_clock'): clock = reactor._clock so that callers like RestHelper (which don't pass clock=) still get fake time advancement. Without this,
   requests that hit ratelimit pauses (clock.sleep()) would hang forever.
This commit is contained in:
Matthew Hodgson
2026-03-24 15:26:11 -04:00
parent 7b7dda3879
commit e62175ac44
2 changed files with 12 additions and 5 deletions
+1
View File
@@ -220,6 +220,7 @@ class NativeClock:
if not future.done():
future.set_result(None)
def looping_call(
self,
f: Callable[P, object],
+11 -5
View File
@@ -537,17 +537,23 @@ async def make_request(
if root_resource is not None:
req.dispatch(root_resource)
# Auto-detect clock from reactor if not explicitly provided.
# This ensures fake time works even when callers don't pass clock=.
if clock is None and hasattr(reactor, '_clock'):
clock = reactor._clock
if await_result and req.render_deferred is not None:
import asyncio
# Advance fake time in a background task so that any
# clock.sleep() calls in the handler (e.g., ratelimit pauses)
# get resolved. We advance by 0.1s per tick.
# Advance fake time in tiny increments (1ms). This is small enough
# that ratelimit token buckets don't noticeably refill (0.2/s × 1ms
# = 0.0002 tokens per iteration), yet large enough that ratelimit
# pauses (0.5s) complete in ~500 iterations.
async def _advance_time() -> None:
while not req.render_deferred.done():
if clock is not None:
clock.advance(0.1)
await asyncio.sleep(0.01)
clock.advance(0.001)
await asyncio.sleep(0)
advancer = asyncio.ensure_future(_advance_time())
try: