stacktrace-decoroutinator icon indicating copy to clipboard operation
stacktrace-decoroutinator copied to clipboard

[despite using init] Android: Stacktrace-decoroutinator runtime can not be loaded because BaseContinuationImpl was already loaded.

Open cee-dee opened this issue 1 year ago • 3 comments

I've got this IllegalStateException although I'm using DecoroutinatorRuntime.load() in the init { } block of my Application class. In order to rule out that it is a general problem, I used an Android Studio template and created a simple sample app, added a custom MyApplication to the manifest and confirmed that DecoroutinatorRuntime.load() is not throwing.

To compare the initialization process of the two apps (the working and the not working one) I replaced the call to DecoroutinatorRuntime.load() with error("Error message"). It looks like it's exactly the same:

I noticed that the callstacks then load pretty identical:

Simple Sample App (Decoroutinator working)

Process: com.example.myapplication, PID: 21334
java.lang.RuntimeException: Unable to instantiate application com.example.myapplication.MyApplication package com.example.myapplication: 
java.lang.IllegalStateException: Error message
	at android.app.LoadedApk.makeApplicationInner(LoadedApk.java:1466)
	at android.app.LoadedApk.makeApplicationInner(LoadedApk.java:1395)
	at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6959)
	at android.app.ActivityThread.-$$Nest$mhandleBindApplication(Unknown Source:0)
	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2236)
	at android.os.Handler.dispatchMessage(Handler.java:106)
	at android.os.Looper.loopOnce(Looper.java:205)
	at android.os.Looper.loop(Looper.java:294)
	at android.app.ActivityThread.main(ActivityThread.java:8177)
	at java.lang.reflect.Method.invoke(Native Method)
	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)
Caused by: java.lang.IllegalStateException: Error message
	at com.example.myapplication.MyApplication.<init>(MyApplication.kt:8)
	at java.lang.Class.newInstance(Native Method)
	at android.app.AppComponentFactory.instantiateApplication(AppComponentFactory.java:76)
	at androidx.core.app.CoreComponentFactory.instantiateApplication(CoreComponentFactory.java:52)
	at android.app.Instrumentation.newApplication(Instrumentation.java:1282)
	at android.app.LoadedApk.makeApplicationInner(LoadedApk.java:1458)
	at android.app.LoadedApk.makeApplicationInner(LoadedApk.java:1395) 
	at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6959) 
	at android.app.ActivityThread.-$$Nest$mhandleBindApplication(Unknown Source:0) 
	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2236) 
	at android.os.Handler.dispatchMessage(Handler.java:106) 
	at android.os.Looper.loopOnce(Looper.java:205) 
	at android.os.Looper.loop(Looper.java:294) 
	at android.app.ActivityThread.main(ActivityThread.java:8177) 
	at java.lang.reflect.Method.invoke(Native Method) 
	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552) 
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971) 

my real app (Decoroutinator not working)

Process: de.company.android.component.alpha, PID: 26890
java.lang.RuntimeException: Unable to instantiate application de.company.android.component.MyApplication package de.check24.android.homes.alpha:
java.lang.IllegalStateException: Error message
	at android.app.LoadedApk.makeApplicationInner(LoadedApk.java:1466)
	at android.app.LoadedApk.makeApplicationInner(LoadedApk.java:1395)
	at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6959)
	at android.app.ActivityThread.-$$Nest$mhandleBindApplication(Unknown Source:0)
	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2236)
	at android.os.Handler.dispatchMessage(Handler.java:106)
	at android.os.Looper.loopOnce(Looper.java:205)
	at android.os.Looper.loop(Looper.java:294)
	at android.app.ActivityThread.main(ActivityThread.java:8177)
	at java.lang.reflect.Method.invoke(Native Method)
	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)
