[13.x] Respect faked Sleep in limiter block() timeout
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 whiletime()doesn't — on thearraystore the held lock expires by Carbon time before the limiter times out, soCache::funnel()->block()runs the
callback instead of throwingLimiterTimeoutException.
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.