Select blob source by priority and weighted random#3458
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## toniricodiez/use-topic-file-for-offline-entitlements #3458 +/- ##
========================================================================================
- Coverage 79.53% 79.53% -0.01%
========================================================================================
Files 369 370 +1
Lines 14885 14898 +13
Branches 2042 2048 +6
========================================================================================
+ Hits 11839 11849 +10
- Misses 2224 2226 +2
- Partials 822 823 +1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
4f3da1a to
a820471
Compare
475500e to
5d17b34
Compare
a820471 to
c125314
Compare
03c6f6f to
d128c04
Compare
c125314 to
5b5596f
Compare
5b5596f to
78ea29f
Compare
d128c04 to
6d89f70
Compare
78ea29f to
48367fa
Compare
6d89f70 to
32ba5ab
Compare
32ba5ab to
85b8714
Compare
4e64b90 to
ae9921c
Compare
3985e09 to
74eedab
Compare
ae9921c to
6eda732
Compare
74eedab to
e1dac4b
Compare
6eda732 to
4cd0810
Compare
74eedab to
e1dac4b
Compare
4cd0810 to
a29029f
Compare
9503c36 to
e9b0f74
Compare
a29029f to
2172324
Compare
ajpallares
left a comment
There was a problem hiding this comment.
Some initial comments on the weight-based randomization logic. Will keep looking 👀
337c191 to
ff1124a
Compare
ajpallares
left a comment
There was a problem hiding this comment.
I think it looks good! I have some doubts though, mostly about whether the lower-priority blob sources not being picked at all for now.
Great job btw!
| override val priority: Int, | ||
| override val weight: Int, | ||
| ) : WeightedSource |
There was a problem hiding this comment.
NABD, but I believe in this PR we're not actually using the conformance of ApiSource to WeightedSource
There was a problem hiding this comment.
Correct! The idea is that we can reuse the same logic to select the ApiSource, but that's not implemented yet (will be in future PRs)
| } | ||
| // WIP: We should have some logic to pick the correct source for this. Right now, hardcoded to the first source. | ||
| val source = response.blobSources.firstOrNull() | ||
| val source = response.blobSources.selectWeighted(random) |
There was a problem hiding this comment.
I'm guessing this will be tackled in follow-up PRs, but, with the current implementation of this PR, we only ever use the highest priority blobSources, right? lower-priority sources aren't fallback candidates.
There was a problem hiding this comment.
That's indeed correct! That's tackled in this follow-up PR: #3466
| val totalWeight = candidates.sumOf { it.weight } | ||
| val anyNegativeWeights = candidates.any { it.weight < 0 } | ||
| return if (totalWeight <= 0 || anyNegativeWeights) { | ||
| candidates[random.nextInt(candidates.size)] |
There was a problem hiding this comment.
Cursor highlighted some concerns about thread safety in random. Seems like the default Random.Default is thread safe, so this would only really affect tests I guess. Just wanted to highlight here as well
There was a problem hiding this comment.
Hmm can you elaborate? I think for tests as well, we create new instances on each test. It's true we don't test with the "actual" Random.Default, but this is to make the tests deterministic.
There was a problem hiding this comment.
You're right, yes. With the uses we have for it now, it's not a problem. Please ignore me
ff1124a to
9d28ec1
Compare
9d28ec1 to
98ba254
Compare
**This is an automatic release.** ## RevenueCat SDK ### 📦 Dependency Updates * [RENOVATE] Update dependency gradle to v8.14.5 (#3459) via RevenueCat Git Bot (@RCGitBot) ## RevenueCatUI SDK ### ✨ New Features * Pre-warm image cache for workflow step states (#3447) via Cesar de la Vega (@vegaro) ### Paywallv2 #### ✨ New Features * Add `close_workflow` button action (#3453) via Cesar de la Vega (@vegaro) #### 🐞 Bugfixes * Fix preload VideoComponent fallback override images (#3449) via Cesar de la Vega (@vegaro) ### 🔄 Other Changes * Select blob source by priority and weighted random (#3458) via Toni Rico (@tonidero) * [AUTOMATIC] Update golden test files for backend integration tests (#3473) via RevenueCat Git Bot (@RCGitBot) * Clean up unreferenced topic files after successful remote-config refresh (#3439) via Toni Rico (@tonidero) * Cache remote config response in memory with TTL and persist to disk (#3457) via Toni Rico (@tonidero) * build(deps): bump fastlane from 2.233.1 to 2.234.0 (#3463) via dependabot[bot] (@dependabot[bot]) * Update codelabs links (#3460) via Jaewoong Eum (@skydoves) * Add RemoteConfigManager and TopicFetcher (#3437) via Toni Rico (@tonidero) * Add exit offers support to workflows (#3452) via Cesar de la Vega (@vegaro) * Update baseline profiles (#3461) via RevenueCat Git Bot (@RCGitBot) * Add network scaffolding for remote config endpoint (#3435) via Toni Rico (@tonidero) * test: cover singleStepFallbackId == initialStepId edge case (#3445) via Facundo Menzella (@facumenzella) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk: this is a release/versioning update (SNAPSHOT -> final) plus docs deployment path changes, with no functional code changes beyond version constants. > > **Overview** > Finalizes the `10.6.0` release by switching all version references from `10.6.0-SNAPSHOT` to `10.6.0` (root `.version`, `gradle.properties`, `Config.frameworkVersion`, and sample/test app `libs.versions.toml` files). > > Updates documentation publishing to point at the `10.6.0` docs path (CircleCI S3 sync target and `docs/index.html` redirect), and prepends the `10.6.0` section to `CHANGELOG.md`/`CHANGELOG.latest.md`. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 4da1697. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->

Motivation
RemoteConfigManager.refreshwas hardcoded to pickblobSources.firstOrNull()with aWIP:comment. The backend already returnspriorityandweighton each source, so this PR wires up the real selection logic.Description
priorityvalue wins.weight:WeightedSourceinterface +List<T>.selectWeighted(Random)extension so the same algorithm can later be reused forApiSource(which has the samepriority/weightshape).RemoteConfigManagernow takes an injectableRandom(defaulted toRandom.Default) for deterministic unit tests.Test plan
WeightedSourceSelectionTestcovers: empty list, single source with zero weight, priority dominance, weighted-pick boundary cases (r=0,r=29,r=30,r=99for weights[30, 70]), zero-weight uniform fallback, negative-weight uniform fallback.RemoteConfigManagerTestupdated: existing multi-source test relaxed to assert delegation; new test asserts the highest-priority source is selected through the manager../gradlew :purchases:testDefaultsBc8DebugUnitTest --tests "com.revenuecat.purchases.common.remoteconfig.*"passes../gradlew detektAllpasses.Note
Medium Risk
Changes remote-config blob source selection from deterministic first-item to priority/weight-based randomness, which can affect which CDN/source is used in production. Risk is moderate but bounded to remote config fetching and covered by new unit tests and injectable RNG for determinism.
Overview
Remote config blob fetching now selects a
BlobSourceby highestpriority, and breaks ties with weighted random usingweight(with a uniform-random fallback for zero/negative weights) rather than always usingblobSources.firstOrNull().This extracts the algorithm into a reusable
WeightedSource+selectWeighted(...)helper, updatesApiSource/BlobSourceto implement it, injectsRandomintoRemoteConfigManagerfor deterministic tests, and expands unit coverage for priority/weight edge cases.Reviewed by Cursor Bugbot for commit 98ba254. Bugbot is set up for automated code reviews on this repo. Configure here.