Caused by: java.lang.IllegalStateException: Error message
	at de.company.android.component.MyApplication.<init>(MyApplication.kt:18)
	at java.lang.Class.newInstance(Native Method)
	at android.app.AppComponentFactory.instantiateApplication(AppComponentFactory.java:76)
	at androidx.core.app.CoreComponentFactory.instantiateApplication(CoreComponentFactory.java:51)
	at android.app.Instrumentation.newApplication(Instrumentation.java:1282)
	at android.app.LoadedApk.makeApplicationInner(LoadedApk.java:1458)
	at android.app.LoadedApk.makeApplicationInner(LoadedApk.java:1395) 
	at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6959) 
	at android.app.ActivityThread.-$$Nest$mhandleBindApplication(Unknown Source:0) 
	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2236) 
	at android.os.Handler.dispatchMessage(Handler.java:106) 
	at android.os.Looper.loopOnce(Looper.java:205) 
	at android.os.Looper.loop(Looper.java:294) 
	at android.app.ActivityThread.main(ActivityThread.java:8177) 
	at java.lang.reflect.Method.invoke(Native Method) 
	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552) 
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971) 

Any ideas how I can find out what is causing this or how to fix this? I could imagine that some part of the app is doing some static initialization and is using Coroutines there. But since it is a huge codebase, I don't have a clue how I could start investigating on that -- exchanging the ClassLoader and setting a breakpoint has the same issue: it's already too late.

cee-dee avatar Mar 21 '24 13:03 cee-dee

@cee-dee Maybe you can add a breakpoint on the constructor of the class kotlin.coroutines.jvm.internal.BaseContinuationImpl to try to figure out when this class loads initially.

Anamorphosee avatar Mar 21 '24 14:03 Anamorphosee

Thank's for the awesome speed of your support! I've already tried to set breakpoints, but either no breakpoint actually breaks, or it never reaches the init block of my App.

This setup with emulated breakpoints (basically) never reaches the init block: BaseContinuationImpl

Do you have a concrete suggestion of where I should set breakpoints?

cee-dee avatar Mar 21 '24 14:03 cee-dee

Is there anything else I can try to find the cause?

cee-dee avatar May 07 '24 11:05 cee-dee

@cee-dee Just released SD version 2.4.1 with a big rework for Android. From now on SD for Android requires only the Gradle plugin and no other dependencies or method calls are required.

Anamorphosee avatar Aug 02 '24 12:08 Anamorphosee

@Anamorphosee Sorry for getting back to you late, I now tried it: the application starts up, but I don't see the real callstack on a crash -- I tried your usage example and got this:

java.lang.Exception: exception at 1729242220513
	at com.example.app.Test.rec(TopDestinationsViewModelDelegateImpl.kt:120)
	at com.example.app.Test$rec$1.invokeSuspend(Unknown Source:15)
	at dev.reformator.stacktracedecoroutinator.common.internal.UnknownSpecMethodsFactory$Spec.resumeNext(unknown.kt:83)
	at dev.reformator.stacktracedecoroutinator.common.internal.UnknownKt.unknown(unknown.kt:28)
	at dev.reformator.stacktracedecoroutinator.common.internal.UnknownKt.unknown(unknown.kt:20)
	at dev.reformator.stacktracedecoroutinator.common.internal.UnknownKt.unknown(unknown.kt:20)
	at dev.reformator.stacktracedecoroutinator.common.internal.UnknownKt.unknown(unknown.kt:20)
	at dev.reformator.stacktracedecoroutinator.common.internal.UnknownKt.unknown(unknown.kt:20)
	at dev.reformator.stacktracedecoroutinator.common.internal.UnknownKt.unknown(unknown.kt:20)
	at dev.reformator.stacktracedecoroutinator.common.internal.UnknownKt.unknown(unknown.kt:20)
	at dev.reformator.stacktracedecoroutinator.common.internal.UnknownKt.unknown(unknown.kt:20)
	at dev.reformator.stacktracedecoroutinator.common.internal.UnknownKt.unknown(unknown.kt:20)
	at dev.reformator.stacktracedecoroutinator.common.internal.UnknownKt.unknown(unknown.kt:20)
	at dev.reformator.stacktracedecoroutinator.common.internal.UnknownKt.unknown(unknown.kt:20)
	at dev.reformator.stacktracedecoroutinator.common.internal.AwakenerKt.callSpecMethods(awakener.kt:80)
	at dev.reformator.stacktracedecoroutinator.common.internal.AwakenerKt.awake(awakener.kt:32)
	at dev.reformator.stacktracedecoroutinator.common.internal.Provider.awakeBaseContinuation(provider-impl.kt:43)
	at dev.reformator.stacktracedecoroutinator.provider.DecoroutinatorProviderApiKt.awakeBaseContinuation(provider-api.kt:47)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(Unknown Source:20)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:101)
	at android.os.Handler.handleCallback(Handler.java:958)
	at android.os.Handler.dispatchMessage(Handler.java:99)
	at android.os.Looper.loopOnce(Looper.java:205)
	at android.os.Looper.loop(Looper.java:294)
	at android.app.ActivityThread.main(ActivityThread.java:8177)
	at java.lang.reflect.Method.invoke(Native Method)
	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)
	Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@b93d395, Dispatchers.Main.immediate]

cee-dee avatar Oct 18 '24 09:10 cee-dee

Hi, @cee-dee. Could you please show the output of method DecoroutinatorCommonApi.getStatus { it() } to check if Decoroutinator has been installed properly? And did you try to call cradle clean after adding Decoroutinator Gradle plugin? And do you use the latest version(2.4.5) of the plugin?

Anamorphosee avatar Oct 18 '24 09:10 Anamorphosee

I used id("dev.reformator.stacktracedecoroutinator") version "2.4.5" in my App gradle module as described. I did a Gradle Sync and a clean build, also issued gradle clean to make sure.

In order for calling DecoroutinatorCommonApi.getStatus { it() } I had to apply the Plugin in another build.gradle. (one module provides a library -- there I needed to add it -- and the other just runs the library code in a container application -- there I previously tried to apply the Plugin)

During compilation (of the code where I added the Plugin to the library code) I now get this error:

com.android.tools.r8.internal.jf: Multiple annotations of type `kotlin.coroutines.jvm.internal.DebugMetadata`

I've added

-keep @kotlin.coroutines.jvm.internal.DebugMetadata class * { *; }
-keep @dev.reformator.stacktracedecoroutinator.provider.DecoroutinatorTransformed class * { *; }

to my ProGuard configuration.

Am I missing something?

cee-dee avatar Oct 18 '24 11:10 cee-dee

During compilation (of the code where I added the Plugin to the library code) I now get this error: com.android.tools.r8.internal.jf: Multiple annotations of type kotlin.coroutines.jvm.internal.DebugMetadata

Seems interesting. Could you please delete the ProGuard rule -keep @kotlin.coroutines.jvm.internal.DebugMetadata class * { *; }?

And Decoroutinator plugin should be applied to the app build.gradle only. It does class transformation and adds decoroutinator-common dependency with the method DecoroutinatorCommonApi.getStatus { it() } automatically.

Anamorphosee avatar Oct 18 '24 12:10 Anamorphosee

Ok.

Now, I only added

-keep @dev.reformator.stacktracedecoroutinator.provider.DecoroutinatorTransformed class * { *; }

to the ProGuard rules.

My build.gradle of the App (containing the Application class) now contains (as the only build.gradle file)

plugins {
    id("dev.reformator.stacktracedecoroutinator") version "2.4.5"
}

My Application class contains

    override fun onCreate() {
        super.onCreate()

        val status = DecoroutinatorCommonApi.getStatus { it() }
        logger { "---------------------------- $status" }

which outputs

---------------------------- DecoroutinatorStatus(successful=true, description=no issues detected)

In the library Gradle module's code I added your sample code in a ViewModel:

viewModelScope.launch {
    Test.rec(10)
 }
 
 ...
 
 object Test {
    suspend fun rec(depth: Int) {
        if (depth == 0) {
            yield()
            throw Exception("exception at ${System.currentTimeMillis()}")
        }
        rec(depth - 1)
    }
}

I did a gradle clean again. But still, I get the callstack from above.

cee-dee avatar Oct 18 '24 13:10 cee-dee

Are call of DecoroutinatorCommonApi.getStatus { it() } and declaration of object Test contained in the same sourceSet? For example, getStatus could be called in src/main and object Test in tests. If so, please check get output of getStatus in the source set of object Test ?

Anamorphosee avatar Oct 18 '24 14:10 Anamorphosee

No, they aren't. The structure of the project basically is like that: a) one Gradle module for a library which contains all of the app functionality b) one Gradle module for a wrapper app which embeds the library from a)

In the latest version, I included Decoroutinator into the module from b), but implemented and called the object Test inside of module from a) -- I mostly need to resolve the callstacks in the library.

So, from what I get from your last message, I cannot achieve it as you asked me to include the Gradle Plugin to the App module (which is the module from b). The library does not know anything of the wrapper app.

