Skip to content

Commit 670bda7

Browse files
committed
chore: address comments
1 parent 5aa4fa6 commit 670bda7

3 files changed

Lines changed: 10 additions & 13 deletions

File tree

core/src/main/java/ai/timefold/solver/core/impl/solver/event/SolverEventSupport.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ public void fireBestSolutionChanged(SolverScope<Solution_> solverScope, Solution
2424
var timeMillisSpent = solverScope.getBestSolutionTimeMillisSpent();
2525
var bestScore = solverScope.getBestScore();
2626
if (it.hasNext()) {
27-
var event = new BestSolutionChangedEvent<>(solver, timeMillisSpent, newBestSolution, bestScore.raw(),
27+
// We need to clone the new best solution, or we may share the same instance with user consumers.
28+
// Reusing the instance can lead to inconsistent states if intermediary consumers change the solution.
29+
var newBestSolutionCloned = solverScope.getScoreDirector().cloneSolution(newBestSolution);
30+
var event = new BestSolutionChangedEvent<>(solver, timeMillisSpent, newBestSolutionCloned, bestScore.raw(),
2831
bestScore.fullyAssigned());
2932
do {
3033
it.next().bestSolutionChanged(event);

core/src/main/java/ai/timefold/solver/core/impl/solver/recaller/BestSolutionRecaller.java

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -118,33 +118,25 @@ public <Score_ extends Score<Score_>> void processWorkingSolutionDuringMove(Inne
118118

119119
public void updateBestSolutionAndFire(SolverScope<Solution_> solverScope) {
120120
updateBestSolutionWithoutFiring(solverScope);
121-
// We need to clone the solution again, or we may use the same instance employed by the solver scope,
122-
// which is restored between solver phases.
123-
// The operation won't block the solving process.
124-
solverEventSupport.fireBestSolutionChanged(solverScope, solverScope.cloneBestSolution());
121+
solverEventSupport.fireBestSolutionChanged(solverScope, solverScope.getBestSolution());
125122
}
126123

127124
public void updateBestSolutionAndFireIfInitialized(SolverScope<Solution_> solverScope) {
128125
updateBestSolutionWithoutFiring(solverScope);
129126
if (solverScope.isBestSolutionInitialized()) {
130-
// We need to clone the solution again, or we may use the same instance employed by the solver scope,
131-
// which is restored between solver phases.
132-
// The operation won't block the solving process.
133-
solverEventSupport.fireBestSolutionChanged(solverScope, solverScope.cloneBestSolution());
127+
solverEventSupport.fireBestSolutionChanged(solverScope, solverScope.getBestSolution());
134128
}
135129
}
136130

137131
private void updateBestSolutionAndFire(SolverScope<Solution_> solverScope, InnerScore<?> bestScore,
138132
Solution_ bestSolution) {
139133
updateBestSolutionWithoutFiring(solverScope, bestScore, bestSolution);
140-
// We need to clone the solution again, or we may use the same instance employed by the solver scope,
141-
// which is restored between solver phases.
142-
// The operation won't block the solving process.
143-
solverEventSupport.fireBestSolutionChanged(solverScope, solverScope.cloneBestSolution());
134+
solverEventSupport.fireBestSolutionChanged(solverScope, bestSolution);
144135
}
145136

146137
@SuppressWarnings({ "unchecked", "rawtypes" })
147138
private void updateBestSolutionWithoutFiring(SolverScope<Solution_> solverScope) {
139+
// We clone the existing working solution to set it as the best current solution
148140
var newBestSolution = solverScope.getScoreDirector().cloneWorkingSolution();
149141
var newBestScore = solverScope.getSolutionDescriptor().<Score> getScore(newBestSolution);
150142
var innerScore = InnerScore.withUnassignedCount(newBestScore, -solverScope.getScoreDirector().getWorkingInitScore());

core/src/test/java/ai/timefold/solver/core/impl/solver/BestSolutionHolderTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ void testCloningBetweenPhases() throws ExecutionException, InterruptedException
205205
.withProblem(problem)
206206
.withBestSolutionConsumer(
207207
intermediate -> {
208+
// Add a new entity to the cloned solution
208209
intermediate.getEntityList().add(new TestdataEntity("Bad Entity 3"));
209210
countDownLatch.countDown();
210211
})
@@ -213,6 +214,7 @@ void testCloningBetweenPhases() throws ExecutionException, InterruptedException
213214
.run()
214215
.getFinalBestSolution();
215216
countDownLatch.await();
217+
// At this point, the events generated by the best solution consumer should not add any new entities
216218
assertThat(solution.getEntityList()).hasSize(2);
217219
}
218220
}

0 commit comments

Comments
 (0)