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

ANRs when calling Purchases.configure() during Application.onCreate() #1629

Closed
snapwoodapps opened this issue Feb 29, 2024 · 6 comments
Closed

Comments

@snapwoodapps
Copy link

snapwoodapps commented Feb 29, 2024

I'm seeing in the field several dozen ANRs happening inside Purchases.configure. I am calling configure during Application.onCreate(), which matches your examples.

For reference, I have 16 products defined for Google Play where this is failing and 13 products defined for Amazon. These are part of 8 offerings, most with 3 packages. I don't know if the quantity here is impacting the configure time, but it doesn't seem like much compared to other apps.

Here are a sampling of my ANRs. Each ANR exception is a bit different, likely due to how far configure got in reconstituting its data before the system killed it with the ANR hammer. The second one is especially concerning to me since it shows json parsing happening on the main thread, which implies I/O and that is generally a bad idea on the main thread.

at com.revenuecat.purchases.PurchasesFactory.createPurchases (PurchasesFactory.kt:166)
at com.revenuecat.purchases.PurchasesFactory.createPurchases$default (PurchasesFactory.kt:58)
at com.revenuecat.purchases.Purchases$Companion.configure (Purchases.kt:852)

at kotlinx.serialization.modules.SerializersModuleKt. (SerializersModule.kt:80)
at kotlinx.serialization.modules.SerializersModuleKt.getEmptySerializersModule (SerializersModule.kt)
at kotlinx.serialization.modules.SerializersModuleBuildersKt.EmptySerializersModule (SerializersModuleBuilders.kt:40)
at kotlinx.serialization.json.Json$Default. (Json.kt:71)
at kotlinx.serialization.json.Json$Default. (Json.kt)
at kotlinx.serialization.json.Json. (Json.kt)
at kotlinx.serialization.json.JsonKt.Json$default (Json.kt:197)
at com.revenuecat.purchases.common.OfferingParser. (OfferingParser.kt:21)
at com.revenuecat.purchases.OfferingParserFactory.createOfferingParser (OfferingParserFactory.kt:12)
at com.revenuecat.purchases.PurchasesFactory.createPurchases (PurchasesFactory.kt:218)
at com.revenuecat.purchases.PurchasesFactory.createPurchases$default (PurchasesFactory.kt:58)
at com.revenuecat.purchases.Purchases$Companion.configure (Purchases.kt:852)

at com.revenuecat.purchases.BillingFactory.createBilling (BillingFactory.kt:24)
at com.revenuecat.purchases.PurchasesFactory.createPurchases (PurchasesFactory.kt:132)
at com.revenuecat.purchases.PurchasesFactory.createPurchases$default (PurchasesFactory.kt:58)
at com.revenuecat.purchases.Purchases$Companion.configure (Purchases.kt:852)

at java.util.concurrent.ConcurrentHashMap. (ConcurrentHashMap.java:844)
at kotlinx.serialization.json.internal.CreateMapForCacheKt.createMapForCache (createMapForCache.kt:15)
at kotlinx.serialization.json.internal.DescriptorSchemaCache. (SchemaCache.kt:20)
at kotlinx.serialization.json.Json. (Json.kt:64)
at kotlinx.serialization.json.Json. (Json.kt)
at kotlinx.serialization.json.Json$Default. (Json.kt:71)
at kotlinx.serialization.json.Json$Default. (Json.kt)
at kotlinx.serialization.json.Json. (Json.kt)
at kotlinx.serialization.json.JsonKt.Json$default (Json.kt:197)
at com.revenuecat.purchases.common.OfferingParser. (OfferingParser.kt:21)
at com.revenuecat.purchases.OfferingParserFactory.createOfferingParser (OfferingParserFactory.kt:12)
at com.revenuecat.purchases.PurchasesFactory.createPurchases (PurchasesFactory.kt:218)
at com.revenuecat.purchases.PurchasesFactory.createPurchases$default (PurchasesFactory.kt:58)
at com.revenuecat.purchases.Purchases$Companion.configure (Purchases.kt:852)

at kotlin.Pair. (Tuples.kt:26)
at kotlin.TuplesKt.to (Tuples.kt:43)
at com.revenuecat.purchases.common.BackendHelper. (BackendHelper.kt:15)
at com.revenuecat.purchases.PurchasesFactory.createPurchases (PurchasesFactory.kt:120)
at com.revenuecat.purchases.PurchasesFactory.createPurchases$default (PurchasesFactory.kt:58)
at com.revenuecat.purchases.Purchases$Companion.configure (Purchases.kt:852)

