All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jaitools.jiffle.runtime.JiffleExecutor Maven / Gradle / Ivy

Go to download

Jiffle is a raster algebra language that can be used to create new images from logical and mathematical expressions involving source images and user defined variables. It is intended to let users concentrate on algorithms rather than tedious boiler-plate code.

The newest version!
/*
 * Copyright 2011 Michael Bedward
 * 
 * This file is part of jai-tools.
 *
 * jai-tools 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.
 *
 * jai-tools 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public 
 * License along with jai-tools.  If not, see .
 * 
 */

package jaitools.jiffle.runtime;

import java.awt.image.RenderedImage;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;

import jaitools.DaemonThreadFactory;
import jaitools.jiffle.Jiffle;
import jaitools.jiffle.JiffleException;


/**
 * A multi-threaded, event-driven executor service for Jiffle scripts. Jiffle
 * objects can be submitted to this class to execute rather than the client 
 * working with a JiffleRuntime instance directly. This is preferable
 * for computationally demanding tasks because the scripts run in a separate
 * thread to the client. For multiple tasks, the executor can be set up to
 * run all tasks concurrently in separate threads (if system resources permit),
 * or run up to N tasks concurrently. If necessary, a task will be held in a 
 * queue while waiting for a thread. Setting N to 1 gives the option of serial
 * execution.
 * 

* The client can optionally follow progress during execution with a * {@link jaitools.jiffle.runtime.JiffleProgressListener}. When the task is * finished its status and results can be retrieved via {@link JiffleEventListener}. *

* Example of use: * *


 * // assuming the executor is a class field in this example
 * executor = new JiffleExecutor();
 *
 * executor.addEventListener(new JiffleEventListener() {
 *
 *     public void onCompletionEvent(JiffleEvent ev) {
 *         myCompletionMethod(ev);
 *     }
 *
 *     public void onFailureEvent(JiffleEvent ev) {
 *         myFailureMethod(ev);
 *     }
 * });
 * 
* * Now we can build Jiffle objects and submit them to the executor as shown here: * *

 * String script = "dest = src > 10 ? src : null;" ;
 *
 * Map<String, Jiffle.ImageRole> imageParams = CollectionFactory.map();
 * imageParams.put("src", Jiffle.ImageRole.SOURCE);
 * imageParams.put("dest", Jiffle.ImageRole.DEST);
 *
 * Jiffle jiffle = new Jiffle(script, imageParams);
 *
 * // Map with the source and destination images
 * RenderedImage sourceImg = ...
 * WritableRenderedImage destImg = ...
 * Map<String, RenderedImage> images = CollectionFactory.map();
 * images.put("src", sourceImg);
 * images.put("dest", destImg);
 *
 * // Submit the task to the executor
 * executor.submit(jiffle, images, new MyProgressListener());
 * 
* * When the script has completed the event listener will be notified and * the results can be retrieved: * *

 * private void myCompletionMethod(JiffleEvent ev) {
 *     // Get and display the result image
 *     JiffleExecutorResult result = ev.getResult();
 *     RenderedImage img = result.getImages().get("dest");
 *     ...
 * }
 *
 * private void myFailureMethod(JiffleEvent ev) {
 *     System.out.println("Bummer...");
 * }
 * 
* * Once the application has finished with th executor it should call one of * the shutdown methods which terminate the task and polling threads. * * @author Michael Bedward * @since 1.1 * @version $Id: JiffleExecutor.java 1527 2011-03-08 05:54:23Z michael.bedward $ */public class JiffleExecutor { private static final Logger LOGGER = Logger.getLogger(JiffleExecutor.class.getName()); // Templates for log INFO messages private static final String TASK_SUBMITTED_MSG = "Task {0} submitted"; private static final String TASK_SUCCESS_MSG = "Task {0} completed"; private static final String TASK_FAILURE_MSG = "Task {0} failed"; /** * The default interval for polling tasks to check for * completion (20 mS) */ public static final long DEFAULT_POLLING_INTERVAL = 20L; private long pollingInterval = DEFAULT_POLLING_INTERVAL; /* Provides unique job ID values across all executor instances. */ private static final AtomicInteger jobID = new AtomicInteger(0); private final Object _lock = new Object(); private final ExecutorService taskService; private final ScheduledExecutorService pollingService; private final ScheduledExecutorService shutdownService; private final ExecutorCompletionService completionService; private final List listeners; private final AtomicBoolean isPolling = new AtomicBoolean(false); private final AtomicInteger numTasksRunning = new AtomicInteger(0); /* Used by constructors when setting up the task service. */ private static enum ThreadPoolType { CACHED, FIXED; } /** * Creates an executor with default settings. There is no upper limit * on the number of concurrent tasks. A cached thread pool will be used * which recycles existing threads where possible. */ public JiffleExecutor() { this(ThreadPoolType.CACHED, -1); } /** * Creates an executor that can have, at most,{@code maxTasks} * running concurrently, with further tasks being placed in a queue. * * @param maxTasks the maximum number of concurrent tasks */ public JiffleExecutor(int maxTasks) { this(ThreadPoolType.FIXED, maxTasks); } /** * Private constructor for common setup. * * @param type type of thread pool to use * * @param maxJobs maximum number of concurrent jobs (ignored if * {@code type} is not {@code FIXED} */ private JiffleExecutor(ThreadPoolType type, int maxJobs) { switch (type) { case CACHED: taskService = Executors.newCachedThreadPool(); break; case FIXED: taskService = Executors.newFixedThreadPool(maxJobs); break; default: throw new IllegalArgumentException("Bad arg to private JiffleExecutor constructor"); } completionService = new ExecutorCompletionService(taskService); pollingService = Executors.newSingleThreadScheduledExecutor( new DaemonThreadFactory(Thread.NORM_PRIORITY, "executor-poll")); shutdownService = Executors.newSingleThreadScheduledExecutor( new DaemonThreadFactory(Thread.NORM_PRIORITY, "executor-shutdown")); listeners = new ArrayList(); } /** * Sets the polling interval for task completion. JiffleExecutor uses a * separate thread to poll tasks for completion (either success * or failure) at a fixed interval. The interval can only be changed * prior to submitting the first task. After that, any calls to this * method will result in a warning message being logged and the new * value being ignored. * * @param millis interval between task polling in milliseconds; values * less than 1 are ignored * * @see #DEFAULT_POLLING_INTERVAL */ public void setPollingInterval(long millis) { synchronized (_lock) { if (isPolling.get()) { if (LOGGER.isLoggable(Level.WARNING)) { LOGGER.log(Level.WARNING, "Request to change polling interval ignored"); } } else if (millis < 1) { if (LOGGER.isLoggable(Level.WARNING)) { LOGGER.log(Level.WARNING, "Invalid polling interval ignored: {0}", millis); } } else { pollingInterval = millis; } } } /** * Gets the interval in milliseconds for polling task completion. * * @return polling interval */ public long getPollingInterval() { synchronized (_lock) { return pollingInterval; } } /** * Adds an event listener. * * @param listener the listener * * @see {@link JiffleEvent} */ public void addEventListener(JiffleEventListener listener) { synchronized(_lock) { listeners.add(listener); } } /** * Removes an event listener. * * @param listener the listener * * @return {@code true} if the listener was removed; * {@code false} if it was not registered with this executor */ public boolean removeEventListener(JiffleEventListener listener) { synchronized(_lock) { return listeners.remove(listener); } } /** * Checks if a particular listener is registered with this executor. * * @param listener the listener * * @return {@code true} if the listener has already been added; * {@code false} otherwise */ public boolean isListening(JiffleEventListener listener) { synchronized(_lock) { return listeners.contains(listener); } } /** * Submits an {@code Jiffle} object for execution. If the script is not * already compiled the executor will compile it. Depending on existing * tasks and the number of threads available to the executor there could * be a delay before the task starts. Clients can receive notification * via an optional progress listener. *

* * @param jiffle a properly compiled {@code Jiffle} object * * @param images source and destination images as a {@code Map} with * keys being image variable names as used in the Jiffle script * and image parameters * * @param progressListener an optional progress listener (may be {@code null}) * * @return the job ID that can be used to query progress * * @throws JiffleExecutorException if the {@code Jiffle} object was not * compiled correctly */ public int submit(Jiffle jiffle, Map images, JiffleProgressListener progressListener) throws JiffleExecutorException { synchronized(_lock) { if (taskService.isShutdown()) { throw new IllegalStateException("Submitting task after executor shutdown"); } try { if (!jiffle.isCompiled()) { jiffle.compile(); } } catch (JiffleException ex) { throw new JiffleExecutorException(ex); } startPolling(); int id = jobID.getAndIncrement(); if (LOGGER.isLoggable(Level.INFO)) { LOGGER.log(Level.INFO, TASK_SUBMITTED_MSG, id); } numTasksRunning.incrementAndGet(); completionService.submit( new JiffleExecutorTask( this, id, jiffle, images, progressListener)); return id; } } /** * Requests that the executor shutdown after completing any tasks * already submitted. Control returns immediately to the client. */ public void shutdown() { synchronized(_lock) { taskService.shutdown(); stopPolling(false); } } /** * Requests that the executor shutdown after completing any tasks * already submitted. Control returns to the calling thread after * the executor has shutdown or the time out period has elapsed, * whichever comes first. * * @param timeOut time-out period * @param unit time unit * * @return {@code true} if the executor has shutdown; {@code false} if * the time-out period elapsed or the thread was interrupted */ public boolean shutdownAndWait(long timeOut, TimeUnit unit) { synchronized (_lock) { boolean success = false; taskService.shutdown(); stopPolling(false); try { success = taskService.awaitTermination(timeOut, unit); } catch (InterruptedException ex) { throw new RuntimeException(ex); } return success; } } /** * Attempts to shutdown the executor immediately. */ public void shutdownNow() { taskService.shutdownNow(); stopPolling(true); } /** * Starts the polling service if it is not already running. */ private void startPolling() { if (!isPolling.get()) { pollingService.scheduleWithFixedDelay(new PollingTask(), pollingInterval, pollingInterval, TimeUnit.MILLISECONDS); isPolling.set(true); } } /** * Stops the polling service. * * @param immediate whether to stop the service immediately or wait * for any running tasks to complete */ private void stopPolling(boolean immediate) { if (immediate) { pollingService.shutdown(); return; } shutdownService.scheduleAtFixedRate(new Runnable() { public void run() { if (numTasksRunning.get() == 0) { pollingService.shutdown(); shutdownService.shutdown(); } } }, pollingInterval, pollingInterval, TimeUnit.MILLISECONDS); } private class PollingTask implements Runnable { public void run() { try { Future future = completionService.poll(); if (future != null) { JiffleExecutorResult result = future.get(); numTasksRunning.decrementAndGet(); if (result.isCompleted()) { if (LOGGER.isLoggable(Level.INFO)) { LOGGER.log(Level.INFO, TASK_SUCCESS_MSG, result.getTaskID()); } notifySuccess(result); } else { if (LOGGER.isLoggable(Level.INFO)) { LOGGER.log(Level.INFO, TASK_FAILURE_MSG, result.getTaskID()); } notifyFailure(result); } } } catch (Exception ex) { throw new RuntimeException(ex); } } private void notifySuccess(JiffleExecutorResult result) { for (JiffleEventListener listener : listeners) { listener.onCompletionEvent(new JiffleEvent(result)); } } private void notifyFailure(JiffleExecutorResult result) { for (JiffleEventListener listener : listeners) { listener.onFailureEvent(new JiffleEvent(result)); } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy