Skip to content
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

[Fireperf] Fix RateLimiter by avoiding manually setting time #4027

Merged
merged 1 commit into from
Aug 26, 2022

Conversation

leotianlizhan
Copy link
Contributor

@leotianlizhan leotianlizhan commented Aug 25, 2022

Blocking #4024.

Context and why this is bad

b/243686627
TL;DR: don't use @VisibleForTesting method for real code, they break encapsulation. In this case, Timer(long time) is breaking logic too because it's mixing 2 different timebases together (time-since-epoch and time-since-VM-start). It's a bad method that should not exist even for tests.

Leftover problem

However if we remove the above, there's a leftover problem from #2662:
Before #2662, say there are 0 tokens at first. We can call rateLimiter.check() after any amount of time, so something like 1.33 tokens can be generated, but we only use long for our tokenCount, so it will automatically round to 1, and record current time as lastTimeTokenReplenished, then use that token so we are back down to 0. However if next time we call check(), 0.67 tokens are generated, then we round to 0, logic before #2662 would say we have no tokens to use, which is wrong because within the time described in above scenario, 2 tokens should have been generated, and we only used 2 tokens, so there should be enough.

#2662 tried to fix this by manually setting lastTimeTokenReplenished to the exact time at which a whole-number of tokens are added. In the same example, if 1.33 tokens are generated, then we still round-down and only add 1 to tokenCount, but we advance lastTimeTokenReplenished by the time it took to generate 1 token. This is like time only increment in ticks, with each tick guaranteed to be a multiple of the time it takes to generate a single token. This works, but manually setting time is error-prone (this PR/bug is literal proof).

Solution

Simply change tokens to double. Guava also uses double for their rate limiter's token counter https://github.com/google/guava/blob/9c1e5dea4b980202ba003b90fcb64183d42031b3/android/guava/src/com/google/common/util/concurrent/SmoothRateLimiter.java#L313

@leotianlizhan leotianlizhan changed the title [Fireperf] Remove misleading Timer constructor call from RateLimiter and use double for tokenCount [Fireperf] Fix RateLimiter by avoiding manually setting time Aug 25, 2022
@google-oss-bot
Copy link
Contributor

Coverage Report 1

Affected Products

  • firebase-perf

    Overall coverage changed from 71.38% (13083a2) to 71.37% (8ac300f) by -0.01%.

    FilenameBase (13083a2)Merge (8ac300f)Diff
    RateLimiter.java90.98%90.77%-0.21%

Test Logs

Notes

  • Commit (8ac300f) is created by Prow via merging PR base commit (13083a2) and head commit (497b9bc).
  • Run gradle <product>:checkCoverage to produce HTML coverage reports locally. After gradle commands finished, report files can be found under <product-build-dir>/reports/jacoco/.

  1. https://storage.googleapis.com/firebase-sdk-metric-reports/2mloYNBSZX.html

@google-oss-bot
Copy link
Contributor

Size Report 1

Affected Products

  • firebase-perf

    TypeBase (13083a2)Merge (8ac300f)Diff
    aar311 kB311 kB-56 B (-0.0%)
    apk (aggressive)1.03 MB1.03 MB-64 B (-0.0%)
    apk (release)2.48 MB2.48 MB-132 B (-0.0%)

Test Logs

Notes

  1. https://storage.googleapis.com/firebase-sdk-metric-reports/zNjNbakiZ6.html

@github-actions
Copy link
Contributor

Unit Test Results

  50 files   -    342    50 suites   - 342   1m 41s ⏱️ - 14m 50s
969 tests  - 3 739  969 ✔️  - 3 716  0 💤  - 22  0  - 1 
969 runs   - 3 755  969 ✔️  - 3 732  0 💤  - 22  0  - 1 

Results for commit 497b9bc. ± Comparison against base commit 13083a2.

new Timer(
lastTimeTokenReplenished.getMicros()
+ (long) (newTokens * MICROS_IN_A_SECOND / rate.getTokensPerSeconds()));
double newTokens =
Copy link
Contributor

Choose a reason for hiding this comment

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

A similar issue would exist in iOS as well. We might need to followup on that to fix it.

Copy link
Contributor Author

@leotianlizhan leotianlizhan Aug 25, 2022

Choose a reason for hiding this comment

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

Does iOS also have our own rate limiter implementation?

Copy link
Contributor

@jfcong jfcong left a comment

Choose a reason for hiding this comment

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

Thanks for fixing this!

@leotianlizhan
Copy link
Contributor Author

leotianlizhan commented Aug 25, 2022

Only failing GHA is Health Metrics / Coverage (push). I asked Acore, this check is to compute metrics differences like size and coverage, and it's not required because it runs for every product (not just fireperf), so other products might fail / be flaky. I checked the logs and it indeed failed due to other products, unrelated to us, so it is okay to merge.

[I] 2022-08-25 02:32:14 +0000 UTC fireci.gradle: FAILURE: Build completed with 3 failures.
[I] 2022-08-25 02:32:14 +0000 UTC fireci.gradle: 
[I] 2022-08-25 02:32:14 +0000 UTC fireci.gradle: 1: Task failed with an exception.
[I] 2022-08-25 02:32:14 +0000 UTC fireci.gradle: -----------
[I] 2022-08-25 02:32:14 +0000 UTC fireci.gradle: * What went wrong:
[I] 2022-08-25 02:32:14 +0000 UTC fireci.gradle: Execution failed for task ':firebase-messaging:testDebugUnitTest'.
[I] 2022-08-25 02:32:14 +0000 UTC fireci.gradle: ==============================================================================
[I] 2022-08-25 02:32:14 +0000 UTC fireci.gradle: 
[I] 2022-08-25 02:32:14 +0000 UTC fireci.gradle: 2: Task failed with an exception.
[I] 2022-08-25 02:32:14 +0000 UTC fireci.gradle: -----------
[I] 2022-08-25 02:32:14 +0000 UTC fireci.gradle: * What went wrong:
[I] 2022-08-25 02:32:14 +0000 UTC fireci.gradle: Execution failed for task ':firebase-storage:testDebugUnitTest'.
[I] 2022-08-25 02:32:14 +0000 UTC fireci.gradle: ==============================================================================
[I] 2022-08-25 02:32:14 +0000 UTC fireci.gradle: 
[I] 2022-08-25 02:32:14 +0000 UTC fireci.gradle: 3: Task failed with an exception.
[I] 2022-08-25 02:32:14 +0000 UTC fireci.gradle: -----------
[I] 2022-08-25 02:32:14 +0000 UTC fireci.gradle: * What went wrong:
[I] 2022-08-25 02:32:14 +0000 UTC fireci.gradle: Execution failed for task ':firebase-messaging:testReleaseUnitTest'.
[I] 2022-08-25 02:32:14 +0000 UTC fireci.gradle: ==============================================================================

@leotianlizhan leotianlizhan merged commit 087083f into master Aug 26, 2022
@leotianlizhan leotianlizhan deleted the fireperf-fix-rate-limiter branch August 26, 2022 00:13
@firebase firebase locked and limited conversation to collaborators Sep 25, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants