I'm using OptaPlanner 9.37.0.Final
with quarkus 3.1.3.Final
and Kotlin 1.8.21
to solve a VRP.
When I run quarkusDev
, my current setup runs just fine, but when I run a @QuarkusTest
, I get the following ClassCastException
, hinting that something with class loading might be broken.
java.lang.ClassCastException: class com.cargonexx.vehiclerouting.domain.LoadJob$OptaPlanner$MemberAccessor$Field$arrivalTime cannot be cast to class org.optaplanner.core.impl.domain.common.accessor.MemberAccessor (com.cargonexx.vehiclerouting.domain.LoadJob$OptaPlanner$MemberAccessor$Field$arrivalTime is in unnamed module of loader 'OptaPlanner Gizmo SolutionCloner ClassLoader' @611d16c9; org.optaplanner.core.impl.domain.common.accessor.MemberAccessor is in unnamed module of loader io.quarkus.bootstrap.classloading.QuarkusClassLoader @319988b0)
at org.optaplanner.core.impl.domain.common.accessor.gizmo.GizmoMemberAccessorImplementor.createInstance(GizmoMemberAccessorImplementor.java:118)
at org.optaplanner.core.impl.domain.common.accessor.gizmo.GizmoMemberAccessorImplementor.createAccessorFor(GizmoMemberAccessorImplementor.java:112)
at org.optaplanner.core.impl.domain.common.accessor.gizmo.GizmoMemberAccessorFactory.buildGizmoMemberAccessor(GizmoMemberAccessorFactory.java:40)
at org.optaplanner.core.impl.domain.common.accessor.MemberAccessorFactory.buildMemberAccessor(MemberAccessorFactory.java:38)
at org.optaplanner.core.impl.domain.common.accessor.MemberAccessorFactory.lambda$buildAndCacheMemberAccessor$0(MemberAccessorFactory.java:125)
at java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1708)
at org.optaplanner.core.impl.domain.common.accessor.MemberAccessorFactory.buildAndCacheMemberAccessor(MemberAccessorFactory.java:124)
at org.optaplanner.core.impl.domain.entity.descriptor.EntityDescriptor.processPlanningVariableAnnotation(EntityDescriptor.java:239)
at org.optaplanner.core.impl.domain.entity.descriptor.EntityDescriptor.processAnnotations(EntityDescriptor.java:154)
at org.optaplanner.core.impl.domain.solution.descriptor.SolutionDescriptor.buildSolutionDescriptor(SolutionDescriptor.java:106)
at org.optaplanner.core.impl.solver.DefaultSolverFactory.buildSolutionDescriptor(DefaultSolverFactory.java:147)
at org.optaplanner.core.impl.solver.DefaultSolverFactory.<init>(DefaultSolverFactory.java:69)
at org.optaplanner.core.api.solver.SolverFactory.create(SolverFactory.java:106)
at org.optaplanner.quarkus.bean.DefaultOptaPlannerBeanProvider.solverFactory(DefaultOptaPlannerBeanProvider.java:46)
at org.optaplanner.quarkus.bean.DefaultOptaPlannerBeanProvider_ProducerMethod_solverFactory_3b1fa4ff0a3de7781ba3e1239701086bba97ef14_Bean.doCreate(Unknown Source)
at org.optaplanner.quarkus.bean.DefaultOptaPlannerBeanProvider_ProducerMethod_solverFactory_3b1fa4ff0a3de7781ba3e1239701086bba97ef14_Bean.create(Unknown Source)
at org.optaplanner.quarkus.bean.DefaultOptaPlannerBeanProvider_ProducerMethod_solverFactory_3b1fa4ff0a3de7781ba3e1239701086bba97ef14_Bean.get(Unknown Source)
at org.optaplanner.quarkus.bean.DefaultOptaPlannerBeanProvider_ProducerMethod_solverFactory_3b1fa4ff0a3de7781ba3e1239701086bba97ef14_Bean.get(Unknown Source)
at org.optaplanner.quarkus.bean.DefaultOptaPlannerBeanProvider_ProducerMethod_solverManager_d6636211e93ca3985f0495d972987bdadf803f37_Bean.doCreate(Unknown Source)
at org.optaplanner.quarkus.bean.DefaultOptaPlannerBeanProvider_ProducerMethod_solverManager_d6636211e93ca3985f0495d972987bdadf803f37_Bean.create(Unknown Source)
at org.optaplanner.quarkus.bean.DefaultOptaPlannerBeanProvider_ProducerMethod_solverManager_d6636211e93ca3985f0495d972987bdadf803f37_Bean.get(Unknown Source)
at org.optaplanner.quarkus.bean.DefaultOptaPlannerBeanProvider_ProducerMethod_solverManager_d6636211e93ca3985f0495d972987bdadf803f37_Bean.get(Unknown Source)
at com.cargonexx.vehiclerouting.rest.SolverResource_Bean.doCreate(Unknown Source)
at com.cargonexx.vehiclerouting.rest.SolverResource_Bean.create(Unknown Source)
at com.cargonexx.vehiclerouting.rest.SolverResource_Bean.create(Unknown Source)
at io.quarkus.arc.impl.AbstractSharedContext.createInstanceHandle(AbstractSharedContext.java:113)
at io.quarkus.arc.impl.AbstractSharedContext$1.get(AbstractSharedContext.java:37)
at io.quarkus.arc.impl.AbstractSharedContext$1.get(AbstractSharedContext.java:34)
at io.quarkus.arc.impl.LazyValue.get(LazyValue.java:26)
at io.quarkus.arc.impl.ComputingCache.computeIfAbsent(ComputingCache.java:69)
at io.quarkus.arc.impl.AbstractSharedContext.get(AbstractSharedContext.java:34)
at com.cargonexx.vehiclerouting.rest.SolverResource_Bean.get(Unknown Source)
at com.cargonexx.vehiclerouting.rest.SolverResource_Bean.get(Unknown Source)
at io.quarkus.arc.impl.ArcContainerImpl.beanInstanceHandle(ArcContainerImpl.java:499)
at io.quarkus.arc.impl.ArcContainerImpl.beanInstanceHandle(ArcContainerImpl.java:479)
at io.quarkus.arc.impl.ArcContainerImpl.beanInstanceHandle(ArcContainerImpl.java:512)
at io.quarkus.arc.impl.ArcContainerImpl$2.get(ArcContainerImpl.java:287)
at io.quarkus.arc.impl.ArcContainerImpl$2.get(ArcContainerImpl.java:284)
at io.quarkus.arc.runtime.BeanContainerImpl$1.create(BeanContainerImpl.java:46)
at io.quarkus.resteasy.reactive.common.runtime.ArcBeanFactory.createInstance(ArcBeanFactory.java:27)
at org.jboss.resteasy.reactive.server.handlers.InstanceHandler.handle(InstanceHandler.java:26)
at io.quarkus.resteasy.reactive.server.runtime.QuarkusResteasyReactiveRequestContext.invokeHandler(QuarkusResteasyReactiveRequestContext.java:139)
at org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext.run(AbstractResteasyReactiveContext.java:145)
at io.quarkus.vertx.core.runtime.VertxCoreRecorder$14.runWith(VertxCoreRecorder.java:576)
at org.jboss.threads.EnhancedQueueExecutor$Task.run(EnhancedQueueExecutor.java:2513)
at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1538)
at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:833)
I have a PlanningEntity
along the lines of:
@PlanningEntity
class LoadJob {
@PlanningId lateinit var id: String
@InverseRelationShadowVariable(sourceVariableName = "tour") //
var vehicle: Vehicle? = null
@PreviousElementShadowVariable(sourceVariableName = "tour") //
var previousLoadJob: LoadJob? = null
@NextElementShadowVariable(sourceVariableName = "tour") //
var nextLoadJob: LoadJob? = null
@JvmField
@ShadowVariable(
variableListenerClass = ArrivalTimeUpdateListener::class,
sourceVariableName = "vehicle"
)
@ShadowVariable(
variableListenerClass = ArrivalTimeUpdateListener::class,
sourceVariableName = "previousLoadJob"
)
var arrivalTime: LocalDateTime? = null
}
which is used in the PlanningListVariable
in the Vehicle
class:
@PlanningEntity
class Vehicle {
@PlanningId lateinit var planningId: String
@PlanningListVariable lateinit var tour: MutableList<LoadJob>
}
Both are part of the PlanningSolution
@PlanningSolution
class TourPlan {
lateinit var id: String
@PlanningEntityCollectionProperty //
lateinit var vehicles: List<Vehicle>
@ValueRangeProvider
@ProblemFactCollectionProperty //
lateinit var loadJobs: List<LoadJob>
@PlanningScore //
var score: HardMediumSoftLongScore? = null
@ConstraintConfigurationProvider
var constraintConfiguration: TourPlanConstraintConfiguration = TourPlanConstraintConfiguration()
constructor() // required for optaplanner
}
Has anybody else run into an issue like that? Are there any known workarounds or fixes for this issue?
I have been working for almost a month with this setup now, but in the long run it might not be feasible to just don't run any QuarkusTest
s as they are broken with the current setup.
Notes
- When using
REFLECTION
instead ofGIZMO
for optaplanner domain access, I don't have this issue. However, since reflection is slower, as the optaplanner documentation also mentions, I'd like to be able to useGIZMO
for the performance gain and not have to use diverging configurations for tests and regular builds - I'm aware of the fact that quarkus loads classes differently during tests. Unfortunately, I haven't had the time yet to look into quarkus class loading in depth (or at least further than the documentation goes)
- I have stumbled upon https://github.com/quarkusio/quarkus/issues/34099 and https://github.com/quarkusio/quarkus/pull/34681 which may be related to the root cause of this exception, but that's more a guess than anything I could say for sure.
- I haven't tried if the problem also exists with timefold instead of optaplanner yet, but I'd be willing to invest the time and try to extract a reproducer with timefold in case that's of interest to the timefold team