firebase / firebase-android-sdk

Firebase Android SDK
https://firebase.google.com
Apache License 2.0
2.23k stars 565 forks source link

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

Open FilippoVigani opened 8 months ago

FilippoVigani commented 8 months ago

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
            }
        }
    }
google-oss-bot commented 8 months ago

I found a few problems with this issue:

argzdev commented 6 months ago

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!

AyupovIlgam commented 4 months ago

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)