Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork9.6k
[RateLimiter] Fix DateInterval normalization#58757
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Conversation
carsonbot commentedNov 4, 2024
Hey! I see that this is your first PR. That is great! Welcome! Symfony has acontribution guide which I suggest you to read. In short:
Review the GitHub status checks of your pull request and try to solve the reported issues. If some tests are failing, try to see if they are failing because of this change. When two Symfony core team members approve this change, it will be merged and you will become an official Symfony contributor! I am going to sit back now and wait for the reviews. Cheers! Carsonbot |
danydev commentedNov 4, 2024 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Let's focus onBug 1 by looking at some code
So the output of this code executed in any time of the year (except for the interval of time I defined above) is:
That's perfectly right, the output above is the expected one and perfectly as expected since Now let's see the same exact code executed when the time is between
A new notes to better grasp the problem:
At this point, I hope you understand what's going on, let's see it the implication with the RateLimiter For sake of simplicity let's say you have a RateLimiter configured like
So the main thing here is that interval is "1 minute". This is the original RateLimiter code pre-fix
If executed when the bug occurs (in the date interval I defined above) the first date would be the "real" current time, at which we Now this is going to be used as TTL of the Redis key, (see |
danydev commentedNov 4, 2024 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Now let's focus onBug 2 by looking at some other code
So the output of this code executed in any time of the year (except for the interval of time I defined above) is:
That's perfectly right, the output above is the expected one and perfectly as expected since Now let's see the same exact code executed when the time is ~27 Oct 2:01AM CEST Europe/Rome, and interval is 65 minutes (so current time + interval > time when DST change)
A new notes to better grasp the problem:
|
3d9788b
tob137b90
Comparedanydev commentedNov 4, 2024 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
This comment analyzes the bugfix for 2 cases above. Reference code that uses the same approach of the currently pushed fix.
output when executing 27 Oct 2024 2:10:02AM DST (so theBug 1 explained above)
Comment: all good, all intervals are created correctly and diff by 1 minute in all php versions Now let's verifyBug 2
output when executing 27 Oct 2024 2:12:15AM DST + using interval of 65 minutes that summed up to current time goes to > time when DST changes (so theBug 2 explained above)
Comment: all good, all intervals are created correctly and diff by 65 minutes in all php versions. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Thanks for the detailed explanations.
Can this be tested?
I explored several ways to try to create a unit test, but since the current datetime is not easily mockable, I couldn't find a way to create a test that fails on master and passes on this branch. I could create a unit test that verifies that the expected TTL is created accordingly to the defined rate limiter interval, but that wouldn't validate specifically my case, it would pass on master as well. |
Uh oh!
There was an error while loading.Please reload this page.
b137b90
to07b3937
Comparesrc/Symfony/Component/RateLimiter/Tests/RateLimiterFactoryTest.php OutdatedShow resolvedHide resolved
Uh oh!
There was an error while loading.Please reload this page.
src/Symfony/Component/RateLimiter/Tests/RateLimiterFactoryTest.php OutdatedShow resolvedHide resolved
Uh oh!
There was an error while loading.Please reload this page.
56514ac
to2b0537c
Comparedanydev commentedNov 5, 2024 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Thank you for your suggestions, I just did a followup with the unit test fixing the date when the bug occurs. I think we should be okay now, the funny thing is that by changing the fix to be able to mock it, the bug was still there until I modified also the other point where we create the |
@@ -69,7 +69,11 @@ protected static function configureOptions(OptionsResolver $options): void | |||
{ | |||
$intervalNormalizer = static function (Options $options, string $interval): \DateInterval { | |||
try { | |||
return (new \DateTimeImmutable())->diff(new \DateTimeImmutable('+'.$interval)); | |||
// Force UTC timezone, because we don't want to deal with quirks happening when modifying dates in case | |||
// there is a default timezone with DST. Read more here https://github.com/symfony/symfony/pull/58757 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
ah sorry I missed this one - we don't put references to github in the codebase - instead we put PR discussion in git notes (and git blame can help also)
// there is a default timezone with DST. Read more here https://github.com/symfony/symfony/pull/58757 | |
// there is a default timezone with DST. |
c842016
to9a52e7e
Compare9a52e7e
tod81af98
Comparedanydev commentedNov 5, 2024 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
@nicolas-grekas I'm bothering you just one last time. After looking at thephp doc, I noticed the note
So I think that's just "better" to not specify the timezone, since actually it's superfluous, in case of timestamps, PHP uses by default UTC. If you agree, could you re-approve? Thank you |
Thank you@danydev. |
d2ba257
intosymfony:5.4Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
This PR fixes a nasty bug (actually 2 different bugs) that caused our API to be unavailable for more than 24 hours. We needed to apply a manual mitigation to resolve the issue (see more below).
Specifically, the PR fixes a bug that causes the wrong calculation of the TTL keys containing the information of the rate limits for a
fixed window
limiter.By doing so, our API consumers that respected the limit, actually were (wrongly) blocked by the RateLimiter, because the RateLimiter relies on the fact that the keys expire after the window defined by the configuration, so by using a wrong TTL, it ends up to a wrong evaluation of the consumed tokens.
To make it even worse, the TTL is reset at its original value every time a new rate limiter evaluation occurs for that key, so since our clients continued to do the requests (that without the bug would be accepted) the "denial of service" lasted indefinitely until we manually removed all the affected keys in Redis.
Bug 1
Prerequisites:
Europe/Rome
)27 October 2024 2:00AM CEST
and27 October 2024 2:59AM CEST
(e.g.27 October 2024 2:02AM CEST
)Bug 2
Prerequisites:
Europe/Rome
)27 October 2024 2:50AM CEST
using an rate limiter interval of 20 minutes)Those bugs are caused by a php behaviour that it's not optimal nor intuitive, I feel likeBug 1 could be defined as a PHP bug, whileBug 2 is intended behaviour, anyway it's like this, and we need to deal with it at the application level.