/* This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.opentripplanner.updater; import com.beust.jcommander.internal.Lists; import com.google.common.collect.Maps; import com.google.common.util.concurrent.ThreadFactoryBuilder; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import org.opentripplanner.routing.graph.Graph; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This class is attached to the graph: * * <pre> * GraphUpdaterManager updaterManager = graph.getUpdaterManager(); * </pre> * * Each updater will run in its own thread. When changes to the graph have to be made by these * updaters, this should be done via the execute method of this manager to prevent race conditions * between graph write operations. * */ public class GraphUpdaterManager { private static Logger LOG = LoggerFactory.getLogger(GraphUpdaterManager.class); /** * Text used for naming threads when the graph lacks a routerId. */ private static String DEFAULT_ROUTER_ID = "(default)"; /** * Thread factory used to create new threads. */ private ThreadFactory threadFactory; /** * OTP's multi-version concurrency control model for graph updating allows simultaneous reads, * but never simultaneous writes. We ensure this policy is respected by having a single writer * thread, which sequentially executes all graph updater tasks. Each task is a runnable that is * scheduled with the ExecutorService to run at regular intervals. */ private ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); /** * Pool with updaters */ private ExecutorService updaterPool = Executors.newCachedThreadPool(); /** * List with updaters to be able to free resources TODO: is this list necessary? */ List<GraphUpdater> updaterList = new ArrayList<GraphUpdater>(); /** * Parent graph of this manager */ Graph graph; /** * Constructor * * @param graph is parent graph of manager */ public GraphUpdaterManager(Graph graph) { this.graph = graph; String routerId = graph.routerId; if(routerId == null || routerId.isEmpty()) routerId = DEFAULT_ROUTER_ID; threadFactory = new ThreadFactoryBuilder().setNameFormat("GraphUpdater-" + routerId + "-%d").build(); scheduler = Executors.newSingleThreadScheduledExecutor(threadFactory); updaterPool = Executors.newCachedThreadPool(threadFactory); } public void stop() { // TODO: find a better way to stop these threads // Shutdown updaters updaterPool.shutdownNow(); try { boolean ok = updaterPool.awaitTermination(30, TimeUnit.SECONDS); if (!ok) { LOG.warn("Timeout waiting for updaters to finish."); } } catch (InterruptedException e) { // This should not happen LOG.warn("Interrupted while waiting for updaters to finish."); } // Clean up updaters for (GraphUpdater updater : updaterList) { updater.teardown(); } updaterList.clear(); // Shutdown scheduler scheduler.shutdownNow(); try { boolean ok = scheduler.awaitTermination(30, TimeUnit.SECONDS); if (!ok) { LOG.warn("Timeout waiting for scheduled task to finish."); } } catch (InterruptedException e) { // This should not happen LOG.warn("Interrupted while waiting for scheduled task to finish."); } } /** * Adds an updater to the manager and runs it immediately in its own thread. * * @param updater is the updater to add and run */ public void addUpdater(final GraphUpdater updater) { updaterList.add(updater); updaterPool.execute(new Runnable() { @Override public void run() { try { updater.setup(); try { updater.run(); } catch (Exception e) { LOG.error("Error while running updater {}:", updater.getClass().getName(), e); } } catch (Exception e) { LOG.error("Error while setting up updater {}:", updater.getClass().getName(), e); } } }); } /** * This is the method to use to modify the graph from the updaters. The runnables will be * scheduled after each other, guaranteeing that only one of these runnables will be active at * any time. * * @param runnable is a graph writer runnable */ public void execute(GraphWriterRunnable runnable) { executeReturningFuture(runnable); } /** * This is another method to use to modify the graph from the updaters. It behaves like execute, * but blocks until the runnable has been executed. This might be particularly useful in the * setup method of an updater. * * @param runnable is a graph writer runnable * @throws ExecutionException * @throws InterruptedException * @see GraphUpdaterManager.execute */ public void executeBlocking(GraphWriterRunnable runnable) throws InterruptedException, ExecutionException { Future<?> future = executeReturningFuture(runnable); // Ask for result of future. Will block and return null when runnable is successfully // finished, throws otherwise future.get(); } private Future<?> executeReturningFuture(final GraphWriterRunnable runnable) { // TODO: check for high water mark? Future<?> future = scheduler.submit(new Runnable() { @Override public void run() { try { runnable.run(graph); } catch (Exception e) { LOG.error("Error while running graph writer {}:", runnable.getClass().getName(), e); } } }); return future; } public int size() { return updaterList.size(); } /** * Just an example of fetching status information from the graph updater manager to expose it in a web service. * More useful stuff should be added later. */ public Map<Integer, String> getUpdaterDescriptions () { Map<Integer, String> ret = Maps.newTreeMap(); int i = 0; for (GraphUpdater updater : updaterList) { ret.put(i++, updater.toString()); } return ret; } /** * Just an example of fetching status information from the graph updater manager to expose it in a web service. * More useful stuff should be added later. */ public GraphUpdater getUpdater (int id) { if (id >= updaterList.size()) return null; return updaterList.get(id); } }