/** TrakEM2 plugin for ImageJ(C). Copyright (C) 2005-2009 Albert Cardona and Rodney Douglas. 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 (http://www.gnu.org/licenses/gpl.txt) 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. You may contact Albert Cardona at acardona at ini.phys.ethz.ch Institute of Neuroinformatics, University of Zurich / ETH, Switzerland. **/ package ini.trakem2.utils; import ini.trakem2.ControlWindow; import ini.trakem2.Project; import java.util.ArrayList; /** Sets a Worker thread to work, and waits until it finishes, blocking all user interface input until then, except for zoom and pan, for all given projects. */ public class Bureaucrat extends Thread { final private Worker worker; final private Thread worker_thread; final private long onset; final private Project[] project; private boolean started = false; /** A list of tasks to run when the Worker finishes--but not when it quits. */ private ArrayList<Runnable> post_tasks = new ArrayList<Runnable>(); /** Registers itself in the project loader job queue. */ private Bureaucrat(ThreadGroup tg, Worker worker, Project project) { this(tg, worker, new Project[]{project}); } private Bureaucrat(ThreadGroup tg, Worker worker, Project[] project) { super(tg, "T2-Bureaucrat"); setPriority(Thread.NORM_PRIORITY); this.worker = worker; this.worker_thread = new CachingThread(tg, worker, worker.getThreadName()); this.worker_thread.setPriority(NORM_PRIORITY); worker.setThread(worker_thread); this.project = project; onset = System.currentTimeMillis(); for (int i=0; i<project.length; i++) { project[i].setReceivesInput(false); project[i].getLoader().addJob(this); } } /** Creates but does not start the Bureaucrat thread. */ static public Bureaucrat create(Worker worker, Project project) { return create (worker, new Project[]{project}); } /** Creates but does not start the Bureaucrat thread. */ static public Bureaucrat create(Worker worker, Project[] project) { ThreadGroup tg = new ThreadGroup("T2-Bureaucrat for " + worker.getTaskName()); return new Bureaucrat(tg, worker, project); } /** Creates and start the Bureaucrat thread. */ static public Bureaucrat createAndStart(Worker worker, Project project) { return createAndStart(worker, new Project[]{project}); } /** Creates and start the Bureaucrat thread. */ static public Bureaucrat createAndStart(Worker worker, Project[] project) { ThreadGroup tg = new ThreadGroup("T2-Bureaucrat for " + worker.getTaskName()); tg.setMaxPriority(Thread.NORM_PRIORITY); Bureaucrat burro = new Bureaucrat(tg, worker, project); burro.goHaveBreakfast(); return burro; } /** Starts the Bureaucrat thread: sets the worker to work and monitors it until it finishes.*/ public void goHaveBreakfast() { worker_thread.start(); // Make sure we start AFTER the worker has started. while (!worker.hasStarted()) { try { Thread.sleep(50); } catch (InterruptedException ie) { ie.printStackTrace(); } } start(); // Make sure we return AFTER having started. while (!started) { try { Thread.sleep(50); } catch (InterruptedException ie) { ie.printStackTrace(); } } } private void cleanup() { Utils.showProgress(1); // cleanup all possible interruptions for (int i=0; i<project.length; i++) { project[i].getLoader().removeJob(this); project[i].setReceivesInput(true); } } public void run() { started = true; // wait until worker starts while (!worker.isWorking()) { try { Thread.sleep(50); } catch (InterruptedException ie) {} if (worker.hasQuitted() || worker_thread.isInterrupted()) { //Utils.log("Cleaning up..."); worker.cleanup2(); cleanup(); //Utils.log("...done."); return; } } ControlWindow.startWaitingCursor(); int sandwitch = ControlWindow.isGUIEnabled() ? 100 : 5000; // 0.1 second or 5 Utils.showStatus("Started processing: " + worker.getTaskName(), false); // don't steal focus, ever final StringBuilder sb = new StringBuilder("Processing... ").append(worker.getTaskName()).append(" - "); final int base_len = sb.length(); while (worker.isWorking() && !worker.hasQuitted()) { try { Thread.sleep(sandwitch); } catch (InterruptedException ie) {} float elapsed_seconds = (System.currentTimeMillis() - onset) / 1000.0f; if (elapsed_seconds < 60) { sb.append((int)elapsed_seconds).append(" seconds"); } else { sb.append((int)(elapsed_seconds / 60)).append("' ").append((int)(elapsed_seconds % 60)).append("''"); } Utils.showStatus(sb.toString(), false); // don't steal focus // Increment up to 1 second if (sandwitch < 1000) sandwitch += 100; // reset: sb.setLength(base_len); } ControlWindow.endWaitingCursor(); final long elapsed = System.currentTimeMillis() - onset; final String done = "Done " + worker.getTaskName() + " (" + Utils.cutNumber(elapsed/1000.0, 2) + "s approx.)"; Utils.showStatus(done, false); // don't steal focus; Utils.log2(done); try { if (null != post_tasks) { for (final Runnable r : post_tasks) { try { r.run(); } catch (Throwable t) { IJError.print(t); } } } } catch(Throwable t) { IJError.print(t); } cleanup(); } /** Returns the task the worker is currently executing, which may change over time. */ public String getTaskName() { return worker.getTaskName(); } /** Waits until worker finishes before returning. * Calls quit() on the Worker and interrupt() on each threads in this ThreadGroup and subgroups. */ public void quit() { try { // Cancel post tasks: synchronized (post_tasks) { post_tasks.clear(); post_tasks = null; } Utils.log2("ThreadGroup is " + getThreadGroup()); Utils.log2("ThreadGroup active thread count: " + getThreadGroup().activeCount()); Utils.log2("ThreadGroup active group count: " + getThreadGroup().activeGroupCount()); Thread[] active = new Thread[getThreadGroup().activeCount()]; int count = getThreadGroup().enumerate(active); Utils.log2("Active threads: " + count); for (int i=0; i < count; i++) { Utils.log2("Active thread: " + active[i]); } // Set flag to each thread and thread in subgroup to quit: worker.quit(); getThreadGroup().interrupt(); } catch (Exception e) { IJError.print(e); } // wait until worker finishes try { Utils.log("Waiting for worker to quit..."); worker_thread.join(); Utils.log("Worker quitted."); } catch (InterruptedException ie) { IJError.print(ie); } // wait for all others in a separate thread, then clear progress bar Thread.yield(); final ThreadGroup tg = getThreadGroup(); if (null == tg) { Utils.log2("All threads related to the task died."); return; // will be null if all threads of the former group have died } new Thread() { public void run() { try { // Reasonable effort to join all threads Thread[] t = new Thread[tg.activeCount() * 2]; int len = tg.enumerate(t); for (int i=0; i<len && i<t.length; i++) { Utils.log2("Joining thread: " + t[i]); try { t[i].join(); } catch (InterruptedException ie) {} Utils.log2("... thread died: " + t[i]); } } catch (Exception e) { IJError.print(e); } finally { Utils.showProgress(1); } }}.start(); } public boolean isActive() { return worker.isWorking(); } public Worker getWorker() { return worker; } /** Add a task to run after the Worker has finished or quit. Does not accept more tasks once the Worker no longer runs. */ public boolean addPostTask(final Runnable task) { if (worker.hasQuitted() || null == post_tasks) return false; synchronized (post_tasks) { this.post_tasks.add(task); } return true; } }