Skip to content

[RateLimiter] Fix wait duration for fixed window policy #42168

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 30, 2021
Merged

[RateLimiter] Fix wait duration for fixed window policy #42168

merged 1 commit into from
Oct 30, 2021

Conversation

jlekowski
Copy link
Contributor

@jlekowski jlekowski commented Jul 17, 2021

Q A
Branch? 5.3
Bug fix? yes
New feature? no
Deprecations? no
Tickets Fix #...
License MIT
Doc PR symfony/symfony-docs#...

When using fixed_window policy and calling ->wait() after overconsuming, there might be no sleep() called. Reproducer below:

<?php

use Symfony\Component\RateLimiter\RateLimiterFactory;
use Symfony\Component\RateLimiter\Storage\InMemoryStorage;

require_once './vendor/autoload.php';


$factory = new RateLimiterFactory([
    'id' => 'some_id',
    'policy' => 'fixed_window',
    'limit' => 10,
    'interval' => '2 seconds',
], new InMemoryStorage());

$limiter = $factory->create();

$limit = $limiter->consume(8);

$limit = $limiter->consume(4);
$start = microtime(true);
$limit->wait();
echo 'WAITED: ' . 1000 * (microtime(true) - $start);
// WAITED: 0.0030994415283203 - instantaneous instead of WAITED: 2000.3020763397 after the fix

@carsonbot
Copy link

Hey!

I think @kbond has recently worked with this code. Maybe they can help review this?

Cheers!

Carsonbot

@Nyholm
Copy link
Member

Nyholm commented Jul 18, 2021

Thank you.
It is indeed a bug. But Im not sure you have the correct solution.

Isn't the correct behaviour to not accept that you are trying to overconsume?

@jlekowski
Copy link
Contributor Author

Isn't the correct behaviour to not accept that you are trying to overconsume?

@Nyholm, thanks for looking at this PR. I think you can always call ->consume(), but you can check something like var_dump($limit->getRemainingTokens(), $limit->isAccepted()); afterwards to see there are still 2 tokens remaining and that it has not been accepted.

Here's a code I used to compare different policies:

foreach (['fixed_window', 'sliding_window', 'token_bucket'] as $policy) {
    echo "\nPOLICY: $policy\n";

    $factory = new RateLimiterFactory([
        'id' => 'db_write',
        'policy' => $policy,
        'limit' => 10,
        'interval' => '2 seconds',
        'rate' => ['amount' => 10,'interval' => '2 seconds'],
    ], new InMemoryStorage());

    $limiter = $factory->create();

    $limit = $limiter->consume(8);

    $limit = $limiter->consume(4);
    echo "REMAINING TOKENS: " . $limit->getRemainingTokens() . PHP_EOL;
    echo "IS ACCEPTED: " . (int)$limit->isAccepted() . PHP_EOL;
    $start = microtime(true);
    $limit->wait();
    echo 'WAITED: ' . 1000 * (microtime(true) - $start) . PHP_EOL;
    $limit = $limiter->consume(4);
    echo "REMAINING TOKENS: " . $limit->getRemainingTokens() . PHP_EOL;
    echo "IS ACCEPTED: " . (int)$limit->isAccepted() . PHP_EOL;
}

BTW, there might be another issue with sliding_window policy, but it's not for this ticket.

@fabpot fabpot modified the milestones: 5.2, 5.3 Jul 26, 2021
@jlekowski
Copy link
Contributor Author

@Nyholm, would you have any suggestions for this PR? Could this be merged, or requires a change?

Also, could you have a look at #42194, please?

Copy link
Member

@Nyholm Nyholm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I finally got my head around this PR.

What do you think of changing the solution slightly? Will it still work?

@jlekowski
Copy link
Contributor Author

@Nyholm, thanks for having a closer look into this.

Fixing the number of tokens to calculate time for works too.

I made the changes according to your suggestions. If that looks fine, I will:

  • squash my commits
  • rebase the branch from 5.3 and point the PR to 5.3 branch (as the milestone here was set)

The reason behind removing calculateTimeForTokens() was that the class is marked as @internal and in the current flow $window->calculateTimeForTokens($tokens); will always return the equivalent of $this->interval; (see my comment in 44807a6#diff-26af5f9f51ed8bd11b4b02e6d02eef2872e174b3ba50a6f6aa9edb218400ac13).
That means that even if we keep calculateTimeForTokens() for BC, we can avoid calling it.

Copy link
Member

@Nyholm Nyholm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Im happy with this.

Please squash the commits and rebase on 5.3. Make sure that none of the test failures are related to this PR. Then I would be happy if @wouterj saw this PR too.

About removing the method.
I didn't see the class was internal, if so, you are correct. Removing a method from an internal class is not a BC break.

@jlekowski
Copy link
Contributor Author

Thank you @Nyholm. I squashed and rebased my changes.

About removing the method. I didn't see the class was internal, if so, you are correct. Removing a method from an internal class is not a BC break.

@wouterj, please have a look how you feel about this change. My original commit was here jlekowski@44807a6 with Symfony\Component\RateLimiter\Policy\Window::calculateTimeForTokens() being removed as it does not need to be used in FixedWindowLimiter. But it's your party, so I'm fine with either change :)

@jlekowski jlekowski changed the base branch from 5.2 to 5.3 October 23, 2021 20:29
@fabpot
Copy link
Member

fabpot commented Oct 30, 2021

Thank you @jlekowski.

@fabpot fabpot merged commit a972a6a into symfony:5.3 Oct 30, 2021
fabpot added a commit that referenced this pull request Nov 1, 2021
This PR was merged into the 5.3 branch.

Discussion
----------

[RateLimiter] Increase delta in limiter tests

| Q             | A
| ------------- | ---
| Branch?       | 5.3 <!-- see below -->
| Bug fix?      | yes
| New feature?  | no <!-- please update src/**/CHANGELOG.md files -->
| Deprecations? | no <!-- please update UPGRADE-*.md and src/**/CHANGELOG.md files -->
| License       | MIT

Tests from [PR](#42168) might randomly fail [1](https://ci.appveyor.com/project/fabpot/symfony/builds/41346868#L597) [2](https://ci.appveyor.com/project/fabpot/symfony/builds/41346183#L650)

I increased delta like in [this test](https://github.com/symfony/symfony/blob/5.3/src/Symfony/Component/RateLimiter/Tests/Policy/TokenBucketLimiterTest.php#L90).

![image](https://user-images.githubusercontent.com/57155526/139543405-e8e1b760-96b2-4aea-8ed2-15c6221cd387.png)

![image](https://user-images.githubusercontent.com/57155526/139543418-d9558c2e-9248-49dd-b941-ad917f5e23d2.png)

`@fabpot` `@Nyholm` please review

Commits
-------

3e2ca33 [RateLimiter] Increase delta in limiter tests
@fabpot fabpot mentioned this pull request Nov 22, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants