Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Publish discovered RoutesBuilders via CamelBeanBuildItem #358

Merged
merged 2 commits into from Nov 12, 2019

Conversation

ppalaga
Copy link
Contributor

@ppalaga ppalaga commented Oct 31, 2019

The main idea here is to provide a custom QuarkusInjector that first looks up the given type in the Arc container before falling back to direct instantiation. At the same time RoutesBuilders are not instantiated by us, we rather supply the RoutesBuilder class names to Camel main to instantiate them via our QuarkusInjector.

Let's discuss whether the above is a good idea.

I see the following possible risks:

  1. Slower instantiation and thus slower boot because all types are first looked up in the Arc container. I should perhaps try to measure the difference.
  2. The instantiation of the RoutesBuilders moves from STATIC_INIT to RUNTIME_INIT. Native image boot time may get worse due to this.

Both 1. and 2. could get mitigated for RoutesBuilders that do not contain any injection points by filtering out those and instantiating them as we do now, whereas only RoutesBuilders with injection points would be instantiated as sketched in this proposal. The question is whether the more complex implementation is worth the effort.

@ppalaga
Copy link
Contributor Author

ppalaga commented Oct 31, 2019

This should have been a draft PR. Sorry.

@lburgazzoli
Copy link
Contributor

lburgazzoli commented Oct 31, 2019

I'm wonder if it worth doing that as there's basic dependency injection in camel that works out of the box (example) and if people need to use MicroProfile / CDI injection then they could simply annotate the route with ApplicationScope or any similar annotation (we still need to fix the problem of having the same route discovered at runtime and at build time, but that's something we need to do in any case).

I would really like to keep instantiating routes in static init as much as possible.

@ppalaga
Copy link
Contributor Author

ppalaga commented Oct 31, 2019

if people need to use MicroProfile / CDI injection then they could simply annotate the route with ApplicationScope

Maybe I am missing something trivial, but adding @ApplicationScoped to a class extending RouteBuilder does not make @Inject in that same class work. The itest in this PR implements exactly that scenario and I was not able to make it work without the proposed fix. Or you mean something else by "annotate the route"?

still need to fix the problem of having the same route discovered at runtime and at build time

Good point, let me add a test for that.

@lburgazzoli
Copy link
Contributor

if people need to use MicroProfile / CDI injection then they could simply annotate the route with ApplicationScope

Maybe I am missing something trivial, but adding @ApplicationScoped to a class extending RouteBuilder does not make @Inject in that same class work. The itest in this PR implements exactly that scenario and I was not able to make it work without the proposed fix. Or you mean something else by "annotate the route"?

I think it does not work because camel discovers it on the classpath and load it on ts own so it bypass ArC.

still need to fix the problem of having the same route discovered at runtime and at build time

Good point, let me add a test for that.

@asf-ci
Copy link

asf-ci commented Oct 31, 2019

Refer to this link for build results (access rights to CI server needed):
https://builds.apache.org/job/camel-quarkus-pr/373/

Failed Tests: 94

camel-quarkus-pr/org.apache.camel.quarkus:camel-quarkus-examples-observability: 4

camel-quarkus-pr/org.apache.camel.quarkus:camel-quarkus-examples-rest-json: 4

camel-quarkus-pr/org.apache.camel.quarkus:camel-quarkus-integration-test-csv: 4

camel-quarkus-pr/org.apache.camel.quarkus:camel-quarkus-integration-test-infinispan: 1

camel-quarkus-pr/org.apache.camel.quarkus:camel-quarkus-integration-test-jackson: 2

camel-quarkus-pr/org.apache.camel.quarkus:camel-quarkus-integration-test-jdbc: 4

camel-quarkus-pr/org.apache.camel.quarkus:camel-quarkus-integration-test-mail: 2

camel-quarkus-pr/org.apache.camel.quarkus:camel-quarkus-integration-test-microprofile-metrics: 22

camel-quarkus-pr/org.apache.camel.quarkus:camel-quarkus-integration-test-netty-http: 4

camel-quarkus-pr/org.apache.camel.quarkus:camel-quarkus-integration-test-opentracing: 2

camel-quarkus-pr/org.apache.camel.quarkus:camel-quarkus-integration-test-platform-http: 30

camel-quarkus-pr/org.apache.camel.quarkus:camel-quarkus-integration-test-platform-http-engine: 6

camel-quarkus-pr/org.apache.camel.quarkus:camel-quarkus-integration-test-slack: 2

camel-quarkus-pr/org.apache.camel.quarkus:camel-quarkus-integration-test-tarfile: 2

camel-quarkus-pr/org.apache.camel.quarkus:camel-quarkus-integration-test-zipfile: 2

camel-quarkus-pr/org.apache.camel.quarkus:camel-quarkus-servlet-deployment: 3

@ppalaga
Copy link
Contributor Author

ppalaga commented Nov 8, 2019

@lburgazzoli I finally figured out why you expect the injection & co in an @AppScoped RoutesBuilder to work without the current fix: the RoutesCollector looks up the RoutesBuilders in Arc via RuntimeBeanRepository. I did not know that.

Actually the current proposal (mostly) eliminates the duplicate route initialization problem we discussed in the chat. There are still two route builders seen by CamelMain, but now they are the same instance (bc. both come from Arc and Arc correctly returns the same one anytime it is queried). The same instance won't get initialized and added to the Context twice thanks to RouteBuilder.checkInitialized().

Let me think how to proceed here.

@lburgazzoli
Copy link
Contributor

lburgazzoli commented Nov 8, 2019

I think we really need to get rid of the double discovery as having two identical builder is confusing and inefficient and it is a generic issue that has broader impacts than camel-quarkus

@ppalaga
Copy link
Contributor Author

ppalaga commented Nov 8, 2019

b14487b is the most elegant fix I was able to find. All RoutesBuilders are instantiated by Arc and queried via the Registry. CamelRoutesBuilderBuildItem removed altogether.

Not a WiP anymore.

@lburgazzoli
Copy link
Contributor

does this means that route builder classes are now instantiated at runtime only ?

@ppalaga
Copy link
Contributor Author

ppalaga commented Nov 8, 2019

does this means that route builder classes are now instantiated at runtime only ?

Yes, the constructors are called from main() not from a clinit.

@@ -232,7 +228,6 @@ CamelMainBuildItem main(
CamelContextBuildItem context,
CamelRoutesCollectorBuildItem routesCollector,
List<CamelMainListenerBuildItem> listeners,
List<CamelRoutesBuilderBuildItem> routesBuilders,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should be able to add routes that are instantiated at build time, i.e. if I use camel annotation for DI I do not need ArC at all thus I can optimize a bit my application.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would re-introduce the double initialization of the routes.

I was thinking of filtering by @ApplicationScoped that can be done well using Jandex: Builders having @ApplicationScoped would behave like in the current proposal and all others could go via CamelRoutesBuilderBuildItem. This would be more complex and thus potentially more buggy and harder to maintain. Maybe you have a better idea?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Camel has the ability to do class-path scanning to discover routes which can be activated by configuring which packages camel has to scan for RoutesBuilder classes (already supported by other platform such as Spring, CDI, Main) so we can mimic that behaviour at build time with a quarkus.camel.* option (note that this can activated also right now through camel main properties but class path scanning would happen at runtime).

By doing that, routes builders can be added to the context by:

  • producing a CamelBeanBuildItem
  • using a MainListener
  • using an injected CamelContext
  • using a CDI annotation such as @ApplicationScoped
  • listing the packages to scan for auto discovery (as far as I remember discovered routes have lower precedence than those available from the container thus discarded if the same route class is also provided by the container)

An additional option that has been discussed would be to leverage camel's annotation @BindToRegistry which would act like a cross platform lite DI.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I agree. You perhaps meant CamelRoutesBuilderBuildItem rather than CamelBeanBuildItem?

The ultimate aim of this PR is to make the @Inject, @ConfigProperty, etc. work in RoutesBuilders (see the tests in this PR that currently fail). I agree that the problem of double Route initialization is actually the root cause.

My previous proposal to solve both problems by delegating the creation of all RouteBuilders to Arc was rejected for boot performance reasons which I found quite legitimate.

I proposed an alternative approach where Arc beans are thrown away from the list provided via CamelRoutesBuilderBuildItem, but I see no feedback for that.

In between, I looked how to implement that reliably, and found that the list of Arc beans could perhaps be pulled from Arc itself like this: ppalaga@cdbfd19#diff-ec6205752018e84a80fcd9eaa92af189R203-R209 (once they change the visibility of some methods, I asked the Arc guys about that). What do you think about the linked solution?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No CamelBeanBuildItem is - IMHO - the right bean to use as every route bound to the registry is then automatically discovered so CamelRoutesBuilderBuildItem is superfluous but that is a minor issue.

The problem about using ArC in STATIC_INIT is that it may not be aware of the "runtime beans" at that point in time, at least in my experience.

You may think to override the route discovery class and apply the filter there.

@lburgazzoli
Copy link
Contributor

This is IMHO bad as we are de optimizing the app

@ppalaga
Copy link
Contributor Author

ppalaga commented Nov 11, 2019

About ac51838:

All open questions are solved from my PoV

@ppalaga
Copy link
Contributor Author

ppalaga commented Nov 11, 2019

Hm... looking at the test failures, it looks like there is one more problem: The registry sees only CDI beans instantiated eagerly - e.g. if they have void onStart(@Observes StartupEvent event) {}. Let me check if that can be fixed somehow.

@ppalaga
Copy link
Contributor Author

ppalaga commented Nov 11, 2019

I solved the mentioned problem by marking all RouteBuilder classes as unremovable via UnremovableBeanBuildItem.


/**
* Holds the {@link RoutesBuilder} {@link RuntimeValue}.
* A {@link MultiBuildItem} holding class names of all {@link RoutesBuilder} implementations found via Jandex.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not very sure if implementations found via Jandex really make sense as any extension developer can create it programmatically.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, let me reword it.

//
// Register routes as reflection aware as camel-main main use reflection
// to bind beans to the registry
//
CamelSupport.getRouteBuilderClasses(view).forEach(name -> {
reflectiveClass.produce(new ReflectiveClassBuildItem(true, false, name));
camelRoutesBuilders.stream().forEach(dotName -> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stream() is not needed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

camelRoutesBuilders.stream()
.map(CamelRoutesBuilderClassBuildItem::getDotName)
.filter(dotName -> !arcBeanClasses.contains(dotName))
.forEach(dotName -> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe map + collect would be more idiomatic

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that would be possible now. The previous revisions did not allow that. Let me refactor.

@ppalaga
Copy link
Contributor Author

ppalaga commented Nov 11, 2019

All requests by @lburgazzoli addressed in 22fed9b

@lburgazzoli
Copy link
Contributor

ok to test

@asf-ci
Copy link

asf-ci commented Nov 11, 2019

Refer to this link for build results (access rights to CI server needed):
https://builds.apache.org/job/camel-quarkus-pr/388/

Build result: FAILURE

[...truncated 1.83 MB...] at sun.reflect.GeneratedMethodAccessor255.invoke (Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke (DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke (Method.java:498) at io.quarkus.deployment.ExtensionLoader$1.execute (ExtensionLoader.java:941) at io.quarkus.builder.BuildContext.run (BuildContext.java:415) at org.jboss.threads.ContextClassLoaderSavingRunnable.run (ContextClassLoaderSavingRunnable.java:35) at org.jboss.threads.EnhancedQueueExecutor.safeRun (EnhancedQueueExecutor.java:2011) at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask (EnhancedQueueExecutor.java:1535) at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run (EnhancedQueueExecutor.java:1426) at java.lang.Thread.run (Thread.java:748) at org.jboss.threads.JBossThread.run (JBossThread.java:479)[ERROR] [ERROR] Re-run Maven using the -X switch to enable full debug logging.[ERROR] [ERROR] For more information about the errors and possible solutions, please read the following articles:[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionException[ERROR] [ERROR] After correcting the problems, you can resume the build with the command[ERROR] mvn -rf :camel-quarkus-integration-test-corechannel stoppedAdding one-line test results to commit status...Setting status of 22fed9b to FAILURE with url https://builds.apache.org/job/camel-quarkus-pr/388/ and message: 'FAILURE 113 tests run, 3 skipped, 0 failed.'Using context: default

@lburgazzoli
Copy link
Contributor

[INFO] [io.quarkus.deployment.pkg.steps.NativeImageBuildStep] docker run -v /home/jenkins/jenkins-slave/workspace/camel-quarkus-pr/integration-tests/core/test/target/camel-quarkus-integration-test-core-0.3.2-SNAPSHOT-native-image-source-jar:/project:z --user 910:910 --rm quay.io/quarkus/ubi-quarkus-native-image:19.2.1 -J-Dsun.nio.ch.maxUpdateArraySize=100 -J-Djava.util.logging.manager=org.jboss.logmanager.LogManager -J-Dvertx.logger-delegate-factory-class-name=io.quarkus.vertx.core.runtime.VertxLogDelegateFactory -J-Dvertx.disableDnsResolver=true -J-Dio.netty.leakDetection.level=DISABLED -J-Dio.netty.allocator.maxOrder=1 --initialize-at-build-time= -H:InitialCollectionPolicy=com.oracle.svm.core.genscavenge.CollectionPolicy$BySpaceAndTime -jar camel-quarkus-integration-test-core-0.3.2-SNAPSHOT-runner.jar -J-Djava.util.concurrent.ForkJoinPool.common.parallelism=1 -H:FallbackThreshold=0 -H:+ReportExceptionStackTraces -H:-AddAllCharsets -H:EnableURLProtocols=http -H:-JNI --no-server -H:-UseServiceLoaderFeature -H:+StackTrace camel-quarkus-integration-test-core-0.3.2-SNAPSHOT-runner
[camel-quarkus-integration-test-core-0.3.2-SNAPSHOT-runner:40]    classlist:   9,590.29 ms
[camel-quarkus-integration-test-core-0.3.2-SNAPSHOT-runner:40]        (cap):   1,994.93 ms
[camel-quarkus-integration-test-core-0.3.2-SNAPSHOT-runner:40]        setup:   4,862.12 ms
21:20:20,130 INFO  [org.jbo.threads] JBoss Threads version 3.0.0.Final
[camel-quarkus-integration-test-core-0.3.2-SNAPSHOT-runner:40]     analysis:  12,192.52 ms
Error: Class initialization of io.quarkus.runner.ApplicationImpl failed. Use the option --initialize-at-run-time=io.quarkus.runner.ApplicationImpl to explicitly request delayed initialization of this class.
Detailed message:

com.oracle.svm.core.util.UserError$UserException: Class initialization of io.quarkus.runner.ApplicationImpl failed. Use the option --initialize-at-run-time=io.quarkus.runner.ApplicationImpl to explicitly request delayed initialization of this class.
Detailed message:

	at com.oracle.svm.core.util.UserError.abort(UserError.java:75)
	at com.oracle.svm.hosted.FallbackFeature.reportAsFallback(FallbackFeature.java:223)
	at com.oracle.svm.hosted.NativeImageGenerator.runPointsToAnalysis(NativeImageGenerator.java:737)
	at com.oracle.svm.hosted.NativeImageGenerator.doRun(NativeImageGenerator.java:526)
	at com.oracle.svm.hosted.NativeImageGenerator.lambda$run$0(NativeImageGenerator.java:444)
	at java.util.concurrent.ForkJoinTask$AdaptedRunnableAction.exec(ForkJoinTask.java:1386)
	at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
	at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
	at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
	at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
Caused by: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException: Class initialization of io.quarkus.runner.ApplicationImpl failed. Use the option --initialize-at-run-time=io.quarkus.runner.ApplicationImpl to explicitly request delayed initialization of this class.
Detailed message:

	at com.oracle.graal.pointsto.constraints.UnsupportedFeatures.report(UnsupportedFeatures.java:130)
	at com.oracle.graal.pointsto.BigBang.finish(BigBang.java:565)
	at com.oracle.svm.hosted.NativeImageGenerator.runPointsToAnalysis(NativeImageGenerator.java:688)
	... 7 more
Caused by: java.lang.ExceptionInInitializerError
	at sun.misc.Unsafe.ensureClassInitialized(Native Method)
	at com.oracle.svm.hosted.classinitialization.ConfigurableClassInitialization.ensureClassInitialized(ConfigurableClassInitialization.java:153)
	at com.oracle.svm.hosted.classinitialization.ConfigurableClassInitialization.computeInitKindAndMaybeInitializeClass(ConfigurableClassInitialization.java:557)
	at com.oracle.svm.hosted.classinitialization.ConfigurableClassInitialization.computeInitKindAndMaybeInitializeClass(ConfigurableClassInitialization.java:116)
	at com.oracle.svm.hosted.classinitialization.ConfigurableClassInitialization.maybeInitializeHosted(ConfigurableClassInitialization.java:144)
	at com.oracle.svm.hosted.SVMHost.registerType(SVMHost.java:190)
	at com.oracle.graal.pointsto.meta.AnalysisUniverse.createType(AnalysisUniverse.java:262)
	at com.oracle.graal.pointsto.meta.AnalysisUniverse.lookupAllowUnresolved(AnalysisUniverse.java:203)
	at com.oracle.graal.pointsto.infrastructure.WrappedConstantPool.lookupType(WrappedConstantPool.java:155)
	at org.graalvm.compiler.java.BytecodeParser.lookupType(BytecodeParser.java:4080)
	at org.graalvm.compiler.java.BytecodeParser.genNewInstance(BytecodeParser.java:4330)
	at org.graalvm.compiler.java.BytecodeParser.processBytecode(BytecodeParser.java:5128)
	at org.graalvm.compiler.java.BytecodeParser.iterateBytecodesForBlock(BytecodeParser.java:3267)
	at org.graalvm.compiler.java.BytecodeParser.processBlock(BytecodeParser.java:3074)
	at org.graalvm.compiler.java.BytecodeParser.build(BytecodeParser.java:976)
	at org.graalvm.compiler.java.BytecodeParser.buildRootMethod(BytecodeParser.java:870)
	at org.graalvm.compiler.java.GraphBuilderPhase$Instance.run(GraphBuilderPhase.java:84)
	at org.graalvm.compiler.phases.Phase.run(Phase.java:49)
	at org.graalvm.compiler.phases.BasePhase.apply(BasePhase.java:197)
	at org.graalvm.compiler.phases.Phase.apply(Phase.java:42)
	at org.graalvm.compiler.phases.Phase.apply(Phase.java:38)
	at com.oracle.graal.pointsto.flow.MethodTypeFlowBuilder.parse(MethodTypeFlowBuilder.java:221)
	at com.oracle.graal.pointsto.flow.MethodTypeFlowBuilder.apply(MethodTypeFlowBuilder.java:340)
	at com.oracle.graal.pointsto.flow.MethodTypeFlow.doParse(MethodTypeFlow.java:310)
	at com.oracle.graal.pointsto.flow.MethodTypeFlow.ensureParsed(MethodTypeFlow.java:300)
	at com.oracle.graal.pointsto.flow.MethodTypeFlow.addContext(MethodTypeFlow.java:107)
	at com.oracle.graal.pointsto.flow.StaticInvokeTypeFlow.update(InvokeTypeFlow.java:346)
	at com.oracle.graal.pointsto.BigBang$2.run(BigBang.java:510)
	at com.oracle.graal.pointsto.util.CompletionExecutor.lambda$execute$0(CompletionExecutor.java:171)
	at java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(ForkJoinTask.java:1402)
	... 4 more
Caused by: java.lang.RuntimeException: Failed to start quarkus
	at io.quarkus.runner.ApplicationImpl.<clinit>(ApplicationImpl.zig:366)
	... 34 more
Caused by: java.lang.ClassNotFoundException: io.quarkus.deployment.recording.BytecodeRecorderImpl$$ClassProxy152
	at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:418)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
	at java.lang.Class.forName0(Native Method)
	at java.lang.Class.forName(Class.java:348)
	at io.quarkus.deployment.steps.Core$registry54.deploy_0(Core$registry54.zig:477)
	at io.quarkus.deployment.steps.Core$registry54.deploy(Core$registry54.zig:36)
	at io.quarkus.runner.ApplicationImpl.<clinit>(ApplicationImpl.zig:320)
	... 34 more
Error: Image build request failed with exit status 

@ppalaga ppalaga changed the title Fix #136 @ConfigProperty and @Inject do not work in RouteBuilders Publish discovered RoutesBuilders via CamelBeanBuildItem Nov 12, 2019
@ppalaga
Copy link
Contributor Author

ppalaga commented Nov 12, 2019

About 53252e8: Fixed the first two commits (should pass in the native mode now) and removed the third one, which requires more work. Will send the third one separately once I make it work.

@lburgazzoli
Copy link
Contributor

ok to test

@asf-ci
Copy link

asf-ci commented Nov 12, 2019

Refer to this link for build results (access rights to CI server needed):
https://builds.apache.org/job/camel-quarkus-pr/394/

@lburgazzoli lburgazzoli merged commit a940fa3 into apache:master Nov 12, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants