-
Notifications
You must be signed in to change notification settings - Fork 50
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
Attempt to fix ANRs by moving some tasks during configure to background #1772
Attempt to fix ANRs by moving some tasks during configure to background #1772
Conversation
internal class IdentityManager( | ||
private val deviceCache: DeviceCache, | ||
private val subscriberAttributesCache: SubscriberAttributesCache, | ||
private val subscriberAttributesManager: SubscriberAttributesManager, | ||
private val offeringsCache: OfferingsCache, | ||
private val backend: Backend, | ||
private val offlineEntitlementsManager: OfflineEntitlementsManager, | ||
private val dispatcher: Dispatcher, | ||
) { | ||
|
||
val currentAppUserID: String |
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.
This could potentially fail now since setting the user id happens asynchronously now right?
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.
hmm right... actually... any call in this class right? for example currentUserIsAnonymous
.
I think we need to make all async and make sure they are all enqueued in the same dispatcher, right?
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.
Yup pretty much, which seems it would be a pretty big undertaking...
0473940
to
293500b
Compare
flushPaywallEvents() | ||
if (firstTimeInForeground && isAndroidNOrNewer()) { | ||
diagnosticsSynchronizer?.syncDiagnosticsFileIfNeeded() | ||
enqueue { |
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.
I don't think there should be any issue with enqueuing all this right? It is all pretty much updating caches, which is already changing into another thread to make the API call. This way we guarantee the accesses to the deviceCache are in the background
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.
Yup I think it should be mostly ok... My only concern is that there is a very small chance of the app moving to background before this is executed, so the code on onAppBackgrounded
being called before... But I don't think that should cause any issues as far as I can tell 👍
deviceCache.cleanupOldAttributionData() | ||
invalidateCustomerInfoAndETagCacheIfNeeded(appUserIDToUse) | ||
|
||
val cacheEditor = deviceCache.startEditing() |
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.
This change (chaining the editions) will probably have marginal effects, but I don't think it hurts
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.
Good ideas! I just left some concerns but I think this should improve things a lot.
flushPaywallEvents() | ||
if (firstTimeInForeground && isAndroidNOrNewer()) { | ||
diagnosticsSynchronizer?.syncDiagnosticsFileIfNeeded() | ||
enqueue { |
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.
Yup I think it should be mostly ok... My only concern is that there is a very small chance of the app moving to background before this is executed, so the code on onAppBackgrounded
being called before... But I don't think that should cause any issues as far as I can tell 👍
appUserID: String, | ||
cacheEditor: SharedPreferences.Editor, | ||
) { | ||
if (backend.verificationMode == SignatureVerificationMode.Disabled) { |
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.
I guess this is to avoid the getCachedCustomerInfo
below from happening when it's disabled right? Makes sense!
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.
exactly! I noticed we were getting the customer info all the time! I will add a comment here to make sure it's clear
purchases/src/main/kotlin/com/revenuecat/purchases/identity/IdentityManager.kt
Show resolved
Hide resolved
purchases/src/main/kotlin/com/revenuecat/purchases/PurchasesOrchestrator.kt
Outdated
Show resolved
Hide resolved
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1772 +/- ##
==========================================
+ Coverage 83.26% 83.31% +0.05%
==========================================
Files 223 223
Lines 7617 7634 +17
Branches 1071 1072 +1
==========================================
+ Hits 6342 6360 +18
+ Misses 854 853 -1
Partials 421 421 ☔ View full report in Codecov by Sentry. |
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.
LGTM!
@@ -179,6 +179,7 @@ internal class PurchasesFactory( | |||
offeringsCache, | |||
backend, | |||
offlineEntitlementsManager, | |||
dispatcher, |
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.
I see this dispatcher was only used previously to parse offerings right? I think that should be ok then (as long as it's not the same dispatcher we run backend queries on)... I'm wondering if we should rename this, but it could come on a separate PR if needed.
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.
I agree we should rename it. I had originally created a new backgroundTaskDispatcher before I noticed we could reuse this one. Is that naming good?
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.
Yeah, I think that could be enough as a "generic" dispatcher 👍
IdentityManager
to execute on a thread**This is an automatic release.** ### Bugfixes * Attempt to fix ANRs by moving some tasks during configure to background (#1772) via Cesar de la Vega (@vegaro) * Use trimmed API key (#1768) via Toni Rico (@tonidero) ### Other Changes * Remove leftovers `ExperimentalPreviewRevenueCatUIPurchasesAPI` (#1778) via Cesar de la Vega (@vegaro) * Update migration docs adding back support for `DEFERRED` upgrades (#1774) via Toni Rico (@tonidero) * Add 7.12.0 to CHANGELOG (#1769) via Toni Rico (@tonidero) --------- Co-authored-by: revenuecat-ops <[email protected]> Co-authored-by: Toni Rico <[email protected]>
Description
This PR addresses a significant number of ANRs (Application Not Responding errors) reported, such as in issue #1629.
Problem
The method
IdentityManager.configure
is currently running on the main thread and accessingSharedPreferences
. AccessingSharedPreferences
can be costly, especially if the XML file storing the preferences is large. This cost is particularly high during the initial accesses, as the XML file needs to be read and parsed, which can block the main thread and lead to ANRs.Solution
Custom Dispatcher: This PR introduces a custom dispatcher to the
IdentityManager
, allowing configure to run on a background thread. By moving this operation off the main thread, we avoid blocking it with the potentially expensive SharedPreferences access.Main Thread Optimization: The remaining setup in
PurchasesOrchestrator
has been deferred to execute after configure completes, ensuring the main thread remains unblocked and improving overall app responsiveness.Notes
While this fix should mitigate the reported ANRs, it's possible there are other underlying causes contributing to these issues. This PR serves as a good initial step towards improving performance.