Search code examples
javaoptaplanner

Optaplanner ConcurrentModificationException when modifying Solution from SolverEventListener


I'm solving a scheduling problem using OptaPlanner solver integrated into a JavaFX GUI that updates on each improvement. Since connecting it to the GUI, this exception frequently happens after the construction heuristic finishes.

Exception in thread "Thread-6" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
    at java.util.ArrayList$Itr.next(ArrayList.java:851)
    at org.optaplanner.core.impl.domain.solution.cloner.FieldAccessingSolutionCloner$FieldAccessingSolutionClonerRun.cloneCollection(FieldAccessingSolutionCloner.java:297)
    at org.optaplanner.core.impl.domain.solution.cloner.FieldAccessingSolutionCloner$FieldAccessingSolutionClonerRun.process(FieldAccessingSolutionCloner.java:280)
    at org.optaplanner.core.impl.domain.solution.cloner.FieldAccessingSolutionCloner$FieldAccessingSolutionClonerRun.processQueue(FieldAccessingSolutionCloner.java:273)
    at org.optaplanner.core.impl.domain.solution.cloner.FieldAccessingSolutionCloner$FieldAccessingSolutionClonerRun.cloneSolution(FieldAccessingSolutionCloner.java:206)
    at org.optaplanner.core.impl.domain.solution.cloner.FieldAccessingSolutionCloner.cloneSolution(FieldAccessingSolutionCloner.java:72)
    at org.optaplanner.core.impl.score.director.AbstractScoreDirector.cloneSolution(AbstractScoreDirector.java:142)
    at org.optaplanner.core.impl.solver.scope.DefaultSolverScope.setWorkingSolutionFromBestSolution(DefaultSolverScope.java:198)
    at org.optaplanner.core.impl.solver.DefaultSolver.runPhases(DefaultSolver.java:217)
    at org.optaplanner.core.impl.solver.DefaultSolver.solve(DefaultSolver.java:176)
    at no.scheduling.shifts.solver.ShiftOptaPlanner.solve(ShiftOptaPlanner.java:124)
    at application.ScheduleSolver.run(ScheduleSolver.java:69)
    at java.lang.Thread.run(Thread.java:745) 

My GUI code does modify the received solutions as the items it contains have to be displayed to the user in a sorted fashion. When I use defensive cloning for solutions provided by the listener's bestSolutionChanged() method, the problem disappears. Am I doing something wrong here or it might be possible that event listener sometimes gets a wired instance used by OptaPlanner instead of a defensive clone? I'd really like to avoid the cloning step, as the cloning functionality I currently have available is limited and slow.

EDIT: With some further debugging, I think I was able to pinpoint the issue in the OptaPlanner source (6.4.0 Final) After finishing the construction heuristic phase, the updateBestSolution() method in the BestSolutionRecaller puts the same solution instance in the fireBestSolutionChanged() as it stores in the solverScope:

 public void updateBestSolution(DefaultSolverScope solverScope, Solution solution, int uninitializedVariableCount) {
        (...)
        solverScope.setBestSolution(solution);
        (...)
        solverEventSupport.fireBestSolutionChanged(solution, uninitializedVariableCount);
    } 

This exactly same instance then goes through cloning in the setWorkingSolutionFromBestSolution at the same time as my GUI thread is sorting the event list in it and exception is thrown as the list of events is iterated in FieldAccessingSolutionClonerRun.cloneCollection(). Guess the solution to the issue would be cloning the solution that goes to fireBestSolutionChanged()?

Did I stumble upon a subtle bug or I'm doing something wrong? Thanks.


Solution

  • Don't do a deep clone (= defensive copy) during bestSolutionEvent. optaplanner-core doesn't do it either because of performance reasons (there's no bug in that code, but planner does do a planning clone (!= deep clone) in BestSolutionRecaller).

    Instead, do a deep clone in your ProblemFactChanges.

    A deep clone is different from a planning clone (which is explained in the docs). The 7.0.0.Beta2 docs contain an explanation with this to clarify this further:

    enter image description here