Skip to content

Instantly share code, notes, and snippets.

@ge0ffrey
Created November 1, 2019 09:47
Show Gist options
  • Save ge0ffrey/2275ef5670e1245750e3be3fc23fbc34 to your computer and use it in GitHub Desktop.
Save ge0ffrey/2275ef5670e1245750e3be3fc23fbc34 to your computer and use it in GitHub Desktop.
/*
* Copyright 2019 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.optaplanner.core.api.solver.manager;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ThreadFactory;
import java.util.function.Consumer;
import org.optaplanner.core.api.domain.entity.PlanningEntity;
import org.optaplanner.core.api.domain.solution.PlanningSolution;
import org.optaplanner.core.api.domain.solution.drools.ProblemFactCollectionProperty;
import org.optaplanner.core.api.score.Score;
import org.optaplanner.core.api.score.constraint.Indictment;
import org.optaplanner.core.api.solver.Solver;
import org.optaplanner.core.api.solver.SolverFactory;
import org.optaplanner.core.api.solver.event.BestSolutionChangedEvent;
import org.optaplanner.core.config.solver.SolverConfig;
import org.optaplanner.core.impl.score.director.ScoreDirector;
import org.optaplanner.core.impl.solver.DefaultSolverFactory;
import org.optaplanner.core.impl.solver.ProblemFactChange;
import org.optaplanner.core.impl.solver.manager.DefaultSolverManager;
import org.optaplanner.core.impl.solver.termination.Termination;
/**
* A SolverManager solves multiple planning problems of the same domain,
* asynchronously without blocking the calling thread.
* <p>
* To create a SolverManager, use {@link #createFromXmlResource(String)}.
* To solve a planning problem, call {@link #solve(Object, Consumer, Consumer)}
* or {@link #solveAndListenToBestSolutions(Object, Consumer, Consumer)}.
* <p>
* These methods are thread-safe unless explicitly stated otherwise.
* <p>
* Internally a SolverManager manages a thread pool of solver threads (which call {@link Solver#solve(Object)})
* and consumer threads (to handle the {@link BestSolutionChangedEvent}s).
* @param <Solution_> the solution type, the class with the {@link PlanningSolution} annotation
*/
public interface SolverManager<Solution_> extends AutoCloseable {
// ************************************************************************
// Static creation methods: XML
// ************************************************************************
/**
* Reads an XML solver configuration from the classpath
* and uses that {@link SolverConfig} to build a {@link SolverFactory} and then a {@link SolverManager}.
* The XML root element must be {@code <solver>}.
* @param solverConfigResource never null, a classpath resource
* as defined by {@link ClassLoader#getResource(String)}
* @return never null
* @param <Solution_> the solution type, the class with the {@link PlanningSolution} annotation
*/
static <Solution_> SolverManager<Solution_> createFromXmlResource(String solverConfigResource) {
SolverFactory<Solution_> solverFactory = SolverFactory.createFromXmlResource(solverConfigResource);
return new DefaultSolverManager<>(solverFactory);
}
/**
* As defined by {@link #createFromXmlResource(String)}.
* @param solverConfigResource never null, a classpath resource
* as defined by {@link ClassLoader#getResource(String)}
* @param classLoader sometimes null, the {@link ClassLoader} to use for loading all resources and {@link Class}es,
* null to use the default {@link ClassLoader}
* @return never null
* @param <Solution_> the solution type, the class with the {@link PlanningSolution} annotation
*/
static <Solution_> SolverManager<Solution_> createFromXmlResource(String solverConfigResource, ClassLoader classLoader) {
SolverFactory<Solution_> solverFactory = SolverFactory.createFromXmlResource(solverConfigResource, classLoader);
return new DefaultSolverManager<>(solverFactory);
}
// TODO Do we need ThreadFactory threadFactory methods? If yes, we need SolverConfig.create*(..., threadFactory) first.
// ************************************************************************
// Static creation methods: SolverFactory
// ************************************************************************
// TODO add SolverManagerConfig to capture solverThreadCount, consumerThreadCount, throttlingDelay, threadFactory, congestionStrategy, etc properties
/**
* Uses a {@link SolverConfig} to build a {@link SolverManager}.
* If you don't need to manipulate the {@link SolverConfig} programmatically,
* use {@link #createFromXmlResource(String)} instead.
* @param solverConfig never null
* @return never null
* @param <Solution_> the solution type, the class with the {@link PlanningSolution} annotation
*/
static <Solution_> SolverManager<Solution_> create(SolverConfig solverConfig) {
SolverFactory<Solution_> solverFactory = SolverFactory.create(solverConfig);
return new DefaultSolverManager<>(solverFactory);
}
// ************************************************************************
// Interface methods
// ************************************************************************
/**
* Submits a planning problem to solve and returns immediately.
* The planning problem is solved on a solver {@link Thread}, as soon as one is available.
* <p>
* When the solver terminates, the {@code finalBestSolutionConsumer} is called once with the final best solution,
* on that solver {@link Thread}, as soon as one is available.
* To listen to intermediate best solutions too,
* use {@link #solveAndListenToBestSolutions(Object, Object, Consumer, Consumer)} instead.
* <p>
* To stop a solver job before it naturally terminates, call {@link #terminateEarly(Object)}.
* @param problem never null, a {@link PlanningSolution}, usually its planning variables are uninitialized
* @param finalBestSolutionConsumer never null, called only once, on a solver thread
* @param exceptionHandler sometimes null, called if an exception or error occurs.
* If null it defaults to logging the exception as an error.
* @return a generated problem ID. Use that ID to {@link #terminateEarly(Object) terminate} the solver early
* or if {@link #addProblemFactChange(Object, ProblemFactChange) the problem changes in real-time}.
*/
Object solve(
Solution_ problem,
Consumer<Solution_> finalBestSolutionConsumer,
Consumer<Throwable> exceptionHandler);
/**
* As defined by {@link #solve(Object, Consumer, Consumer)},
* but instead of generating a unique problem ID, you need to supply one.
* @param problemId never null, a ID for each planning problem. This must be unique.
* @param problem never null, a {@link PlanningSolution}, usually its planning variables are uninitialized
* @param finalBestSolutionConsumer never null, called only once, on a solver thread
* @param exceptionHandler sometimes null, called if an exception or error occurs.
* If null it defaults to logging the exception as an error.
*/
void solve(Object problemId,
Solution_ problem,
Consumer<Solution_> finalBestSolutionConsumer,
Consumer<Throwable> exceptionHandler);
/**
* Submits a planning problem to solve and returns immediately.
* The planning problem is solved on a solver {@link Thread}, as soon as one is available.
* <p>
* When the solver finds a new best solution, the {@code bestSolutionConsumer} is called every time,
* on a consumer {@link Thread}, as soon as it is available (taking into account any throttling waiting time),
* unless a newer best solution is already available by then (in which case skip ahead discards it).
* <p>
* To stop a solver job before it naturally terminates, call {@link #terminateEarly(Object)}.
* @param problem never null, a {@link PlanningSolution}, usually its planning variables are uninitialized
* @param bestSolutionConsumer never null, called multiple times, on a consumer thread
* @param exceptionHandler sometimes null, called if an exception or error occurs.
* If null it defaults to logging the exception as an error.
* @return a generated problem ID. Use that ID to {@link #terminateEarly(Object) terminate} the solver early
* or if {@link #addProblemFactChange(Object, ProblemFactChange) the problem changes in real-time}.
*/
Object solveAndListenToBestSolutions(
Solution_ problem,
Consumer<Solution_> bestSolutionConsumer,
Consumer<Throwable> exceptionHandler);
/**
* As defined by {@link #solveAndListenToBestSolutions(Object, Consumer, Consumer)},
* but instead of generating a unique problem ID, you need to supply one.
* @param problemId never null, a ID for each planning problem. This must be unique.
* @param problem never null, a {@link PlanningSolution}, usually its planning variables are uninitialized
* @param bestSolutionConsumer never null, called multiple times, on a consumer thread
* @param exceptionHandler sometimes null, called if an exception or error occurs.
* If null it defaults to logging the exception as an error.
*/
void solveAndListenToBestSolutions(
Object problemId,
Solution_ problem,
Consumer<Solution_> bestSolutionConsumer,
Consumer<Throwable> exceptionHandler);
/**
*
* @param problemId never null, the return value from {@link #solve(Object, Consumer, Consumer)}
* or {@link #solveAndListenToBestSolutions(Object, Consumer, Consumer)}
*/
void terminateEarly(Object problemId);
/**
* Submits a {@link ProblemFactChange} to apply on a solver.
* <p>
* If the solver has already terminated, this throws an exception.
* Put the solver in daemon mode to avoid that race condition.
* If the solver hasn't started yet, the {@link ProblemFactChange} runs before the solver starts.
* <p>
* As a side-effect, the {@link ProblemFactChange} restarts the {@link Solver},
* effectively resetting all {@link Termination}s, but not {@link #terminateEarly(Object)}.
* @param problemId never null, the return value from {@link #solve(Object, Consumer, Consumer)}
* or {@link #solveAndListenToBestSolutions(Object, Consumer, Consumer)}
* @param problemFactChange never null
* @throws IllegalArgumentException if the problemId doesn't exist or if the solver has already terminated
*/
void addProblemFactChange(Object problemId, ProblemFactChange<Solution_> problemFactChange);
/**
* Terminates all solvers, cancels all planning problems that haven't started yet
* and discards all queued {@link ProblemFactChange}s.
* Releases all thread pool resources.
* <p>
* No new planning problems can be submitted after calling this method.
*/
@Override
void close();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment