Re-tier HTTP request timeouts around per-host memory - #3746
Open
ajpallares wants to merge 43 commits into
Open
Conversation
Main-API requests resolve their base host from the shared RemoteConfigSourceProvider (always wired into HTTPClient), rather than always using AppConfig.baseURL. Behavior-preserving groundwork for remote-config-driven API host failover. Co-authored-by: Cursor <cursoragent@cursor.com>
The inline comment restated the method KDoc; keep only the KDoc. Co-authored-by: Cursor <cursoragent@cursor.com>
…into pallares/api-sources-base-host
Co-authored-by: Cursor <cursoragent@cursor.com>
…mment Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Drop the default values for RemoteConfigManager's topicStore and sourceProvider parameters so the shared instances from PurchasesFactory are always injected, rather than silently constructing separate ones. Callers now pass them explicitly; tests build equivalents wired to the same disk cache. Co-authored-by: Cursor <cursoragent@cursor.com>
The androidTest reachability helper builds an HTTPClient directly and was missing the now-required apiSourceProvider argument. Pass null to keep the legacy base-host behavior, matching the other integration test helpers. Co-authored-by: Cursor <cursoragent@cursor.com>
…into pallares/api-sources-base-host
Express the fallback-attempt check with the already-defined isMainBackend flag instead of re-deriving fallbackURLIndex > 0. Co-authored-by: Cursor <cursoragent@cursor.com>
WebBillingGetProducts is hosted on the same api.revenuecat.com host as the main API, so it should resolve its base host from the API source provider too (with failover) instead of the static base URL. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…base-host Co-authored-by: Cursor <cursoragent@cursor.com> # Conflicts: # purchases/src/main/kotlin/com/revenuecat/purchases/PurchasesFactory.kt
…sConfigIntegrationTest The main merge brought a new integration test that constructs RemoteConfigManager without the topicStore/sourceProvider params introduced on this branch. Pass them so the merged tree compiles. Co-authored-by: Cursor <cursoragent@cursor.com>
…ceProvider directly The networking layer depended on the narrow APISourceProvider interface, an extra abstraction over RemoteConfigSourceProvider that added little. Fold currentAPISource() into RemoteConfigSourceProvider and have HTTPClient depend on it directly. Co-authored-by: Cursor <cursoragent@cursor.com>
Replaces the single global "recent timeout" slot in `HTTPTimeoutManager` (dropping the `AtomicLong`) with a per-host `ConcurrentHashMap` (`host -> lastTimeoutDate`, 10-minute per-entry expiry, pruned on access) and re-tiers the request timeouts: - Main-source, endpoint with no fallback-URL support: 15s base / 5s reduced - Main-source, endpoint with fallback-URL support: 5s base / 2s reduced - Fallback-host and proxied requests: flat 30s (never consult the memory) A main-source request to host H uses the reduced tier iff H has a non-expired timeout entry. Timeouts are now recorded for any main-source request that timed out (dropping the previous fallback-support-only condition), keyed by attempt host; a successful main-source response clears only that host's entry. Renames the `TIMEOUT_ON_MAIN_BACKEND_FOR_FALLBACK_SUPPORTED_ENDPOINT` result to `MAIN_SOURCE_TIMED_OUT`. Adds `AppConfig.hasProxyURL` to signal proxied requests. Co-authored-by: Cursor <cursoragent@cursor.com>
1 task
…base-host Co-authored-by: Cursor <cursoragent@cursor.com> # Conflicts: # purchases/src/main/kotlin/com/revenuecat/purchases/PurchasesFactory.kt
Co-authored-by: Cursor <cursoragent@cursor.com>
…into pallares/api-sources-timeouts Co-authored-by: Cursor <cursoragent@cursor.com> # Conflicts: # purchases/src/main/kotlin/com/revenuecat/purchases/common/HTTPClient.kt
The usesAPISources when-expression did not handle GetRemoteConfigFallback, which was added later via #3750. That endpoint is deliberately targeted at a fallback base URL chosen by the domain layer and must not resolve its host from the API source provider, so it now returns false alongside the other non-API-source endpoints. Co-authored-by: Cursor <cursoragent@cursor.com>
…egration test ProductionRemoteConfigFallbackIntegrationTest arrived via a main merge and constructs RemoteConfigManager without the topicStore/sourceProvider params that this branch made required (09cc7ee), breaking compilation. Wire them to the same disk cache, mirroring ProductionRemoteConfigIntegrationTest. Co-authored-by: Cursor <cursoragent@cursor.com>
…n tests - Rewrite HTTPTimeoutManager.hasRecentTimeout to use a single expired flag so it stays within detekt's ReturnCount limit while keeping the prune-on-expiry behavior. - Stub AppConfig.hasProxyURL in the integration reachability HTTPClient mock; the timeout manager now reads it during performRequest, so the unstubbed mock was throwing MockKException in the load-shedder/production integration tests. Co-authored-by: Cursor <cursoragent@cursor.com>
…onfig mock The re-tiered HTTPTimeoutManager reads appConfig.hasProxyURL during performRequest, but BaseBackendIntegrationTest's AppConfig mock never stubbed it, so every backend integration test threw MockKException. Stub it to false, matching the other HTTPClient AppConfig mocks. Co-authored-by: Cursor <cursoragent@cursor.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3746 +/- ##
==========================================
+ Coverage 80.60% 80.61% +0.01%
==========================================
Files 423 423
Lines 17401 17406 +5
Branches 2682 2687 +5
==========================================
+ Hits 14026 14032 +6
Misses 2362 2362
+ Partials 1013 1012 -1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
AlvaroBrey
approved these changes
Jul 14, 2026
AlvaroBrey
left a comment
Contributor
There was a problem hiding this comment.
Looks good and makes sense I think! Just one question
…s setting Disabled-by-default internal DangerousSettings flag that the API-sources host-resolution work (#3715) will gate behind, so it can merge without enabling the behavior in production and tests can flip it on in CI. Co-authored-by: Cursor <cursoragent@cursor.com>
…toString assertion Co-authored-by: Cursor <cursoragent@cursor.com>
…erous-setting' into pallares/api-sources-base-host
Resolve main-API request hosts from the remote-config API sources only when the `usesRemoteConfigAPISources` dangerous setting is enabled. When disabled (the default), requests keep targeting the static AppConfig base URL. Expose the setting via `AppConfig.usesRemoteConfigAPISources`, enable it in the API-source HTTPClient tests, and add a test asserting an injected provider is ignored while the setting is off. Co-authored-by: Cursor <cursoragent@cursor.com>
…timeouts Co-authored-by: Cursor <cursoragent@cursor.com> # Conflicts: # purchases/src/main/kotlin/com/revenuecat/purchases/common/HTTPClient.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Checklist
Motivation
Now that a single request can fail over across several hosts, the old timeout handling doesn't hold up. We only remember the most recent timeout in one global slot, so one slow host shortens timeouts for unrelated hosts (and a success on any host resets them all). The timeouts are also the same regardless of the request, so we wait too long on a host that's down before failing over to the next one.
Description
New per-attempt timeout tiers:
AtomicLonglast-timeout slot inHTTPTimeoutManagerwith a per-hostConcurrentHashMap(host -> lastTimeoutDate, 10-minute per-entry expiry, pruned on access).TIMEOUT_ON_MAIN_BACKEND_FOR_FALLBACK_SUPPORTED_ENDPOINTresult toMAIN_SOURCE_TIMED_OUT; addAppConfig.hasProxyURL.iOS counterpart: RevenueCat/purchases-ios#7179
Note
Medium Risk
Changes core HTTP timing and failover behavior for all backend calls; behavior is well covered by unit tests but mis-tuned timeouts could affect reliability under poor network conditions.
Overview
Reworks how the Android SDK picks connect timeouts when requests can fail over across multiple hosts. Per-host memory replaces a single global “last timeout” slot so one slow host no longer shortens timeouts for others, and a success on one host only clears that host’s entry (10-minute expiry per host).
New timeout tiers depend on attempt type: main-source without fallback-URL support uses 15s / 5s reduced; main-source with fallback support uses 5s / 2s; fallback-host and proxied requests use a flat 30s and ignore the memory.
HTTPClient records
MAIN_SOURCE_TIMED_OUTfor any main-sourceSocketTimeoutException(not only fallback-capable endpoints), passes resolved host intoHTTPTimeoutManager, and usesAppConfig.hasProxyURLwhen computing timeouts. Test mocks and unit/integration tests are updated for the new API and enum name.Reviewed by Cursor Bugbot for commit 7263274. Bugbot is set up for automated code reviews on this repo. Configure here.