micronaut-projects / micronaut-core

Micronaut Application Framework
http://micronaut.io
Apache License 2.0
6.09k stars 1.07k forks source link

Graal app falsely reports of duplicate routes after running Graal Tracing Agent #2191

Open boonyasukd opened 5 years ago

boonyasukd commented 5 years ago

I created a micronaut app with an aim to convert it to native app. So far, the application works fine when running as a regular Java program. However, after running graalvm tracing agent to generate graalvm config files, the app strangely reports of duplicate routes, when there's only one route in the entire app.

Steps to Reproduce

  1. Create app via cli: mn create-app micronaut-two --lang=kotlin --features=graal-native-image
  2. Create a kotlin data class (in this case, I called it DepartureRow), then create a Controller:
    @Controller("/invoices")
    class InvoiceGenerator {
    @Post("/generate")
    fun generate(@Body departures: List<DepartureRow>) = "data size: ${departures.size}"
    }
  3. Run ./gradlew assemble
  4. Run the app with tracing agent enabled (replacing /PATH/TO/APP with your own path):
    java -agentlib:native-image-agent=config-output-dir=/PATH/TO/APP/micronaut-two/src/main/resources/META-INF/native-image -jar build/libs/micronaut-two-*-all.jar
  5. POST an empty JSON array to locahost:8080/invoices/generate, then close the app
  6. Run ./gradlew assemble again (to make /build folder pick up files generated by tracing agent)
  7. Run native-image --no-server -cp build/libs/micronaut-two-*-all.jar
  8. Run the resulting executable: ./micronaut-two, and POST empty JSON array to it again. If you're using httpie, you can do:
echo '[]' | http POST localhost:8080/invoices/generate --json

Expected Behaviour

The app should return a string: data size: 0

Actual Behaviour

The app returns HTTP 400:

{
    "_links": {
        "self": {
            "href": "/invoices/generate",
            "templated": false
        }
    },
    "message": "More than 1 route matched the incoming request. The following routes matched /invoices/generate: POST - /invoices/generate, POST - /invoices/generate"
}

Environment Information


Gradle 5.5

Build time: 2019-06-28 17:36:05 UTC Revision: 83820928f3ada1a3a1dbd9a6c0d47eb3f199378f

Kotlin: 1.3.31 Groovy: 2.5.4 Ant: Apache Ant(TM) version 1.9.14 compiled on March 12 2019 JVM: 1.8.0_222 (Oracle Corporation 25.222-b08-jvmci-19.2-b02) OS: Mac OS X 10.14.6 x86_64

- **Micronaut Version:** `1.2.3`
- **JDK Version:**

java -version openjdk version "1.8.0_222" OpenJDK Runtime Environment (build 1.8.0_222-20190711112007.graal.jdk8u-src-tar-gz-b08) OpenJDK 64-Bit GraalVM CE 19.2.0.1 (build 25.222-b08-jvmci-19.2-b02, mixed mode)



### Example Application

The app can be found [here](https://github.com/boonyasukd/buggy-micronaut-native). FYI, the project already has all kotlin classes and config files, so you can start from step 6 onward.
graemerocher commented 5 years ago

I am unconvinced this is a bug. I believe the use of the tracing agent is causing an additional service loader definition to be generated (which Micronaut already generates) which is then causing duplicate beans to be registered

graemerocher commented 5 years ago

For reference you should not need to run the tracing agent on a Micronaut application since Micronaut is reflection free. If you are having an issue with a third party library then use the tracing agent but generate the files into a separate directly and select only the output that relates to the library.

boonyasukd commented 5 years ago

@graemerocher Thanks for replying.

Since the tracing agent will always trace the whole app (not just third party jars), in the end the approach I went with was to run my app with tracing agent twice:

Then, after diffing out the differences between these 2 runs, I arrived at "streamlined" reflect-config.json and resource-config.json that Micronaut would work with.


I'm currently evaluating between frameworks that allow me to compile Java projects into native serverless apps. It just so happens that my Quarkus sample project works fine with tracing agent output, so I naively thought it would be the same for Micronaut. That was a mistake on my part.

I have no intention to convince you whether this is (or isn't) a bug since you know your framework best. But it'd be great if Micronaut docs can guide us on how to properly use the framework with third party libs when compiled with graalvm. Until all third party libs come equipped with their own native-image support, I believe there will be more users facing this same issue as well.

graemerocher commented 5 years ago

So I think the reason the issue is happening is that GraalVM automatically registers classes that are declared via service loader for reflection usage.

However the tracing agent then traces calls to Class.forName(..) for these same classes which results in a duplicate registrations of the classes Micronaut generates. I think a correct generation script with the tracing agent would have to remove any classes that are already declared in any META-INF/services file.

You could do that by extracting the zip generated to build/libs/*-all.jar and collecting all the class names from META-INF/services files and removing them from the generated reflection file prior to feeding it to Graal.

Alternatively one could maybe consider this a bug in the agent since it probably shouldn't be including classes declared in META-INF/services in the generated output and you may want to consider a reporting an issue to https://github.com/oracle/graal/issues/

graemerocher commented 5 years ago

I have reported https://github.com/oracle/graal/issues/1749 to resolve this issue.

doctorpangloss commented 4 years ago

I also experience this issue. It's a pretty common part of the workflow.