/* * org.openmicroscopy.shoola.util.concur.tasks.AsyncProcessor * *------------------------------------------------------------------------------ * Copyright (C) 2006 University of Dundee. All rights reserved. * * * 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, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * *------------------------------------------------------------------------------ */ package org.openmicroscopy.shoola.util.concur.tasks; //Java imports import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Set; //Third-party libraries //Application-internal dependencies /** * A concrete {@link CmdProcessor} that supports a thread-per-service * model. * Each service is executed in its own thread which is created when the * service object is handed off to one of the <code>exec</code> methods * and disposed when the service exits. * <p>In this model services are not queued up for execution, so it's not * possible that dependencies among services can cause deadlocks — at * least until threads can keep on being created at the same pace of the * arrival rate of requests to execute services, that is, invocations of * the <code>exec</code> methods.</p> * <p>On the other hand, you might have to face the possibility of resource * exhaustion, depending on arrival rate and duration of services — this * could be something to consider very carefully if your JVM doesn't enforce * any internal thread pooling. Also take into account that having too many * threads kicking around at the same time could make the context switching * and scheduling overhead reach peaks which could account for too a high * percentage of the actual service execution.</p> * * @author Jean-Marie Burel      * <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a> * @author <br>Andrea Falconi      * <a href="mailto:a.falconi@dundee.ac.uk"> * a.falconi@dundee.ac.uk</a> * @version 2.2 * <small> * (<b>Internal version:</b> $Revision$ $Date$) * </small> * @since OME2.2 */ public class AsyncProcessor extends CmdProcessor { //TODO: extract CancellableTask I/F from ExecCommand. This I/F will //extend Runnable and add the cancel method. This I/F will substitute //Runnable in the role of Abstract Command. When done, get rid of all //occurrences of ExecCommand in this file and replace them with this //new I/F. /** * The run loop we use to execute commands. * We run the command, trap any uncaught exceptions (and call the * <code>uncaughtExcHandler</code>, if one was provided), and then * notify the enclosing <code>AsyncProcessor</code> upon exit. */ private class Runner implements Runnable { ExecCommand cmd; Runner(ExecCommand cmd) { this.cmd = cmd; } public void run() { try { cmd.run(); } catch (Throwable t) { if (uncaughtExcHandler != null) uncaughtExcHandler.handle(t); else t.printStackTrace(); } finally { //Make sure we notify in any case. notifyExit(this); } } } /** Maps a <code>Runner</code> onto the thread that is running it. */ private final Map threadsMap; /** Custom handler used in the case of uncaught exceptions. */ private final UncaughtExcHandler uncaughtExcHandler; /** * Tells whether the processor will accept and run new commands. * Initially set to <code>false</code> to mean the processor will * accept and run new commands transferred via the * {@link #doExec(Runnable) doExec} method. Latches to <code>true</code> * when the {@link #terminate(long) terminate} method is called. From * that point on, new commands will be cancelled and discarded. */ private boolean terminated; /** * Factory method to create a thread that will run <code>cmd</code>. * The <code>cmd</code> object is wrapped into a <code>Runner</code>, * which is then actually passed to the new thread. * The {@link #threadsMap} is updated accordingly. * This method will do nothing and return <code>null</code>, if the * {@link #terminated} flag is <code>true</code>. * * @param cmd The command to run. * @return A new thread to run <code>cmd</code> or <code>null</code> if * the {@link #terminated} flag is <code>true</code>. */ private synchronized Thread createRunner(ExecCommand cmd) { if (terminated) return null; Runner r = new Runner(cmd); Thread t = new Thread(r); threadsMap.put(r, t); return t; } /** * Callback used by a <code>Runner</code> when exiting the run loop. * It just updates the {@link #threadsMap} by removing the * <code>Runner</code> and its associated thread. * * @param r The <code>Runner</code> that exited. */ private synchronized void notifyExit(Runner r) { threadsMap.remove(r); } /** * Transfers a command for execution. * * @param cmd The command to run. * @see CmdProcessor#doExec(java.lang.Runnable) */ protected void doExec(Runnable cmd) { ExecCommand srv = (ExecCommand) cmd; Thread t = createRunner(srv); if (t == null) srv.cancel(); else t.start(); } /** * Creates a new instance. */ public AsyncProcessor() { threadsMap = new HashMap(); uncaughtExcHandler = null; terminated = false; } /** * Creates a new instance. * Registers the passed <code>handler</code> to handle all uncaught * exceptions that occurred during the execution of a service. * * @param handler Handles uncaught exceptions. Mustn't be * <code>null</code>. * @see UncaughtExcHandler */ public AsyncProcessor(UncaughtExcHandler handler) { if (handler == null) throw new NullPointerException("No handler."); threadsMap = new HashMap(); uncaughtExcHandler = handler; terminated = false; } /** * Cancels execution of all currently running services. * This is equivalent to calling the * {@link ExecHandle#cancelExecution() cancelExecution} method on each * {@link ExecHandle} of the currently executing services. */ public void cancelAll() { Runner[] runners; synchronized (this) { //Take snapshot. Set keys = threadsMap.keySet(); runners = new Runner[keys.size()]; keys.toArray(runners); } //From now on new runners can be added to the threadsMap and/or //existing ones can exit and thus be removed from the map. This //is not a concern. for (int i = 0; i < runners.length; ++i) runners[i].cmd.cancel(); //Won't hurt if no longer a runner. } /** * Cancels execution of all currently running services and disallows * execution of new ones. * Waits at most <code>maxWait</code> milliseconds for each running * thread to exit and retuns <code>true</code> only if all waited for * threads have died. After this method has been called, the processor * is disabled, meaning that invocations of any of the <code>exec</code> * methods will result in the service being cancelled and discarded. * Ideally you would call this method after all the client threads that * are using this processor have joined and so no call to any of the * <code>exec</code> methods is possible after this method is invoked. * * @param maxWait Maximum amount of milliseconds to wait for each running * thread to exit. * @return <code>true</code> no service is still running, * <code>false</code> otherwise. */ public boolean terminate(long maxWait) { Thread[] runners; synchronized (this) { terminated = true; //Disable processor forever. cancelAll(); //...running services (synch, so re-entrant). //Take snapshot. Collection values = threadsMap.values(); runners = new Thread[values.size()]; values.toArray(runners); } //From now on existing runners can exit and thus be removed from //the threadsMap, but no new runner can be added to the map. In //fact, the terminated flag is true, so the createRunner method //will do nothing. So the runners array is the set of all threads //that are currently running or have exited since the snapshot was //taken. boolean anyThreadStillRunning = false; for (int i = 0; i < runners.length; ++i) { try { runners[i].join(maxWait); } catch (InterruptedException ie) { //Ignore. This whole loop is a bounded wait. } if (runners[i].isAlive()) anyThreadStillRunning = true; //see NOTE. } return !anyThreadStillRunning; } /* NOTE: Some JVM implementations could return true even if the thread * is not completely defunct yet. */ }