mirror of
https://github.com/element-hq/synapse.git
synced 2026-07-28 14:19:59 +00:00
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:
@@ -220,6 +220,7 @@ class NativeClock:
|
||||
if not future.done():
|
||||
future.set_result(None)
|
||||
|
||||
|
||||
def looping_call(
|
||||
self,
|
||||
f: Callable[P, object],
|
||||
|
||||
+11
-5
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user