@@ -58,13 +58,13 @@ public final class DefaultSolverJob<Solution_> implements SolverJob<Solution_>,
5858 private final @ Nullable Consumer <SolverJobStartedEvent <Solution_ >> solverJobStartedConsumer ;
5959 private final BiConsumer <? super Object , ? super Throwable > exceptionHandler ;
6060
61- private volatile SolverStatus solverStatus ;
6261 private final CountDownLatch terminatedLatch ;
6362 private final ReentrantLock solverStatusModifyingLock ;
6463 private final AtomicBoolean terminatedEarly = new AtomicBoolean (false );
6564 private final BestSolutionHolder <Solution_ > bestSolutionHolder = new BestSolutionHolder <>();
6665 private final AtomicReference <@ Nullable ProblemSizeStatistics > temporaryProblemSizeStatistics = new AtomicReference <>();
6766
67+ private volatile SolverStatus solverStatus = SolverStatus .SOLVING_SCHEDULED ;
6868 private @ Nullable Future <Solution_ > finalBestSolutionFuture ;
6969 private @ Nullable ConsumerSupport <Solution_ , Object > consumerSupport ;
7070
@@ -88,9 +88,8 @@ public DefaultSolverJob(DefaultSolverManager<Solution_> solverManager, Solver<So
8888 this .firstInitializedSolutionConsumer = firstInitializedSolutionConsumer ;
8989 this .solverJobStartedConsumer = solverJobStartedConsumer ;
9090 this .exceptionHandler = exceptionHandler ;
91- solverStatus = SolverStatus .SOLVING_SCHEDULED ;
92- terminatedLatch = new CountDownLatch (1 );
93- solverStatusModifyingLock = new ReentrantLock ();
91+ this .terminatedLatch = new CountDownLatch (1 );
92+ this .solverStatusModifyingLock = new ReentrantLock ();
9493 }
9594
9695 public void setFinalBestSolutionFuture (Future <Solution_ > finalBestSolutionFuture ) {
@@ -268,7 +267,7 @@ public ProblemSizeStatistics getProblemSizeStatistics() {
268267 // before the solving has started.
269268 // Once the solving has started, the problem size statistics will be computed
270269 // using the ScoreDirector's hot ValueRangeManager.
271- return temporaryProblemSizeStatistics .updateAndGet (oldStatistics -> {
270+ var result = temporaryProblemSizeStatistics .updateAndGet (oldStatistics -> {
272271 if (oldStatistics != null ) {
273272 // If the problem size statistics were already computed, return them.
274273 // This can happen if the problem size statistics were computed before the solving started.
@@ -278,6 +277,11 @@ public ProblemSizeStatistics getProblemSizeStatistics() {
278277 var valueManager = ValueRangeManager .of (solutionDescriptor , problemFinder .apply (problemId ));
279278 return valueManager .getProblemSizeStatistics ();
280279 });
280+ // Avoids nullness issues reported by IDE which cannot actually happen.
281+ // The result can never be null, because none of the methods called in the lambda can return null,
282+ // and the lambda is the only way to set the value of the temporaryProblemSizeStatistics,
283+ // which is the only way for it to be null.
284+ return Objects .requireNonNull (result );
281285 }
282286
283287 public SolverTermination <Solution_ > getSolverTermination () {
@@ -293,21 +297,33 @@ void close() {
293297
294298 /**
295299 * A listener that unlocks the solverStatusModifyingLock when Solving has started.
296- *
297300 * It prevents the following scenario caused by unlocking before Solving started:
298301 *
299- * Thread 1:
300- * solverStatusModifyingLock.unlock()
301- * >solver.solve(...) // executes second
302- *
303- * Thread 2:
304- * case SOLVING_ACTIVE:
305- * >solver.terminateEarly(); // executes first
302+ * <dl>
303+ * <dt>Thread 1</dt>
304+ * <dd>
305+ *
306+ * <pre>
307+ * solverStatusModifyingLock.unlock()
308+ * > solver.solve(...) // executes second
309+ * </pre>
310+ *
311+ * </dd>
312+ * <dt>Thread 2</dt>
313+ * <dd>
314+ *
315+ * <pre>
316+ * case SOLVING_ACTIVE:
317+ * >solver.terminateEarly(); // executes first
318+ * </pre>
319+ *
320+ * </dd>
321+ * </dl>
306322 *
307323 * The solver.solve() call resets the terminateEarly flag, and thus the solver will not be terminated
308324 * by the call, which means terminatedLatch will not be decremented, causing Thread 2 to wait forever
309325 * (at least until another Thread calls terminateEarly again).
310- *
326+ * <p>
311327 * To prevent Thread 2 from potentially waiting forever, we only unlock the lock after the
312328 * solvingStarted phase lifecycle event is fired, meaning the terminateEarly flag will not be
313329 * reset and thus the solver will actually terminate.
0 commit comments