firebase-android-sdk icon indicating copy to clipboard operation
firebase-android-sdk copied to clipboard

Unable to resume activity: java.util.ConcurrentModificationException in Remote Config

Open FilippoVigani opened this issue 1 year ago • 4 comments

  • Android Studio version: Android Studio Giraffe | 2022.3.1
  • Firebase Component: Remote Config
  • Component version: 21.4.1 (BOM 32.2.2)

I'm seeing a crash from my bug tracker that appears to be coming from remote config, here's the stack trace:

java.util.ConcurrentModificationException: null
    at java.util.LinkedHashMap$LinkedHashIterator.nextNode(LinkedHashMap.java:760)
    at java.util.LinkedHashMap$LinkedKeyIterator.next(LinkedHashMap.java:782)
    at com.google.firebase.remoteconfig.internal.ConfigRealtimeHttpClient.propagateErrors(ConfigRealtimeHttpClient.java:225)
    at com.google.firebase.remoteconfig.internal.ConfigRealtimeHttpClient.makeRealtimeHttpConnection(ConfigRealtimeHttpClient.java:387)
    at com.google.firebase.remoteconfig.internal.ConfigRealtimeHttpClient.startHttpConnection(ConfigRealtimeHttpClient.java:355)
    at com.google.firebase.remoteconfig.internal.ConfigRealtimeHandler.beginRealtime(ConfigRealtimeHandler.java:81)
    at com.google.firebase.remoteconfig.internal.ConfigRealtimeHandler.setBackgroundState(ConfigRealtimeHandler.java:96)
    at com.google.firebase.remoteconfig.FirebaseRemoteConfig.setConfigUpdateBackgroundState(FirebaseRemoteConfig.java:674)
    at com.google.firebase.remoteconfig.RemoteConfigComponent.notifyRCInstances(RemoteConfigComponent.java:341)
    at com.google.firebase.remoteconfig.RemoteConfigComponent.access$100(RemoteConfigComponent.java:60)
    at com.google.firebase.remoteconfig.RemoteConfigComponent$GlobalBackgroundListener.onBackgroundStateChanged(RemoteConfigComponent.java:363)
    at com.google.android.gms.common.api.internal.BackgroundDetector.zza(com.google.android.gms:play-services-basement@@18.2.0:3)
    at com.google.android.gms.common.api.internal.BackgroundDetector.onActivityResumed(com.google.android.gms:play-services-basement@@18.2.0:3)
    at android.app.Application.dispatchActivityResumed(Application.java:450)
    at android.app.Activity.dispatchActivityResumed(Activity.java:1482)
    at android.app.Activity.onResume(Activity.java:2043)
    at com.truescreen.android.MainActivity.onResume(MainActivity.kt:280)
    at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1531)
    at android.app.Activity.performResume(Activity.java:8734)
    at android.app.ActivityThread.performResumeActivity(ActivityThread.java:5351)
    at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:5444)
    at android.app.servertransaction.ResumeActivityItem.execute(ResumeActivityItem.java:54)
    at android.app.servertransaction.ActivityTransactionItem.execute(ActivityTransactionItem.java:45)
    at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:176)
    at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2574)
    at android.os.Handler.dispatchMessage(Handler.java:106)
    at android.os.Looper.loopOnce(Looper.java:226)
    at android.os.Looper.loop(Looper.java:313)
    at android.app.ActivityThread.main(ActivityThread.java:8757)
    at java.lang.reflect.Method.invoke(Method.java)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:571)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1067)
java.lang.RuntimeException: Unable to resume activity {com.truescreen.app/com.truescreen.android.MainActivity}: java.util.ConcurrentModificationException
    at android.app.ActivityThread.performResumeActivity(ActivityThread.java:5378)
    at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:5444)
    at android.app.servertransaction.ResumeActivityItem.execute(ResumeActivityItem.java:54)
    at android.app.servertransaction.ActivityTransactionItem.execute(ActivityTransactionItem.java:45)
    at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:176)
    at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2574)
    at android.os.Handler.dispatchMessage(Handler.java:106)
    at android.os.Looper.loopOnce(Looper.java:226)
    at android.os.Looper.loop(Looper.java:313)
    at android.app.ActivityThread.main(ActivityThread.java:8757)
    at java.lang.reflect.Method.invoke(Method.java)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:571)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1067)

From the stack trace itself it's hard to tell exactly where the issue arises. Some code I use from remote config:

    private val configCoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)

    private val configStatus: MutableStateFlow<ConfigStatus> =
        MutableStateFlow(ConfigStatus.Uninitialized)

    private enum class ConfigStatus {
        Uninitialized, DefaultsReady, RemoteReady
    }

    override val appUrls = configStatus.filter {
            it == ConfigStatus.DefaultsReady || it == ConfigStatus.RemoteReady
        }.flatMapLatest {
            streamRemoteConfig(
                setOf(
                    "example", "test"
                )
            ).retryWithExponentialBackoff(onError = {
                    Timber.w(it)
                })
        }.map {
            ExampleData(
                 example = getString("example"),
                 test = getString("test"),
            )
        }

    private fun streamRemoteConfig(keys: Set<String>): Flow<FirebaseRemoteConfig> {
        return callbackFlow {
            val listener = object : ConfigUpdateListener {
                override fun onUpdate(configUpdate: ConfigUpdate) {
                    Firebase.remoteConfig.activate()
                    if (configUpdate.updatedKeys.intersect(keys).isNotEmpty()) {
                        runBlocking {
                            send(Firebase.remoteConfig)
                        }
                    }
                }

                override fun onError(error: FirebaseRemoteConfigException) {
                    close(error)
                }
            }
            send(Firebase.remoteConfig)
            val registration = Firebase.remoteConfig.addOnConfigUpdateListener(listener)

            awaitClose {
                registration.remove()
            }
        }
    }
    
    override fun initialize() {
        configCoroutineScope.launch {
            retryWithExponentialBackoff {
                Firebase.remoteConfig.setDefaultsAsync(
                    mapOf(
                        // Here i set some defaults...
                    )
                ).await()
                configStatus.value = ConfigStatus.DefaultsReady
            }
            
            retryWithExponentialBackoff {
                Firebase.remoteConfig.fetch(0).await()
                Firebase.remoteConfig.activate()
                configStatus.value = ConfigStatus.RemoteReady
            }
        }
    }

FilippoVigani avatar Oct 18 '23 08:10 FilippoVigani

I found a few problems with this issue:

  • I couldn't figure out how to label this issue, so I've labeled it for a human to triage. Hang tight.
  • This issue does not seem to follow the issue template. Make sure you provide all the required information.

google-oss-bot avatar Oct 18 '23 08:10 google-oss-bot

Hey @FilippoVigani, thanks for reaching out. I've been trying to reproduce this issue but haven't been successful. However, we are still investigating this. We'll keep this open and provide feedback if we figure out something. Feel free to add more details that you think might be helpful. Thanks!

argzdev avatar Dec 29 '23 09:12 argzdev

The same issue happens to my app in production Firebase Component: Remote Config Component version: BOM 32.7.0

Is there anything we could do with this?

Caused by java.util.ConcurrentModificationException
java.util.LinkedHashMap$LinkedHashIterator.nextNode (LinkedHashMap.java:760)
java.util.LinkedHashMap$LinkedKeyIterator.next (LinkedHashMap.java:782)
com.google.firebase.remoteconfig.internal.ConfigRealtimeHttpClient.propagateErrors (ConfigRealtimeHttpClient.java:225)
com.google.firebase.remoteconfig.internal.ConfigRealtimeHttpClient.makeRealtimeHttpConnection (ConfigRealtimeHttpClient.java:387)
com.google.firebase.remoteconfig.internal.ConfigRealtimeHttpClient.startHttpConnection (ConfigRealtimeHttpClient.java:355)
com.google.firebase.remoteconfig.internal.ConfigRealtimeHandler.beginRealtime (ConfigRealtimeHandler.java:81)
com.google.firebase.remoteconfig.internal.ConfigRealtimeHandler.setBackgroundState (ConfigRealtimeHandler.java:96)
com.google.firebase.remoteconfig.FirebaseRemoteConfig.setConfigUpdateBackgroundState (FirebaseRemoteConfig.java:680)
com.google.firebase.remoteconfig.RemoteConfigComponent.notifyRCInstances (RemoteConfigComponent.java:362)
com.google.firebase.remoteconfig.RemoteConfigComponent.access$100 (RemoteConfigComponent.java:64)
com.google.firebase.remoteconfig.RemoteConfigComponent$GlobalBackgroundListener.onBackgroundStateChanged (RemoteConfigComponent.java:400)
com.google.android.gms.common.api.internal.BackgroundDetector.zza (com.google.android.gms:play-services-basement@@18.3.0:3)
com.google.android.gms.common.api.internal.BackgroundDetector.onActivityResumed (com.google.android.gms:play-services-basement@@18.3.0:3)
android.app.Application.dispatchActivityResumed (Application.java:450)
android.app.Activity.dispatchActivityResumed (Activity.java:1516)
android.app.Activity.onResume (Activity.java:2117)
androidx.fragment.app.FragmentActivity.onResume (FragmentActivity.java:309)
com.dazz.hoop.presentation.MainActivity.onResume (MainActivity.kt:121)
android.app.Instrumentation.callActivityOnResume (Instrumentation.java:1603)
android.app.Activity.performResume (Activity.java:9103)
android.app.ActivityThread.performResumeActivity (ActivityThread.java:5399)
android.app.ActivityThread.handleResumeActivity (ActivityThread.java:5507)
android.app.servertransaction.ResumeActivityItem.execute (ResumeActivityItem.java:57)
android.app.servertransaction.ActivityTransactionItem.execute (ActivityTransactionItem.java:45)
android.app.servertransaction.TransactionExecutor.executeLifecycleState (TransactionExecutor.java:180)
android.app.servertransaction.TransactionExecutor.execute (TransactionExecutor.java:98)
android.app.ActivityThread$H.handleMessage (ActivityThread.java:2685)
android.os.Handler.dispatchMessage (Handler.java:106)
android.os.Looper.loopOnce (Looper.java:230)
android.os.Looper.loop (Looper.java:319)
android.app.ActivityThread.main (ActivityThread.java:8893)
java.lang.reflect.Method.invoke (Method.java)
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:608)
com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1103)

AyupovIlgam avatar Feb 20 '24 11:02 AyupovIlgam