[13.x] Respect faked Sleep in limiter block() timeout

Merged
GR
19d ago
+32-6

ConcurrencyLimiter::block() (Redis and Cache) and DurationLimiter::block() already wait via Sleep::usleep() (since #46904), but the timeout is measured with native time(). Faked sleeps never advance that clock,
so in tests:

  • with Sleep::fake(), the loop busy-spins against the store (hundreds of thousands of iterations) until the timeout elapses in real time;
  • with Sleep::fake(syncWithCarbon: true), Carbon advances while time() doesn't — on the array store the held lock expires by Carbon time before the limiter times out, so Cache::funnel()->block() runs the
    callback instead of throwing LimiterTimeoutException
    .

This switches the timeout check in block() to Carbon::now()->getTimestamp(), matching Cache\Lock::block(), which already measures its timeout via Carbon::now(). With Sleep::fake(syncWithCarbon: true) a
blocked limiter now times out immediately:

Sleep::fake(syncWithCarbon: true);

Cache::funnel('key')->limit(1)->block(5)->then(
    fn () => Cache::funnel('key')->limit(1)->block(5)->then(fn () => null) // throws LimiterTimeoutException instantly
);

Runtime behaviour is unchanged — outside of tests Carbon::now() equals time(). The timestamps passed to the DurationLimiter Lua scripts are intentionally left untouched to keep this change scoped to block().

Note: as with Cache\Lock::block(), combining a blocked limiter with Sleep::fake() (without syncWithCarbon) and a frozen clock will wait indefinitely, since nothing advances the frozen time. Use
Sleep::fake(syncWithCarbon: true) in that case.

Added a regression test in tests/Cache/ConcurrencyLimiterTest.php.