cee-dee avatar Oct 18 '24 14:10 cee-dee

Ok than you should

  1. apply Decourutinator Gradle plugin to module b
  2. add dependency dev.reformator.stacktracedecoroutinator:stacktrace-decoroutinator-common:2.4.5 to module a
  3. make sure that calls of getStatus and your test are in the same source sets. And check the output of getStatus

Anamorphosee avatar Oct 18 '24 14:10 Anamorphosee

And 1 more question: how does module a include in module b?

Anamorphosee avatar Oct 18 '24 14:10 Anamorphosee

I include module a in module b with

implementation(project(":module_a")) { transitive = false }

cee-dee avatar Oct 18 '24 15:10 cee-dee

I set up everything as you described, and made a clean build. Now I get that output:

---------------------------- DecoroutinatorStatus(successful=false, description=the stack trace contains unknown frames. Probably some local source is not transformed)

cee-dee avatar Oct 18 '24 15:10 cee-dee

it means that classes of module a don't transform for some reason. I tried but couldn't reproduce the issue. So far you can fix it by allowing transformation at runtime. To do it you should add configuration below to your cradle.build

stacktraceDecoroutinator {
    addAndroidRuntimeDependency = true
}

And it will be great if you could provide a minimal example of your project configuration reproducing the issue.

Anamorphosee avatar Oct 19 '24 13:10 Anamorphosee

I added

stacktraceDecoroutinator {
    addAndroidRuntimeDependency = true
}

to the module b build.gradle and everything is working fine, the status is

DecoroutinatorStatus(successful=true, description=no issues detected)

Also, I see the expected callstack :-) Thank you very much for your helpful and quick support! 👍

Do you think, the library is ready for production usage? Even with addAndroidRuntimeDependency = true? (What does it do exactly?) Or should I stick to using it in debug builds only?

Regarding the minimal example of the project configuration: that will probably be difficult since I'm working on a huge eco-system. I can try to get permission for a screensharing session though, if you think it will help.

cee-dee avatar Oct 21 '24 07:10 cee-dee

Do you think, the library is ready for production usage? Even with addAndroidRuntimeDependency = true?

Yes, it's production ready.

What does it do exactly?

To recovery the stack trace classes have to contain auxiliary methods which are generated by Decorutinator Gradle plugin at build time. For some reason your project's classes are not transformed and don't contain that methods. That's why you saw UnknownKt instead of recovered frames.

Property addAndroidRuntimeDependency = true adds the dependency stacktrace-decoroutinator-generator-android which allows to generate that auxiliary methods at runtime.

Anamorphosee avatar Oct 21 '24 09:10 Anamorphosee

So the resulting build will be smaller when addAndroidRuntimeDependency=true is used, right? Do you generate the methods at build time to reduce app startup time, or what was the reason for the default?

cee-dee avatar Oct 21 '24 09:10 cee-dee

So the resulting build will be smaller when addAndroidRuntimeDependency=true is used, right?

The resulting APK is smaller when addAndroidRuntimeDependency=false(which is default).

Do you generate the methods at build time to reduce app startup time, or what was the reason for the default?

Partly. The main function which done at build time is replacing the class BaseContinuationImpl from Kotlin stdlib. And it works for your project. Without it you wouldn't see any effect at all. And that action is mandatory and can be done only at build time.

And the second part is to generate the auxiliary methods. And it can be done both at build time or at run time. addAndroidRuntimeDependency=true turn it on at run time.

Anamorphosee avatar Oct 21 '24 10:10 Anamorphosee

@cee-dee I have an hypothesis why your projects classes are not transformed at build time. Could you please add configuration below to your build.gradle.kts

stacktraceDecoroutinator {
    artifactTypes = setOf(
        "jar", "java-classes-directory", "zip", "aar", "android-classes-jar", "android-classes-directory"
    )
}

and delete addAndroidRuntimeDependency = true and check if Decoroutinator works properly

Anamorphosee avatar Oct 22 '24 23:10 Anamorphosee

Thanks for the update! Unfortunately, this doesn't work, it yields the "Unknown" callstack entries. The status is: DecoroutinatorStatus(successful=false, description=the stack trace contains unknown frames. Probably some local source is not transformed)

I've asked for permission to do a screensharing session with you, though -- in case you'd be interested.

cee-dee avatar Oct 23 '24 09:10 cee-dee