RateLimiter: Fix peek mode

Why:
- Setting the increment to 0 should check the limit without bumping it.
- This was apparently broken by If3e66491306f22650.

What:
- Use LimitBatch::peek if the increment amount is 0

Bug: T381033
Change-Id: Ife76a1976a2063f051f00302e5adaebd701e6367
(cherry picked from commit e09606b3dc44711571cc6cf2d0d11bd7784d0cdd)
This commit is contained in:
daniel 2025-02-28 19:12:30 +01:00 committed by Reedy
parent 05cce96a77
commit d0bbe78b23
3 changed files with 40 additions and 2 deletions

View file

@ -210,7 +210,8 @@ class RateLimiter {
$conds = $this->getConditions( $action );
$limiter = $this->wrstatsFactory->createRateLimiter( $conds, [ 'limiter', $action ] );
$limitBatch = $limiter->createBatch( $incrBy );
$peekMode = $incrBy === 0;
$limitBatch = $limiter->createBatch( $incrBy ?: 1 );
$this->logger->debug( __METHOD__ . ": limiting $action rate for {$user->getName()}" );
$id = $user->getId();
@ -311,7 +312,7 @@ class RateLimiter {
'ip' => $ip,
];
$batchResult = $limitBatch->tryIncr();
$batchResult = $peekMode ? $limitBatch->peek() : $limitBatch->tryIncr();
foreach ( $batchResult->getFailedResults() as $type => $result ) {
$this->logger->info(
'User::pingLimiter: User tripped rate limit',

View file

@ -87,6 +87,13 @@ class WRStatsReader {
break;
}
}
if ( !$seqSpec ) {
// This check exists to make Phan happy.
// It should never fail since we apply normalization in MetricSpec::__construct()
throw new WRStatsError( 'There should have been at least one sequence' );
}
$timeStep = $seqSpec->timeStep;
$firstBucket = (int)( $range->start / $timeStep );
$lastBucket = (int)ceil( $range->end / $timeStep );

View file

@ -483,6 +483,36 @@ class RateLimiterTest extends MediaWikiIntegrationTestCase {
$this->assertStatsHasCount( 'test.RateLimiter.limit.delete.result.tripped', 1, $statsData );
}
/**
* Test that setting the increment to 0 causes the RateLimiter to operate in
* peek mode, checking a rate limit without setting it.
*
* Regression test for T381033.
*/
public function testPeek() {
$limits = [
'edit' => [
'user' => [ 1, 60 ],
],
];
$user = new RateLimitSubject( new UserIdentityValue( 7, 'Garth' ), '127.0.0.1', [] );
$limiter = $this->newRateLimiter( $limits, [] );
// initial peek should pass
$this->assertFalse( $limiter->limit( $user, 'edit', 0 ) );
// check that repeated peeking doesn't trigger the limit
$this->assertFalse( $limiter->limit( $user, 'edit', 0 ) );
// first increment should pass but trigger the limit
$this->assertFalse( $limiter->limit( $user, 'edit', 1 ) );
// peek should fail now
$this->assertTrue( $limiter->limit( $user, 'edit', 0 ) );
}
/**
* Test that the most permissive limit is used when a limit is defined for
* multiple groups a user belongs to.