Java tutorial
/* * seamframe: Scheduler.java * ============================================================================== * This work has been carried out as part of the SEAMLESS Integrated Framework * project, EU 6th Framework Programme, contract no. 010036-2 and/or as part * of the SEAMLESS association. * * Copyright (c) 2009 The SEAMLESS Association. * * For more information: http://www.seamlessassociation.org; * email: info@seamless-if.org * * The contents of this file is subject to the SEAMLESS Association License for * software infrastructure and model components Version 1.1 (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.seamlessassociation.org/License.htm * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for * the specific governing rights and limitations. * ================================================================================ */ package org.seamless_if.processing.scheduler; import java.io.File; import java.io.FileWriter; import java.util.Collections; import java.util.Iterator; import java.util.List; import org.apache.log4j.Logger; import org.dom4j.Document; import org.dom4j.DocumentHelper; import org.dom4j.Element; import org.dom4j.dom.DOMElement; import org.dom4j.io.XMLWriter; import org.seamless_if.processing.sofa.SeamException; /** * Singleton object that coordinates between a set of Workers and list of Jobs * in a Queue. It schedules Jobs to Workers, follows progress, and keeps track * of the availability of each Worker. * <p/> * Note: Uses the enum pattern to implement the singleton, access it with * Scheduler.INSTANCE. * * @author Rob Knapen; Alterra, Wageningen UR, NL */ public enum Scheduler { INSTANCE; /** * Required state update frequency for workers, before they will be * considered to have timed out and set to unavailable. */ private static long WORKER_STATE_UPDATE_TIMEOUT_IN_SEC = 30; /** * Time in seconds that the scheduler will sleep inbetween scheduling * jobs in the queue. */ private static long SCHEDULER_SLEEP_TIME_IN_SEC = 10; /** * Logger for the Scheduler. */ private static Logger logger = Logger.getLogger(Scheduler.class.getName()); /** * Name of file used to persist scheduler data. */ private static String schedulerFileName = null; private WorkerInfoList workers = new WorkerInfoList(); private JobQueue jobQueue = new JobQueue(); private JobHistory jobHistory = new JobHistory(); // TODO figure out which methods have to be synchronized public void log(String message, boolean fatal) { System.out.println("Scheduler: " + message); if (fatal) { logger.error(message); throw new RuntimeException(message); } else { logger.info(message); } } public WorkerInfo registerWorker(WorkerInfo workerInfo) { // check for duplicate queue entries if (getWorker(workerInfo.getId()) != null) { log("Cancelling registration of worker [" + workerInfo + "], a worker with the same ID is already registered.", true); return null; } workerInfo.setState(WorkerState.UNKNOWN); if (workers.add(workerInfo)) return workerInfo; else return null; } public WorkerInfo registerWorker(String ip, String name) { WorkerInfo worker = new WorkerInfo(ip, name); return registerWorker(worker); } public List<WorkerInfo> getAllWorkers() { return Collections.unmodifiableList(workers); } public WorkerInfo getWorker(String workerId) { return workers.get(workerId); } public void unregisterWorker(String workerId) { workers.remove(workerId); } public ModelChainInfo registerModelChainForWorker(String workerId, String modelChainName, String modelChainVersion) { // check parameters if ((workerId == null) || (modelChainName == null) || (modelChainVersion == null)) return null; // worker with matching id should exist WorkerInfo worker = getWorker(workerId); if (worker == null) return null; // check if model chain with name and version already exists List<ModelChainInfo> current = getAllKnownModelChainsInfo(); for (ModelChainInfo info : current) { if (modelChainName.equalsIgnoreCase(info.getName()) && modelChainVersion.equalsIgnoreCase(info.getVersion())) { worker.addAvailableModelChain(info); return info; } } // introduce new model chain ModelChainInfo info = new ModelChainInfo(); info.setName(modelChainName); info.setVersion(modelChainVersion); worker.addAvailableModelChain(info); return info; } public ModelChainInfo unregisterModelChainForWorker(String workerId, String modelChainId) { // check parameters if ((workerId == null) || (modelChainId == null)) return null; // worker with matching id should exist WorkerInfo worker = getWorker(workerId); if (worker == null) return null; return worker.removeAvailableModelChain(modelChainId); } public List<ModelChainInfo> getAllKnownModelChainsInfo() { List<ModelChainInfo> result = workers.getCurrentlyKnownModelChainsInfo(); return Collections.unmodifiableList(result); } public ModelChainInfo getModelChainInfo(String modelChainId) { List<ModelChainInfo> available = workers.getCurrentlyKnownModelChainsInfo(); for (ModelChainInfo info : available) { if (info.getId().equals(modelChainId)) return info; } return null; } public Job addJob(Long experimentId, String modelChainId) { // check for existing model chain Job job = new Job(); job.setExperimentId(experimentId); job.setModelChain(getModelChainInfo(modelChainId)); if (job.getModelChain() == null) { log("Cancelling adding [" + job + "] to queue, currently no calculation node provides a model chain with ID: " + modelChainId, true); return null; } return addJob(job); } public Job addJob(Job job) { // check for duplicate queue entries if (getJob(job.getId()) != null) { log("Cancelling adding [" + job + "] to queue, a job with the same ID is already queued.", true); return null; } job.setState(JobState.WAITING_UNSCHEDULED); if (jobQueue.add(job)) return job; else return null; } public Job getJobFromQueue(String jobId) { return jobQueue.get(jobId); } public List<Job> getAllJobsFromQueue() { return jobQueue.getAll(); } public Job getJobFromHistory(String jobId) { return jobHistory.getJob(jobId); } public List<Job> getAllJobsFromHistory() { return jobHistory.getAll(); } public Job getJob(String jobId) { Job job = jobQueue.get(jobId); if (job != null) { return job; } return jobHistory.getJob(jobId); } public synchronized Job getJobForWorker(String workerId) { Job job = jobQueue.getFirstJobForWorker(workerId); return job; } public synchronized void updateWorkerState(String workerId, WorkerState newState) { if (!newState.canBeSetExternally()) { log("A client is not allowed to set worker state to " + newState, true); return; } WorkerInfo workerInfo = workers.get(workerId); if (workerInfo == null) { log("No worker registered with id: " + workerId, true); return; } workerInfo.setState(newState); } /** * Updates the state of a job, called by a Worker. Based on the newState * specified the Scheduler decides what to do with the job, for example * remove it from the queue and place it in the history. Note that not * all states can be set this way, Workers are only allowed to set part * of the possible states. * * @param jobId * @param newState */ public synchronized void updateJobState(String jobId, JobState newState) { if (!newState.canBeSetExternally()) { log("A client is not allowed to set job state to " + newState, true); return; } Job job = jobQueue.get(jobId); if (job == null) { log("Queue does not contain a job with id: " + jobId, true); return; } switch (newState) { case IN_PROGRESS: case ABORTED: // do nothing besides updating the state, job stays queued job.setState(newState); break; case COMPLETED_OK: case COMPLETED_WITH_WARNINGS: case COMPLETED_WITH_ERRORS: // job must have been IN_PROGRESS if (!job.getState().equals(JobState.IN_PROGRESS)) log("Invalid job state change from " + job.getState() + " to " + newState, true); job.setState(newState); moveJobToHistory(job); break; } // TODO save changes } private void moveJobToHistory(Job job) { jobQueue.remove(job.getId()); jobHistory.addJob(job); // TODO save changes } public synchronized void scheduleJobs() { checkWorkersStateUpdateTimeout(); updateJobsStateForWorkerAvailability(); for (WorkerInfo workerInfo : workers) { if (workerInfo.getState().equals(WorkerState.IDLE)) { if (!jobQueue.jobsAssignedToWorker(workerInfo.getId())) { Job job = jobQueue.findJobForWorker(workerInfo); if (job != null) { job.setAssignedToWorker(workerInfo); job.setState(JobState.WAITING_SCHEDULED); } } } } } /** * Checks all registered workers to see which one did not update its * state in time (before the specified timeout constant). When state * has not been updated in time it is set to UNKNOWN. */ private synchronized void checkWorkersStateUpdateTimeout() { long thresholdTime = System.currentTimeMillis() - WORKER_STATE_UPDATE_TIMEOUT_IN_SEC * 1000; for (WorkerInfo workerInfo : workers) { if (workerInfo.getLastStateUpdateInMillis() < thresholdTime) { workerInfo.setState(WorkerState.UNKNOWN); } } } /** * Checks current job assignments to workers and when a job is * assigned to a worker that is no longer available resets the * assignment and set the job state to UNSCHEDULED. */ private synchronized void updateJobsStateForWorkerAvailability() { Iterator<Job> iterator = jobQueue.iterator(); while (iterator.hasNext()) { Job queuedJob = iterator.next(); if ((queuedJob.getAssignedToWorker() != null) && (!queuedJob.getAssignedToWorker().getState().isAvailableState())) { queuedJob.setAssignedToWorker(null); queuedJob.setState(JobState.WAITING_UNSCHEDULED); } } } private static volatile boolean stopRequested = false; private static volatile Thread schedulerThread; /** * Runs the scheduling at the specified update frequency as a separate * thread. */ public void start() { if (schedulerThread != null) return; schedulerThread = new Thread(new Runnable() { public void run() { try { while (!stopRequested) { log("Scheduling jobs...", false); Scheduler.INSTANCE.scheduleJobs(); log("Sleeping for " + SCHEDULER_SLEEP_TIME_IN_SEC + " sec.", false); Thread.sleep(SCHEDULER_SLEEP_TIME_IN_SEC * 1000); } log("Scheduler thread stopped", false); schedulerThread = null; // TODO test if we can do this } catch (InterruptedException e) { log("Scheduler thread stopped by error", false); e.printStackTrace(); schedulerThread = null; } } }); log("Starting scheduler thread", false); schedulerThread.start(); } /** * Requests stopping of the background scheduling thread. */ public void stop() { stopRequested = true; } public void clear() { if (schedulerThread != null) { throw new SeamException("Can not clear the scheduler queue while it is running!"); } else { jobQueue.clear(); jobHistory.clear(); workers.clear(); } } /** * Sets the name of the file to be used to persist scheduler data. * * @param filename Name of file that holds scheduler information */ public synchronized void setFileName(String filename) { if ((filename != null) && (schedulerFileName == null) || (!schedulerFileName.equals(filename))) { logger.info("Setting scheduler state filename to: " + filename); schedulerFileName = filename; // see if we are initializing and need to restore data from the file // Scheduling must not be active, queue must still be empty and the file must exist if ((schedulerThread == null) && (jobQueue.size() == 0) && (new File(filename).exists())) { logger.info("Initialising scheduler state from file: " + filename); load(); } } } /** * Returns the name of the file used to persist the scheduler state in. * * @return filename */ public synchronized String getQueueFileName() { return schedulerFileName; } public Element toXml() { Element root = new DOMElement("Scheduler"); Element jobs = root.addElement("Jobs"); Iterator<Job> iterQueue = jobQueue.iterator(); while (iterQueue.hasNext()) { jobs.add(iterQueue.next().toXml()); } Iterator<Job> iterHistory = jobHistory.iterator(); while (iterHistory.hasNext()) { jobs.add(iterHistory.next().toXml()); } return root; } /** * Saves the current state of the scheduler. */ public synchronized void save() { save(schedulerFileName); } /** * Saves the current state of the scheduler in a file with the specified * name. * * @param filename of file to store scheduler state in */ public synchronized void save(String filename) { logger.info("Saving scheduler state to file: " + filename); Document document = DocumentHelper.createDocument(toXml()); try { XMLWriter writer = new XMLWriter(new FileWriter(new File(filename), false)); writer.write(document); writer.close(); } catch (Exception ex) { throw new SeamException(ex, "Failed to save scheduler state! Error: %s", ex.getMessage()); } logger.info("Saving scheduler state completed"); } /** * Restores the state of the queue. */ public synchronized void load() { load(schedulerFileName); } /** * Restores the state of the queue from the file with the specified file * name. * * @param filename to restore queue state from */ public synchronized void load(String filename) { logger.info("Loading scheduler data from file: " + filename); /* TODO StringBuilder contents = new StringBuilder(); try { BufferedReader input = new BufferedReader(new FileReader(new File(filename))); try { String line; while ((line = input.readLine()) != null) { contents.append(line); contents.append(System.getProperty("line.separator")); } } finally { input.close(); } } catch (IOException ex) { throw new SeamException(ex, "Failed to load queue information! Error: %s", ex .getMessage()); } _expQueue.restoreState(contents.toString()); */ logger.info("Loading scheduler data completed"); } }