Skip to content

Commit 2ddef6b

Browse files
committed
Revert "fix: clone the solution before sending it to the consumer"
1 parent d052cd9 commit 2ddef6b

5 files changed

Lines changed: 4 additions & 45 deletions

File tree

core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolver.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ public void setMonitorTagMap(Map<String, String> monitorTagMap) {
204204
restartSolver = checkProblemFactChanges();
205205
}
206206
outerSolvingEnded(solverScope);
207-
return solverScope.cloneBestSolution();
207+
return solverScope.getBestSolution();
208208
}
209209

210210
public void outerSolvingStarted(SolverScope<Solution_> solverScope) {

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

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,7 @@ public void fireBestSolutionChanged(SolverScope<Solution_> solverScope, Solution
2323
var timeMillisSpent = solverScope.getBestSolutionTimeMillisSpent();
2424
var bestScore = solverScope.getBestScore();
2525
if (it.hasNext()) {
26-
// We need to clone the new best solution, or we may share the same instance with user consumers.
27-
// Reusing the instance can lead to inconsistent states if intermediary consumers change the solution.
28-
var newBestSolutionCloned = solverScope.getScoreDirector().cloneSolution(newBestSolution);
29-
var event = new DefaultBestSolutionChangedEvent<>(solver, timeMillisSpent, newBestSolutionCloned, bestScore);
26+
var event = new DefaultBestSolutionChangedEvent<>(solver, timeMillisSpent, newBestSolution, bestScore);
3027
do {
3128
it.next().bestSolutionChanged(event);
3229
} while (it.hasNext());

core/src/main/java/ai/timefold/solver/core/impl/solver/scope/SolverScope.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -224,10 +224,6 @@ public Solution_ getBestSolution() {
224224
return bestSolution.get();
225225
}
226226

227-
public Solution_ cloneBestSolution() {
228-
return scoreDirector.cloneSolution(bestSolution.get());
229-
}
230-
231227
/**
232228
* The {@link PlanningSolution best solution} must never be the same instance
233229
* as the {@link PlanningSolution working solution}, it should be a (un)changed clone.
@@ -336,7 +332,7 @@ public long getMoveEvaluationSpeed() {
336332

337333
public void setWorkingSolutionFromBestSolution() {
338334
// The workingSolution must never be the same instance as the bestSolution.
339-
scoreDirector.setWorkingSolution(cloneBestSolution());
335+
scoreDirector.setWorkingSolution(scoreDirector.cloneSolution(getBestSolution()));
340336
}
341337

342338
public SolverScope<Solution_> createChildThreadSolverScope(ChildThreadType childThreadType) {

core/src/test/java/ai/timefold/solver/core/impl/constructionheuristic/DefaultConstructionHeuristicPhaseTest.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,7 @@ void solveWithInitializedSolution() {
9898
new TestdataEntity("e3", v3)));
9999

100100
var solution = PlannerTestUtils.solve(solverConfig, inputProblem, false);
101-
assertThat(inputProblem.getEntityList().stream().map(TestdataEntity::getValue).toList())
102-
.isEqualTo(solution.getEntityList().stream().map(TestdataEntity::getValue).toList());
101+
assertThat(inputProblem).isSameAs(solution);
103102
}
104103

105104
@Test

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

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,14 @@
1313
import java.util.UUID;
1414
import java.util.concurrent.CompletableFuture;
1515
import java.util.concurrent.CountDownLatch;
16-
import java.util.concurrent.ExecutionException;
1716
import java.util.concurrent.Executors;
1817

1918
import ai.timefold.solver.core.api.solver.Solver;
20-
import ai.timefold.solver.core.api.solver.SolverConfigOverride;
2119
import ai.timefold.solver.core.api.solver.SolverManager;
2220
import ai.timefold.solver.core.api.solver.change.ProblemChange;
2321
import ai.timefold.solver.core.api.solver.change.ProblemChangeDirector;
2422
import ai.timefold.solver.core.config.solver.SolverConfig;
2523
import ai.timefold.solver.core.config.solver.SolverManagerConfig;
26-
import ai.timefold.solver.core.config.solver.termination.TerminationConfig;
2724
import ai.timefold.solver.core.testdomain.TestdataEasyScoreCalculator;
2825
import ai.timefold.solver.core.testdomain.TestdataEntity;
2926
import ai.timefold.solver.core.testdomain.TestdataSolution;
@@ -189,36 +186,6 @@ void problemChangeBarrageIntermediateBestSolutionConsumer() throws InterruptedEx
189186

190187
}
191188

192-
@Test
193-
void testCloningBetweenPhases() throws ExecutionException, InterruptedException {
194-
var solverConfig = new SolverConfig()
195-
.withSolutionClass(TestdataSolution.class)
196-
.withEntityClasses(TestdataEntity.class)
197-
.withEasyScoreCalculatorClass(TestdataEasyScoreCalculator.class);
198-
199-
try (var solverManager = SolverManager.<TestdataSolution, UUID> create(solverConfig, new SolverManagerConfig())) {
200-
var problem = TestdataSolution.generateSolution(2, 2);
201-
problem.getEntityList().forEach(entity -> entity.setValue(null));
202-
var countDownLatch = new CountDownLatch(1);
203-
var solution = solverManager.solveBuilder()
204-
.withProblemId(UUID.randomUUID())
205-
.withProblem(problem)
206-
.withBestSolutionConsumer(
207-
intermediate -> {
208-
// Add a new entity to the cloned solution
209-
intermediate.getEntityList().add(new TestdataEntity("Bad Entity 3"));
210-
countDownLatch.countDown();
211-
})
212-
.withConfigOverride(new SolverConfigOverride<TestdataSolution>()
213-
.withTerminationConfig(new TerminationConfig().withMoveCountLimit(100L)))
214-
.run()
215-
.getFinalBestSolution();
216-
countDownLatch.await();
217-
// At this point, the events generated by the best solution consumer should not add any new entities
218-
assertThat(solution.getEntityList()).hasSize(2);
219-
}
220-
}
221-
222189
private record RecordedFuture(int id, CompletableFuture<Void> future) {
223190

224191
boolean isDone() {

0 commit comments

Comments
 (0)