Add client side timeout logic for endpoints that support fallback URLs#2807
Conversation
| companion object { | ||
| const val SUPPORTED_FALLBACK_TIMEOUT_MS = 5000L // 5 seconds for requests with fallback support | ||
| const val REDUCED_TIMEOUT_MS = 2000L // 2 seconds for requests with fallback support after timeout | ||
| const val DEFAULT_TIMEOUT_MS = 30000L // 30 seconds for requests without fallback support and fallback requests |
There was a problem hiding this comment.
This is pretty long... but right now we aren't setting anything, and this felt pretty safe IMO.
There was a problem hiding this comment.
I agree. I'd be more worried about the SUPPORTED_FALLBACK_TIMEOUT_MS. I wonder if 5 seconds is too short?
|
@tonidero Very nice, seems like quite a clean implementation. Reviewed it briefly and will work on the iOS side and then review again with the knowledge gathered :) |
rickvdl
left a comment
There was a problem hiding this comment.
Awesome work! I think in the end the behavior is quite complex, but your solution is very clean and testable!
| // region Timeout Management | ||
|
|
||
| @Test | ||
| fun `HTTPClient records SUCCESS_ON_MAIN_BACKEND when successful request to main backend`() { |
There was a problem hiding this comment.
Great set of tests as well!
I think ideally we also mock the HTTPTimeoutManager and assert on the recorded events.I think these tests are very useful, but they assert on the behavior of the timeout manager, assuming it recorded what we think it recorded.
I think there could be a small role for this mock, making 100% sure it recorded the right event. What do you think?
There was a problem hiding this comment.
Right, I've wanted to avoid mocking things in tests when possible for a long time, and test a more comprehensive behavior. Since this is relatively coupled, I thought we could use the real dependency, but I think having some extra tests that mock it and verify that calls happen as expected, does make sense as well. I will add those aside from the already added ones 👍 Thanks for the feedback!
There was a problem hiding this comment.
In the end what I did was use spies and reuse the existing tests, so I could just verify the correct calls were being made: 47a8212. Lmk what you think!
| } | ||
|
|
||
| @Test | ||
| fun `getTimeoutForRequest returns SUPPORTED_FALLBACK_TIMEOUT_MS for endpoints with fallback support when no timeout occurred`() { |
There was a problem hiding this comment.
Awesome set of tests! Took some inspiration from them :)
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #2807 +/- ##
==========================================
+ Coverage 77.95% 78.02% +0.07%
==========================================
Files 318 319 +1
Lines 12465 12505 +40
Branches 1712 1722 +10
==========================================
+ Hits 9717 9757 +40
Misses 2022 2022
Partials 726 726 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
| private val dateProvider: DateProvider = DefaultDateProvider(), | ||
| ) { | ||
| companion object { | ||
| const val READ_TIMEOUT_MS = 30000L // We always use 30 seconds for read timeout, once we have established a connection |
There was a problem hiding this comment.
I decided to change the read timeout to 30 seconds always. I feel that if it's a connectivity issue with the backend (because it's queuing or something like that), it would be catched during the connect timeout, which we can keep at 5s for endpoints supporting fallback. But for the read timeout, that could be an indication of slow connections, so I think it might be better to be more conservative. Lmk what you think!
ajpallares
left a comment
There was a problem hiding this comment.
Wow! This is a very clean implementation and it was a great idea to separate it into its own object. And as Rick mentioned, great set of tests 👌
Thanks for being so thorough! 🙌
I have some small comments.
The first thing that came to mind was when reading the description.
Once we timeout in one of the endpoints that support fallbacks, the new timeout for these endpoints that support timeouts in the main server will be 2s, so we can fail early and try the fallback.
I later realized it is indeed implemented, but probably I'd add that
Whenever the main server is hit successfully, the timeout is reset to the initial 5s for the endpoints that support fallbacks
Other than that, some small comments! Thanks!
| companion object { | ||
| const val SUPPORTED_FALLBACK_TIMEOUT_MS = 5000L // 5 seconds for requests with fallback support | ||
| const val REDUCED_TIMEOUT_MS = 2000L // 2 seconds for requests with fallback support after timeout | ||
| const val DEFAULT_TIMEOUT_MS = 30000L // 30 seconds for requests without fallback support and fallback requests |
There was a problem hiding this comment.
I agree. I'd be more worried about the SUPPORTED_FALLBACK_TIMEOUT_MS. I wonder if 5 seconds is too short?
| fun recordRequestResult(result: RequestResult) { | ||
| when (result) { | ||
| RequestResult.SUCCESS_ON_MAIN_BACKEND -> { | ||
| resetTimeout() |
There was a problem hiding this comment.
Nice! I was exactly looking for this! Makes total sense 👍
…y settings and big responses" This reverts commit d21e961.
ajpallares
left a comment
There was a problem hiding this comment.
Makes sense! Thank you for all the iterations! Again, great job on this 🙏
| if (shouldResetTimeout()) { | ||
| resetTimeout() | ||
| } |
There was a problem hiding this comment.
If my mental compiler is correct I guess we could have 2 threads both resetting the time out, if the second thread reads lastTimeoutRequestTime before the first resets it, but that's probably fine?
There was a problem hiding this comment.
Indeed, that would be possible. But as you said, it wouldn't be a problem. We would just be setting to 0 multiple times. Great catch though!
…Client.kt Co-authored-by: Antonio Pallares <ajpallares@users.noreply.github.com>
### Description Seems the backend integration tests were flaky after adding the timeouts in #2807. This is because the timeout was too short during tests, causing it to be hit frequently.
**This is an automatic release.** > [!WARNING] > If you don't have any login system in your app, please make sure your one-time purchase products have been correctly configured in the RevenueCat dashboard as either consumable or non-consumable. If they're incorrectly configured as consumables, RevenueCat will consume these purchases. This means that users won't be able to restore them from version 9.0.0 onward. > Non-consumables are products that are meant to be bought only once, for example, lifetime subscriptions. ## RevenueCat SDK ### 🐞 Bugfixes * Restore Purchases config automatically in CustomerCenter (#2867) via Facundo Menzella (@facumenzella) * Handle error reading `errorStream` in some devices (#2865) via Toni Rico (@tonidero) * [MON-1122] Revert variable rounding logic to not round up (#2857) via Pol Piella Abadia (@polpielladev) ## RevenueCatUI SDK ### Paywallv2 #### 🐞 Bugfixes * Select default package on Sheet dismissal (#2861) via Cesar de la Vega (@vegaro) ### Customer Center #### ✨ New Features * CC-581 | Allow for support ticket creation (#2810) via Rosie Watson (@RosieWatson) ### 🔄 Other Changes * Bump fastlane-plugin-revenuecat_internal from `7328ea7` to `efca663` (#2864) via dependabot[bot] (@dependabot[bot]) * Bump fastlane from 2.228.0 to 2.229.0 (#2863) via dependabot[bot] (@dependabot[bot]) * Bump fastlane-plugin-revenuecat_internal from `083ced9` to `7328ea7` (#2862) via dependabot[bot] (@dependabot[bot]) * Runs plugin actions from correct directory (#2858) via JayShortway (@JayShortway) * Flush multiple event batches (#2842) via Toni Rico (@tonidero) * Add file size limit to events tracking files (#2841) via Toni Rico (@tonidero) * Make events manager be supported in Android < 24 (#2854) via Toni Rico (@tonidero) * Add non paid revenue reporting infra (#2728) via Toni Rico (@tonidero) * Fix backend integration tests (#2860) via Toni Rico (@tonidero) * Track `connection_error_reason` property in diagnostics (#2855) via Toni Rico (@tonidero) * Uses some git+GitHub lanes from Fastlane plugin (#2856) via JayShortway (@JayShortway) * Add client side timeout logic for endpoints that support fallback URLs (#2807) via Toni Rico (@tonidero) * [EXTERNAL] Fix deprecation warnings in examples module (#2852) contributed by @gojoel (#2853) via Toni Rico (@tonidero) * Bump fastlane-plugin-revenuecat_internal from `9f78bb9` to `083ced9` (#2848) via dependabot[bot] (@dependabot[bot]) Co-authored-by: revenuecat-ops <ops@revenuecat.com>
Description
This adds some logic to implement a client side connection timeout. The logic is like this:
This is pretty delicate, so lmk what you think!