Home > other >  OptaPlanner: SolverManager::solveAndListen synchronization/locking bug?
OptaPlanner: SolverManager::solveAndListen synchronization/locking bug?

Time:09-01

OptaPlanner v8.22.1.Final

TL;DR: the default SolverListener implementation, DefaultSolverListener, doesn't sync. properly for the Consumer<? super Solution_> finalBestSolutionConsumer... or I'm misunderstanding the purpose of this Consumer.

Is this the intended behavior?

The details:

I've found recently some of my tests logging warnings about the last-current-best solution not matching the final-best solution.

The cause is terminateEarly not waiting for the final-best solution Consumer thread to finish.

This isn't a huge problem, because the last-current-best solution is available through the intermediate solutions, but it doesn't seem right that final-best isn't being saved.

I have the following call that starts the optimization:

this.getSolverManager()
    .solveAndListen(solutionID,
                    this::findCurrentBest,
                    this::saveCurrentBest,
                    this::saveFinalBest,
                    this::handleException);

saveFinalBest is this, so it's not like it's an expensive call:

this.finalBestConsumer.accept(finalBest); // does nothing by default
this.finalBestSolutionsMap.put(finalBest.getId(), finalBest);

And we complain:

// logging
this.getSolverManager().terminateEarly(solutionID);
// logging

final var finalBest = this.findFinalBest(solutionID);
final var crrntBest = this.findCurrentBest(solutionID);
final var best = ObjectUtils.max(finalBest, crrntBest);

if (!Objects.equals(finalBest, crrntBest)) {
    // complain
}

return best;

What I'm finding is that terminateEarly returns and we do findFinalBest before the final-best is saved.

So, am I misunderstanding? Is this a bug?

CodePudding user response:

The SolverManager.terminateEarly() does not provide any guarantees regarding when the consumption of the final best solution happens.

When the Solver terminates, either by the configured termination or early, the last known best solution is passed to the final best solution consumer.

The consumer (both the intermediate and the final one) runs in a dedicated thread and there is truly no synchronization between this thread and the SolverManager.terminateEarly().

Consuming the best solution can be a long-running operation, outside of the control of the Solver. If terminateEarly() waited for the consumption to finish, it may lead, for instance, to HTTP request timeouts in the caller thread.

If you want to wait for the final best solution, you can synchronize inside the final best solution consumer, which works for both the normal and early termination.

The Javadoc should explain this clearly, though.

  • Related