at kotlin.jvm.internal.Intrinsics.checkNotNullParameter (Intrinsics.java)
at com.revenuecat.purchases.common.networking.ETagManager. (ETagManager.kt)
at com.revenuecat.purchases.common.networking.ETagManager. (ETagManager.kt:51)
at com.revenuecat.purchases.PurchasesFactory.createPurchases (PurchasesFactory.kt:86)
at com.revenuecat.purchases.PurchasesFactory.createPurchases$default (PurchasesFactory.kt:58)
at com.revenuecat.purchases.Purchases$Companion.configure (Purchases.kt:852)

Now I could call configure in the background, but that could introduce race conditions around Purchases being configured. So I would like to know what the design intent is here and if there is a bug in doing too much I/O. Also depending upon the answer, your examples may need to be changed.

@RCGitBot
Copy link
Contributor

👀 We've just linked this issue to our internal tracker and notified the team. Thank you for reporting, we're checking this out!

@rglanz-rc
Copy link
Contributor

Hi,
Happy to help here. Could you share the version you are using and an example of how you're configuring the SDK?

@snapwoodapps
Copy link
Author

snapwoodapps commented Mar 4, 2024

The app is on the latest 7.6.0 version, but the version in error reports vary over the last 28 days. Since the ANR locations are not consistent, I thought it was more important to demonstrate the ANRs happening than to only cherry pick those on the latest version. If you want me to do that, I can.

This error is very rare with 60 events over 28 days out of many thousands of users. That said, it is the top crash in my app so it got my attention and if you were doing I/O, that should be fixed. But I see now the stack trace is more likely just loading the parser and not actually parsing.

I don't believe I'm configuring the SDK any differently than your example.

In my Application:

    public void onCreate()
    {
          super.onCreate();
          ... look in shared preferences for userId ...
          Purchases.configure(PurchasesConfiguration.Builder(applicationContext, sApiKey!!).appUserID(userId).store(Store.PLAY_STORE).build())

@snapwoodapps
Copy link
Author

snapwoodapps commented Mar 5, 2024

Here is a stack from the latest version showing work being done on the main thread to restore subscriber attributes from the cache. This implies I/O happening on the main thread.

kotlin.collections.MapsKt__MapsKt.toMap (Maps.kt)
com.revenuecat.purchases.subscriberattributes.SubscriberAttributesFactoriesKt.buildSubscriberAttributesMapPerUser (subscriberAttributesFactories.kt:17)
com.revenuecat.purchases.subscriberattributes.caching.SubscriberAttributesCache.getAllStoredSubscriberAttributes (SubscriberAttributesCache.kt:42)
com.revenuecat.purchases.subscriberattributes.caching.SubscriberAttributesCache.deleteSyncedSubscriberAttributesForOtherUsers (SubscriberAttributesCache.kt:97)
com.revenuecat.purchases.subscriberattributes.caching.SubscriberAttributesCache.cleanUpSubscriberAttributeCache (SubscriberAttributesCache.kt:81)
com.revenuecat.purchases.identity.IdentityManager.configure (IdentityManager.kt:53)
com.revenuecat.purchases.PurchasesOrchestrator.<init> (PurchasesOrchestrator.kt:139)
com.revenuecat.purchases.PurchasesOrchestrator.<init> (PurchasesOrchestrator.kt:70)
com.revenuecat.purchases.PurchasesFactory.createPurchases (PurchasesFactory.kt:254)
com.revenuecat.purchases.PurchasesFactory.createPurchases$default (PurchasesFactory.kt:58)
com.revenuecat.purchases.Purchases$Companion.configure (Purchases.kt:852)

@ChrisKruegerDev
Copy link

ChrisKruegerDev commented Apr 1, 2024

@rglanz-rc Here are some more stack traces. Seeing 30+ ANRs within the last 90 days after calling Purchases$Companion.configure from the Android startup library.

at com.revenuecat.purchases.VerificationResult.<init> (VerificationResult.kt:13)
  at com.revenuecat.purchases.VerificationResult.<clinit> (VerificationResult.kt:19)
  at com.revenuecat.purchases.common.caching.DeviceCache.getCachedCustomerInfo (DeviceCache.kt:131)
  at com.revenuecat.purchases.identity.IdentityManager.invalidateCustomerInfoAndETagCacheIfNeeded (IdentityManager.kt:139)
  at com.revenuecat.purchases.identity.IdentityManager.configure (IdentityManager.kt:55)
  at com.revenuecat.purchases.PurchasesOrchestrator.<init> (PurchasesOrchestrator.kt:139)
  at com.revenuecat.purchases.PurchasesOrchestrator.<init> (PurchasesOrchestrator.kt:70)
  at com.revenuecat.purchases.PurchasesFactory.createPurchases (PurchasesFactory.kt:254)
  at com.revenuecat.purchases.PurchasesFactory.createPurchases$default (PurchasesFactory.kt:58)
  at com.revenuecat.purchases.Purchases$Companion.configure (Purchases.kt:852)
at java.lang.Character.isWhitespace (Character.java:6923)
  at kotlin.text.CharsKt__CharJVMKt.isWhitespace (CharsKt__CharJVM.kt:98)
  at kotlin.text.StringsKt__StringsJVMKt.isBlank (StringsKt__StringsJVM.kt:624)
at com.revenuecat.purchases.PurchasesOrchestrator.<clinit> (PurchasesOrchestrator.kt:1123)
  at com.revenuecat.purchases.Purchases$Companion.getPlatformInfo (Purchases.kt:757)
  at com.revenuecat.purchases.Purchases$Companion.configure (Purchases.kt:854)
  at com.revenuecat.purchases.PurchasesFactory.createPurchases (PurchasesFactory.kt:124)
  at com.revenuecat.purchases.PurchasesFactory.createPurchases$default (PurchasesFactory.kt:58)
  at com.revenuecat.purchases.Purchases$Companion.configure (Purchases.kt:852)
  at com.revenuecat.purchases.AttributionFetcherFactory$WhenMappings.<clinit> (AttributionFetcherFactory.kt)
  at com.revenuecat.purchases.AttributionFetcherFactory.createAttributionFetcher (AttributionFetcherFactory.kt:13)
  at com.revenuecat.purchases.PurchasesFactory.createPurchases (PurchasesFactory.kt:144)
  at com.revenuecat.purchases.PurchasesFactory.createPurchases$default (PurchasesFactory.kt:58)
  at com.revenuecat.purchases.Purchases$Companion.configure (Purchases.kt:852)
  at com.revenuecat.purchases.common.networking.ETagManager.<init> (ETagManager.kt:54)
  at com.revenuecat.purchases.PurchasesFactory.createPurchases (PurchasesFactory.kt:86)
  at com.revenuecat.purchases.PurchasesFactory.createPurchases$default (PurchasesFactory.kt:58)
  at com.revenuecat.purchases.Purchases$Companion.configure (Purchases.kt:852)
#00  pc 0x0000000000019ad0  /system/lib/libc.so (__bionic_clone+20)
  #01  pc 0x000000000001e54b  /system/lib/libc.so (clone+158)
  #02  pc 0x0000000000065793  /system/lib/libc.so (pthread_create+506)
  #03  pc 0x000000000037c389  /system/lib/libart.so (art::Thread::CreateNativeThread(_JNIEnv*, _jobject*, unsigned int, bool)+448)
  at java.lang.Thread.nativeCreate (Native method)
  at java.lang.Thread.start (Thread.java:733)
  at android.app.SharedPreferencesImpl.startLoadFromDisk (SharedPreferencesImpl.java:124)
  at android.app.SharedPreferencesImpl.<init> (SharedPreferencesImpl.java:113)
  at android.app.ContextImpl.getSharedPreferences (ContextImpl.java:471)
  at android.app.ContextImpl.getSharedPreferences (ContextImpl.java:441)
  at android.content.ContextWrapper.getSharedPreferences (ContextWrapper.java:179)
  at com.revenuecat.purchases.common.networking.ETagManager$Companion.initializeSharedPreferences (ETagManager.kt:169)
  at com.revenuecat.purchases.PurchasesFactory.createPurchases (PurchasesFactory.kt:86)
  at com.revenuecat.purchases.PurchasesFactory.createPurchases$default (PurchasesFactory.kt:58)
  at com.revenuecat.purchases.Purchases$Companion.configure (Purchases.kt:852)

vegaro added a commit that referenced this issue Jul 8, 2024
…nd (#1772)

## 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 accessing `SharedPreferences`. Accessing `SharedPreferences`
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.
Copy link

This issue has been automatically locked due to no recent activity after it was closed. Please open a new issue for related reports.

@github-actions github-actions bot locked and limited conversation to collaborators Sep 21, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants