Skip to content

ANR/Deadlock during Json initialization with a custom serializer #3069

@VEZE

Description

@VEZE

Describe the bug
When initializing a Json instance with a SerializersModule that includes a custom KSerializer, a class initialization deadlock can occur, leading to an ANR (Application Not Responding) on Android. This seems to happen specifically when the custom serializer's descriptor is initialized eagerly (without by lazy).

To Reproduce
Create a custom KSerializer for any type (e.g., java.time.Instant). Initialize its descriptor property directly.

object InstantSerializer : KSerializer<Instant> {
    override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Instant", PrimitiveKind.STRING)
    // ...
}

Create a singleton object that configures a Json instance using this serializer.

object JsonHolder {

    private val serializersModule: SerializersModule = SerializersModule {
        contextual(Instant::class, InstantSerializer)
    }

    val baseJson: Json by lazy {
        Json {
            serializersModule = JsonHolder.serializersModule
            explicitNulls = false
            ignoreUnknownKeys = true
            encodeDefaults = true
            coerceInputValues = true
        }
    }
}

Access the JsonHolder singleton for the first time on the main thread during application startup, for example, as part of a Dagger dependency graph initialization.

       main (runnable):tid=[TID] systid=[SYS_TID] 
#00 pc [ADDRESS] libart.so (art::DumpNativeStack + [OFFSET]) (BuildId: [BUILD_ID])
#01 pc [ADDRESS] libart.so (art::Thread::DumpStack const + [OFFSET]) (BuildId: [BUILD_ID])
#02 pc [ADDRESS] libart.so (art::DumpCheckpoint::Run + [OFFSET]) (BuildId: [BUILD_ID])
#03 pc [ADDRESS] libart.so (art::Thread::RunCheckpointFunction + [OFFSET]) (BuildId: [BUILD_ID])
#04 pc [ADDRESS] libart.so (artTestSuspendFromCode + [OFFSET]) (BuildId: [BUILD_ID])
#05 pc [ADDRESS] libart.so (art_quick_test_suspend + [OFFSET]) (BuildId: [BUILD_ID])
        at java.lang.String.fillBytesLatin1(Native method)
        at java.lang.String.fillBytes(String.java:4400)
        at java.lang.AbstractStringBuilder.putStringAt(AbstractStringBuilder.java:1693)
        at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:549)
        at java.lang.StringBuilder.append(StringBuilder.java:186)
        at kotlinx.serialization.internal.PrimitiveArrayDescriptor.<init>(CollectionDescriptors.kt:114)
        at kotlinx.serialization.internal.PrimitiveArraySerializer.<init>(CollectionSerializers.kt:147)
        at kotlinx.serialization.internal.BooleanArraySerializer.<init>(PrimitiveArraysSerializers.kt:370)
        at kotlinx.serialization.internal.BooleanArraySerializer.<clinit>(PrimitiveArraysSerializers.kt:12)
        at kotlinx.serialization.builtins.BuiltinSerializersKt.BooleanArraySerializer(BuiltinSerializers.kt:174)
        at kotlinx.serialization.internal.PlatformKt.initBuiltins(Platform.kt:188)
        at kotlinx.serialization.internal.PrimitivesKt.<clinit>(Primitives.kt:18)
        at kotlinx.serialization.descriptors.SerialDescriptorsKt.PrimitiveSerialDescriptor(SerialDescriptors.kt:91)
        at com.mycompany.app.core.data.json.InstantSerializer.<clinit>(InstantSerializer.kt:18)
        at com.mycompany.app.core.data.utils.JsonProvider.<clinit>(JsonProvider.kt:13)
        at com.mycompany.app.core.data.di.module.CoreDataModule$JsonInner.provideJson(CoreDataModule.java:94)
        at com.mycompany.app.core.data.di.module.CoreDataModule_JsonInner_ProvideJsonFactory.provideJson(CoreDataModule_JsonInner_ProvideJsonFactory.java:38)
        at com.mycompany.app.core.data.di.DaggerCoreDataComponent$CoreDataComponentImpl.getBaseJson(DaggerCoreDataComponent.java:168)
        at com.mycompany.app.core.network.di.DaggerCoreNetworkComponent$CoreNetworkComponentImpl.getBaseJsonRetrofitConverterFactory(DaggerCoreNetworkComponent.java:441)
        at com.mycompany.app.feature.featureA.impl.di.DaggerRetrofitBinderComponent$RetrofitBinderComponentImpl.getRetrofit(DaggerRetrofitBinderComponent.java:55)
        at com.mycompany.app.feature.featureA.impl.di.DaggerExperimentsFeatureComponent$ExperimentsFeatureComponentImpl$SwitchingProvider.get(DaggerExperimentsFeatureComponent.java:145)
        at dagger.internal.DoubleCheck.getSynchronized(DoubleCheck.java:54)
        at dagger.internal.DoubleCheck.get(DoubleCheck.java:45)
        at com.mycompany.app.feature.featureA.impl.di.DaggerExperimentsFeatureComponent$ExperimentsFeatureComponentImpl$SwitchingProvider.get(DaggerExperimentsFeatureComponent.java:142)
        at dagger.internal.DoubleCheck.getSynchronized(DoubleCheck.java:54)
        at dagger.internal.DoubleCheck.get(DoubleCheck.java:45)
        at com.mycompany.app.feature.featureA.impl.di.DaggerExperimentsFeatureComponent$ExperimentsFeatureComponentImpl$SwitchingProvider.get(DaggerExperimentsFeatureComponent.java:139)
        at dagger.internal.DoubleCheck.getSynchronized(DoubleCheck.java:54)
        at dagger.internal.DoubleCheck.get(DoubleCheck.java:45)
        at com.mycompany.app.feature.featureA.impl.di.DaggerExperimentsFeatureComponent$ExperimentsFeatureComponentImpl.getGenericExperimentsInteractor(DaggerExperimentsFeatureComponent.java:111)
        at com.mycompany.app.feature.featureB.impl.di.DaggerFeatureToggleApiComponent$FeatureToggleApiComponentImpl$SwitchingProvider.get(DaggerFeatureToggleApiComponent.java:209)
        at dagger.internal.DoubleCheck.getSynchronized(DoubleCheck.java:54)
        at dagger.internal.DoubleCheck.get(DoubleCheck.java:45)

Expected behavior

The Json object should be created and initialized without freezing the main thread or causing an ANR. The application should start up smoothly.

Environment

  • Kotlin version: 2.1.21
  • Library version: 1.7.3
  • Platforms: Android
  • Gradle version: 8.14

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions