/******************************************************************************* * Copyright (c) MOBAC developers * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 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 mobac.program; import java.io.FileNotFoundException; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import mobac.exceptions.StopAllDownloadsException; import mobac.program.interfaces.MapSourceListener; import mobac.program.tilestore.berkeleydb.DelayedInterruptThread; import org.apache.log4j.Logger; /** * Controls the worker threads that are downloading the map tiles in parallel. Additionally the job queue containing the * unprocessed tile download jobs can be accessed via this class. */ public class JobDispatcher { private static Logger log = Logger.getLogger(JobDispatcher.class); protected int maxJobsInQueue = 100; protected int minJobsInQueue = 50; protected WorkerThread[] workers; protected PauseResumeHandler pauseResumeHandler; protected MapSourceListener mapSourceListener; protected BlockingQueue<Job> jobQueue = new LinkedBlockingQueue<Job>(); public JobDispatcher(int threadCount, PauseResumeHandler pauseResumeHandler, MapSourceListener mapSourceListener) { this.pauseResumeHandler = pauseResumeHandler; this.mapSourceListener = mapSourceListener; workers = new WorkerThread[threadCount]; for (int i = 0; i < threadCount; i++) workers[i] = new WorkerThread(i); } @Override protected void finalize() throws Throwable { terminateAllWorkerThreads(); super.finalize(); } public void terminateAllWorkerThreads() { cancelOutstandingJobs(); log.trace("Killing all worker threads"); for (int i = 0; i < workers.length; i++) { try { WorkerThread w = workers[i]; if (w != null) { w.interrupt(); } workers[i] = null; } catch (Exception e) { // We don't care about exception here } } } public void cancelOutstandingJobs() { jobQueue.clear(); } /** * Blocks if more than 100 jobs are already scheduled. * * @param job * @throws InterruptedException */ public void addJob(Job job) throws InterruptedException { while (jobQueue.size() > maxJobsInQueue) { Thread.sleep(200); if ((jobQueue.size() < minJobsInQueue) && (maxJobsInQueue < 2000)) { // System and download connection is very fast - we have to // increase the maximum job count in the queue maxJobsInQueue *= 2; minJobsInQueue *= 2; } } jobQueue.put(job); } /** * Adds the job to the job-queue and returns. This method will never block! * * @param job */ public void addErrorJob(Job job) { try { jobQueue.put(job); } catch (InterruptedException e) { // Can never happen with LinkedBlockingQueue } } public int getWaitingJobCount() { return jobQueue.size(); } public static interface Job { public void run(JobDispatcher dispatcher) throws Exception; } public boolean isAtLeastOneWorkerActive() { for (int i = 0; i < workers.length; i++) { WorkerThread w = workers[i]; if (w != null) { if ((!w.idle) && (w.getState() != Thread.State.WAITING)) return true; } } log.debug("All worker threads are idle"); return false; } /** * Each worker thread takes the first job from the job queue and executes it. If the queue is empty the worker * blocks, waiting for the next job. */ protected class WorkerThread extends DelayedInterruptThread implements MapSourceListener { Job job = null; boolean idle = true; private Logger log = Logger.getLogger(WorkerThread.class); public WorkerThread(int threadNum) { super(String.format("WorkerThread %02d", threadNum)); setDaemon(true); start(); } @Override public void run() { try { executeJobs(); } catch (InterruptedException e) { } log.trace("Thread is terminating"); } protected void executeJobs() throws InterruptedException { while (!isInterrupted()) { try { pauseResumeHandler.pauseWait(); idle = true; job = jobQueue.take(); idle = false; } catch (InterruptedException e) { return; } if (job == null) return; try { job.run(JobDispatcher.this); job = null; } catch (InterruptedException e) { } catch (StopAllDownloadsException e) { JobDispatcher.this.terminateAllWorkerThreads(); JobDispatcher.this.cancelOutstandingJobs(); log.warn("All downloads has been stoppened: " + e.getMessage()); return; } catch (FileNotFoundException e) { log.error("Download failed: " + e.getMessage()); } catch (Exception e) { log.error("Unknown error occured while executing the job: ", e); } catch (OutOfMemoryError e) { log.error("", e); Thread.sleep(5000); System.gc(); } } } public void tileDownloaded(int size) { mapSourceListener.tileDownloaded(size); } public void tileLoadedFromCache(int size) { mapSourceListener.tileLoadedFromCache(size); } } }