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
OptaPlanner extension #7049
OptaPlanner extension #7049
Conversation
} | ||
if (annotationInstances.isEmpty()) { | ||
throw new IllegalStateException("No classes (" + convertAnnotationInstancesToString(annotationInstances) | ||
+ ") found with a @" + PlanningSolution.class.getSimpleName() + " annotation."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We talked about removing this fail-fast in the previous PR, to follow hibernate-extension's approach, so I 'll do that in a next PR if that's ok, because of the open debate below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Open question, what is the behavior we want:
A) When there is no @PlanningSolution class, fail fast (current implementation, differs from hibernate extension)
B) When there is no @PlanningSolution class, only fail fast if there are any quarkus.optaplanner.*
application.properties, otherwise do nothing
C) When there is no @PlanningSolution class, even if there are any quarkus.optaplanner.*
application.properties, always do nothing quietly
Does de hibernate extension do B) or C)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I presume A) is a PITA when doing demo's where you grab something from code.quarkus.io and it doesn't "just run"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah. I would avoid A). I would do C with a warning instead of staying quiet if some configuration properties are defined.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a real review but a reminder that we need this to be properly squashed.
I think you can already squash most if not all of the commits prior to review.
This comment has been minimized.
This comment has been minimized.
One thing I see is that the module hasen't been added to |
@geoand In stages.yml, can I just add it under "kogito" on line 380? Or should it have it's own template? How do I verify that my changes didn't break anything? |
Yeah, just add it under kogito and it should be run by CI automatically :) You'll be able to verify from the name of the job - it will include your module |
@geoand By the way, I got the entire thing running in GraalVM without doing special tricks in the example for OptaPlanner. No custom solution cloner, etc :) It truly just works on GraalVM. |
Excellent! |
Thanks for the hard work @ge0ffrey! We'll hopefully be able to review some time next week. |
Once this is merged, we'll work on the quickstart example that we'll build from scratch in a video about AI on quarkus. And we'll work on the guide of course :) |
solverConfig = new SolverConfig(classLoader); | ||
} | ||
// The deployment classLoader must not escape to runtime | ||
solverConfig.setClassLoader(null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just out of curiosity, what CL will be used at runtime as a result of this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most of the time, it's not used, except if a scoreDRL
is active.
If it's null
in that case, ScoreDirectorFactoryConfig
does this at runtime, during SolverFactory.buildSolver()
:
ClassLoader actualClassLoader = (classLoader != null) ? classLoader : getClass().getClassLoader();
Once we start doing more work during the static init, after we internalize the SolverConfig in the SolverFactory (currently impossible due to deprecated methods that will dissappear in 8.0), I 'll have to come back to this, as that code might become a problem then.
Good question indeed. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What am I sort of worried about is the dev-mode. There are certain situations where getClass().getClassLoader()
might not give you what you expect.
Perhaps also adding QuarkusDevModeTest
is a good idea to make sure things work properly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I 've been trying to add in QuarkusDevModeTest, but I reluctant to add a test that doesn't work in IntelliJ (it hurts productivity). In IntelliJ I get the "Undeclared build item class io.quarkus.deployment.builditem.ApplicationClassNameBuildItem" exception, but on the command line, I don't.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IntelliJ can mess up the classpath when running tests inside the Quarkus repo. I don't think it's something we need to worry about
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be possible to change that logic to use the TCCL if null?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The dev mode test should prove that things are OK with this change.
To be 100% Quarkus compliant, upstream here should provide a flag to use the TCCL (of the time of execution) when the classloader is null, but I don't think we need it now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using ThreadContextClassLoader is what it does by default if we don't set it to null.
When I remove the solverConfig.setClassLoader(null); line, I get
Caused by: java.lang.RuntimeException: Unable to serialize objects of type class io.quarkus.bootstrap.classloading.QuarkusClassLoader to bytecode as it has no default constructor
at io.quarkus.deployment.recording.BytecodeRecorderImpl$53.createValue(BytecodeRecorderImpl.java:1273)
...planner/deployment/src/main/java/io/quarkus/optaplanner/deployment/OptaPlannerProcessor.java
Outdated
Show resolved
Hide resolved
<dependency> | ||
<groupId>io.quarkus</groupId> | ||
<artifactId>quarkus-jackson</artifactId> | ||
<optional>true</optional> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, have you tested to make sure this behaves the way you expect it to? And are you sure that OptaPlannerObjectMapperCustomizer
is never loaded it the user doesn't add quarkus-jackson
.
@gsmet WDYT about this pattern? I know it's not the way we have been doing things so far, but I do remember @ge0ffrey had a UX issue in mind when doing this, but I don't remember what it was :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll check if that customizer is loaded or not. It shouldn't be, but I indeed need to verify.
I am doing it this way, because:
- Having an extra extension quarkus-optaplanner-jackson-extension (and jaxb, jpa, etc), means users must explicitly add it. That's another hoop to jump through. If they forget to do that, the json marshalling of Score etc defaults to reflection way, which is hard to detect for new users.
- optaplanner-spring-boot-starter does it this way, it's automagic configuration. No hoop to jump through.
If you guys insist, we can switch to the normal pattern - or leave out the jackson integration entirely for now into a separate PR so the rest can be merged already.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, they would have to add the optaplanner Jackson dependency anyway, no? That's not very different from adding an extension.
I wouldn't make it a blocker to merge this PR if we are sure everything works when Jackson is not there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed by extracting new quarkus-optaplanner-jackson module
...aplanner/runtime/src/main/java/io/quarkus/optaplanner/OptaPlannerObjectMapperCustomizer.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added some comments inline. I don't think there are a lot of blockers and we could probably merge a first step and then iterate.
The CL question and the Jackson exception should probably been sorted out before merging though.
solverConfig = new SolverConfig(classLoader); | ||
} | ||
// The deployment classLoader must not escape to runtime | ||
solverConfig.setClassLoader(null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be possible to change that logic to use the TCCL if null?
} | ||
if (annotationInstances.isEmpty()) { | ||
throw new IllegalStateException("No classes (" + convertAnnotationInstancesToString(annotationInstances) | ||
+ ") found with a @" + PlanningSolution.class.getSimpleName() + " annotation."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah. I would avoid A). I would do C with a warning instead of staying quiet if some configuration properties are defined.
// Fail fast during build to avoid a certain runtime failure | ||
throw new IllegalStateException( | ||
"When using both Jackson and OptaPlanner," | ||
+ " add a dependency on org.optaplanner:optaplanner-persistence-jackson too.", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What annoys me a bit here is that if you use Jackson for your REST services and OptaPlanner without Jackson persistence, you end up with this exception. Or did I misunderstand?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea - the idea is that it would stick out like a soar thumb, fail-fast basically. While if you forgot to add quarkus-optaplanner-jackson, there is no fail fast: it's just hard to notice that score instances aren't being jacksoned correctly.
Anyway, not worth the hassle - I am creating quarkus-optaplanner-jackson to align with the quarkus ecosystem spirit :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
|
||
// Not named OptaPlannerConfig because classes ending with just "Config" collide with OptaPlanner's API | ||
@ConfigRoot(name = "optaplanner") | ||
public class OptaPlannerQuarkusConfig { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Call it OptaPlannerBuildTimeConfig
and get rid of the comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, good point.
* Other options include a number or formula based on the available processor count. | ||
*/ | ||
@ConfigItem | ||
Optional<String> parallelSolverCount; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That looks like a runtime property to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It only affects SolverManager during build time - those get injected, but we don't need to read this property at runtime.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Discussed with Georgios. He agrees, these are build time properties (they get translated into optaplanner configuration during the build time, they aren't used at runtime). Renamed class to end with BuildTimeConfig instead of QuarkusConfig to signal that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, they might be build time for your code but I think it's typically values that you will want to change at runtime because it might depend on the hardware on which you deploy it.
I don't have a problem merging that as is for now but I think it's probably something we should think about for a later version (but it might be totally impractical, I don't know OptaPlanner at all).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed - or users wanting to override them with -D system properties?
<dependency> | ||
<groupId>io.quarkus</groupId> | ||
<artifactId>quarkus-jackson</artifactId> | ||
<optional>true</optional> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, they would have to add the optaplanner Jackson dependency anyway, no? That's not very different from adding an extension.
I wouldn't make it a blocker to merge this PR if we are sure everything works when Jackson is not there.
extensions/optaplanner/runtime/src/main/resources/META-INF/quarkus-extension.yaml
Outdated
Show resolved
Hide resolved
...ts/optaplanner/src/main/java/io/quarkus/it/optaplanner/domain/ITestdataPlanningSolution.java
Outdated
Show resolved
Hide resolved
...nner/src/main/java/io/quarkus/it/optaplanner/solver/ITestdataPlanningConstraintProvider.java
Outdated
Show resolved
Hide resolved
@gsmet I've separated the jackson stuff into a new module called quarkus-optaplanner-jackson. (one day we'll also add quarkus-optaplanner-jaxb) |
12d131b
to
1c8edb3
Compare
I think all review comments are handled (many adjustments done, accepted all majors and few minors, rejected a few minors), except for the fail-fast "use plan C" issue, which is for a later PR. Let me know if there's anything left blocking the merge on this PR :) |
Can you please squash your commits into a single one? Also it seems you need to rebase since there are conflicts from recent changes to master |
06a20a2
to
a9d2fdb
Compare
The CI logs from the latest run are here. The issue appears to have anything to do with this PR:
So I'll restart CI |
CI error is:
Seems like a problem with the PR |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Raaah, forgot to validate my review.
extensions/optaplanner-jackson/runtime/src/main/resources/quarkus-extension.yaml
Show resolved
Hide resolved
...planner/deployment/src/main/java/io/quarkus/optaplanner/deployment/OptaPlannerProcessor.java
Outdated
Show resolved
Hide resolved
test.modifyResourceFile("solverConfig.xml", s -> s.replace("<secondsSpentLimit>2</secondsSpentLimit>", | ||
"<secondsSpentLimit>9</secondsSpentLimit>")); | ||
resp = RestAssured.get("/solver-config/seconds-spent-limit").asString(); | ||
Assertions.assertEquals("secondsSpentLimit=9", resp); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm curious how this works without a LiveReloadBuildItem
? I think we should probably have one to restart all the app in case of a change, similar to what we do elsewhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A HotDeploymentWatchedFileBuildItem
has been added to the PR that enables the config file for reload.
extensions/optaplanner/runtime/src/main/resources/META-INF/quarkus-extension.yaml
Show resolved
Hide resolved
@geoand yes it is, thanks for your message because, apparently, I forgot to post my last review from yesterday. The name of the module is incorrect in the pipeline file. |
@ge0ffrey I think we're really not far from having this in a mergeable state. Let's try to get this in by Friday! |
a9d2fdb
to
48f23da
Compare
@gsmet Thanks for the review. All comments applied except the LiveReloadBuildItem one, because we use HotDeploymentWatchedFileBuildItem (see Georgios's comment) and we have a hot deploy test that works. Ready for another review. Let me know if there's anything else that needs to change. |
48f23da
to
c3c0e69
Compare
Merged, thanks a lot for this effort. Can you work on preparing the guide and the quickstart? Thanks! |
Coming soon :) |
Hi @lordofthejars there is a PR here #7579 to add the guide. |
Works on GraalVM with -Dnative (see integration test).