/** * */ package org.openntf.domino.thread; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Observable; import java.util.Observer; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.Delayed; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.FutureTask; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RunnableFuture; import java.util.concurrent.RunnableScheduledFuture; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.logging.Level; import java.util.logging.Logger; import javolution.util.FastMap; import org.openntf.domino.annotations.Incomplete; import org.openntf.domino.events.IDominoListener; import org.openntf.domino.utils.Factory; /** * A ThreadPoolExecutor for Domino runnables. It sets up a shutdown hook for proper termination. * * * The <code>DominoExecutor.schedule</code> schedules a Runnable or a Callable for execution. The Runnable/Callable is wrapped in a * {@link WrappedRunnable} (i.e. {@link WrappedCallable}) which is responsible for proper Thread setUp/tearDown.<br> * * The Wrapped Runnable is wrapped again in a {@link DominoFutureTask} which observes the Runnable and keeps track of some status * information.<br> * * * <b>This class should not be used directly. Use XotsDaemon.getInstance() instead</b> * * @author Nathan T. Freeman * @author Roland Praml */ @Incomplete public abstract class AbstractDominoExecutor extends ScheduledThreadPoolExecutor implements XotsExecutorService { public enum TaskState { /** The Task is Queued and will be executed next */ QUEUED, /** The Task is sleeping and will be executed further */ SLEEPING, /** The Task is currently running */ RUNNING, /** The Task is finished */ DONE, ERROR } private static final Logger log_ = Logger.getLogger(AbstractDominoExecutor.class.getName()); /** This list contains ALL tasks */ protected Map<Long, DominoFutureTask<?>> tasks = new FastMap<Long, DominoFutureTask<?>>().atomic(); @SuppressWarnings("unused") private Set<IDominoListener> listeners_; private static final AtomicLong sequencer = new AtomicLong(0L); protected Calendar now_ = Calendar.getInstance(); // the shutdown-hook for proper termination protected Runnable shutdownHook = new Runnable() { @Override public void run() { shutdownNow(); Factory.removeShutdownHook(shutdownHook); try { for (int i = 5; i > 0; i--) { if (!awaitTermination(10, TimeUnit.SECONDS)) { if (i > 0) { Factory.println("Could not terminate java threads... Still waiting " + (i * 10) + " seconds"); } else { Factory.println("Could not terminate java threads... giving up. Server may crash now."); } } } } catch (InterruptedException e) { e.printStackTrace(); } } }; private String executorName_; protected Calendar getNow() { now_.clear(); return now_; } private static ThreadFactory createThreadFactory() { try { return Executors.privilegedThreadFactory(); } catch (Throwable t) { log_.log(Level.WARNING, "cannot create a privilegedThreadFactory - this is the case if you run as java app or in an unsupported operation: " + t.toString(), t); return Executors.defaultThreadFactory(); } } /** * Creates a new {@link AbstractDominoExecutor}. Specify the * */ public AbstractDominoExecutor(final int corePoolSize, final String executorName) { super(corePoolSize, createThreadFactory()); executorName_ = executorName; Factory.addShutdownHook(shutdownHook); } /** * A FutureTask for {@link WrappedCallable}s and {@link WrappedRunnable}s. It is nearly identical with * {@link ScheduledThreadPoolExecutor.ScheduledFutureTask} But ScheduledFutureTask is private, so that we cannot inherit * * @author Roland Praml, FOCONIS AG */ public class DominoFutureTask<T> extends FutureTask<T> implements Delayed, RunnableScheduledFuture<T>, Observer { // the period. Values > 0: fixed rate. Values < 0: fixed delay private IWrappedTask wrappedTask; // private long period; // The next runtime; // private long time = 0; private Scheduler scheduler; public long sequenceNumber = sequencer.incrementAndGet(); private TaskState state = TaskState.QUEUED; private Object objectState; private Thread runner; /** * Sets the new state of this Thread * * @param s */ synchronized void setState(final TaskState s) { state = s; } /** * Returns the State of this task * * @return the {@link TaskState} */ public synchronized TaskState getState() { return state; } /** * Returns the ID for this task * * @return the Id */ public long getId() { return sequenceNumber; } public DominoFutureTask(final IWrappedCallable<T> callable, final Scheduler scheduler) { super(callable); this.wrappedTask = callable; this.scheduler = scheduler; // the wrappedCallable itself is not observable, but maybe its wrapped object callable.addObserver(this); } public DominoFutureTask(final IWrappedRunnable runnable, final T result, final Scheduler scheduler) { super(runnable, result); this.wrappedTask = runnable; this.scheduler = scheduler; // the wrappedCallable itself is not observable, but maybe its wrapped object runnable.addObserver(this); } @Override public boolean isPeriodic() { return scheduler.isPeriodic(); } /** * log exception */ @Override protected void setException(Throwable t) { super.setException(t); if (t instanceof ExecutionException) { t = ((ExecutionException) t).getCause(); log_.log(Level.WARNING, "Task '" + getWrappedTask().getDescription() + "' failed: " + t.toString(), t); } else { log_.log(Level.SEVERE, "Task '" + getWrappedTask().getDescription() + "' failed: " + t.toString(), t); } } private void runPeriodic() { scheduler.eventStart(getNow()); boolean success = super.runAndReset(); if (success && (!isShutdown() || ((getContinueExistingPeriodicTasksAfterShutdownPolicy()) && (!isTerminating())))) { scheduler.eventStop(getNow()); getQueue().add(this); } } @Override public final void run() { this.runner = Thread.currentThread(); try { if (isPeriodic()) { runPeriodic(); } else { super.run(); } } finally { synchronized (this) { this.runner = null; } } } /** * Cancels the FutureTask. Tries also to cancel the inner task. If this is a periodic task, it will stop */ @Override public boolean cancel(final boolean mayInterruptIfRunning) { wrappedTask.stop(); if (super.cancel(mayInterruptIfRunning)) { return true; } if (mayInterruptIfRunning) { // unfortunately subsequent calls to cancel will return false. synchronized (this) { if (runner != null) runner.interrupt(); return true; } } return false; } @Override public long getDelay(final TimeUnit timeUnit) { long delay = scheduler.getNextExecutionTimeInMillis() - System.currentTimeMillis(); return timeUnit.convert(delay, TimeUnit.MILLISECONDS); } public long getNextExecutionTimeInMillis() { return scheduler.getNextExecutionTimeInMillis(); } @Override public int compareTo(final Delayed other) { long delta = 0; if (other instanceof DominoFutureTask<?>) { delta = getNextExecutionTimeInMillis() - ((DominoFutureTask<?>) other).getNextExecutionTimeInMillis(); } else { delta = getDelay(TimeUnit.NANOSECONDS) - other.getDelay(TimeUnit.NANOSECONDS); } if (delta < 0L) return -1; if (delta > 0L) return 1; //delta == 0 - compare sequence numbers if ((other instanceof DominoFutureTask)) { if (this.sequenceNumber < ((DominoFutureTask<?>) other).sequenceNumber) { return -1; } return 1; } return 0; } @Override public String toString() { // TODO increment Period/Time return sequenceNumber + "State: " + getState() + " Task: " + wrappedTask + " objectState: " + objectState; } @Override public void update(final Observable arg0, final Object arg1) { objectState = arg1; } public IWrappedTask getWrappedTask() { return wrappedTask; } } @SuppressWarnings("unused") private long overflowFree(long paramLong) { Delayed localDelayed = (Delayed) super.getQueue().peek(); if (localDelayed != null) { long l = localDelayed.getDelay(TimeUnit.NANOSECONDS); if ((l < 0L) && (paramLong - l < 0L)) paramLong = Long.MAX_VALUE + l; } return paramLong; } // --- end duplicate stuff /** * Returns a list of all tasks sorted by next execution time or Sequence number * * @return a List of tasks */ @Override public List<DominoFutureTask<?>> getTasks(final Comparator<DominoFutureTask<?>> comparator) { ArrayList<DominoFutureTask<?>> ret = new ArrayList<DominoFutureTask<?>>(); for (DominoFutureTask<?> task : tasks.values()) { ret.add(task); } if (comparator == null) { Collections.sort(ret); } else { Collections.sort(ret, comparator); } return ret; } /** * Return a Task with the given ID. May return null if the task is no longer in the queue. * * @param id * The ID of the task to retrieve. * @return The task for the given ID, or null if the task is no longer in the queue. */ public DominoFutureTask<?> getTask(final long id) { return tasks.get(id); } @Override protected void beforeExecute(final Thread thread, final Runnable runnable) { super.beforeExecute(thread, runnable); if (runnable instanceof DominoFutureTask) { DominoFutureTask<?> task = (DominoFutureTask<?>) runnable; thread.setName(executorName_ + ": " + task.getWrappedTask().getDescription() + " - " + new Date()); task.setState(TaskState.RUNNING); } else { thread.setName(executorName_ + ": #" + thread.getId()); } } @Override protected void afterExecute(final Runnable runnable, final Throwable error) { super.afterExecute(runnable, error); if (runnable instanceof DominoFutureTask) { DominoFutureTask<?> task = (DominoFutureTask<?>) runnable; if (task.isDone()) { if (error == null) { task.setState(TaskState.DONE); } else { task.setState(TaskState.ERROR); } tasks.remove(task.sequenceNumber); } } } // @Incomplete // //these can't use the IDominoListener interface. Will need to put something new together for that. // public void addListener(final IDominoListener listener) { // if (listeners_ == null) { // listeners_ = new LinkedHashSet<IDominoListener>(); // } // listeners_.add(listener); // } // // @Incomplete // public IDominoListener removeListener(final IDominoListener listener) { // if (listeners_ != null) { // listeners_.remove(listener); // } // return listener; // } // @Override // public void shutdown() { // //Factory.removeShutdownHook(shutdownHook); // super.shutdown(); // } // // @Override // public List<Runnable> shutdownNow() { // List<Runnable> ret = super.shutdownNow(); // //Factory.removeShutdownHook(shutdownHook); // return ret; // } protected <V> RunnableScheduledFuture<V> queue(final RunnableScheduledFuture<V> future) { if (isShutdown()) { throw new RejectedExecutionException(); } if (getPoolSize() < getCorePoolSize()) { prestartCoreThread(); } if (future instanceof DominoFutureTask) { DominoFutureTask<?> dft = (DominoFutureTask<?>) future; tasks.put(dft.sequenceNumber, dft); if (dft.getDelay(TimeUnit.NANOSECONDS) > 0) { dft.setState(TaskState.SLEEPING); } } super.getQueue().add(future); return future; } @Override public <V> ScheduledFuture<V> schedule(final Callable<V> callable, final long delay, final TimeUnit timeUnit) { return queue(new DominoFutureTask<V>(wrap(callable), new PeriodicScheduler(delay, 0L, timeUnit))); } @SuppressWarnings({ "unchecked", "rawtypes" }) @Override public ScheduledFuture<?> schedule(final Runnable runnable, final long delay, final TimeUnit timeUnit) { return queue(new DominoFutureTask(wrap(runnable), null, new PeriodicScheduler(delay, 0L, timeUnit))); } @SuppressWarnings({ "unchecked", "rawtypes" }) @Override public ScheduledFuture<?> scheduleAtFixedRate(final Runnable runnable, final long delay, final long period, final TimeUnit timeUnit) { if (period <= 0) { throw new IllegalStateException("period must be > 0"); } return queue(new DominoFutureTask(wrap(runnable), null, new PeriodicScheduler(delay, period, timeUnit))); } @SuppressWarnings({ "unchecked", "rawtypes" }) @Override public ScheduledFuture<?> scheduleWithFixedDelay(final Runnable runnable, final long delay, final long period, final TimeUnit timeUnit) { if (period <= 0) { throw new IllegalStateException("period must be > 0"); } return queue(new DominoFutureTask(wrap(runnable), null, new PeriodicScheduler(delay, -period, timeUnit))); } protected abstract <V> IWrappedCallable<V> wrap(Callable<V> inner); protected abstract IWrappedRunnable wrap(Runnable inner); protected abstract IWrappedCallable<?> wrap(final String moduleName, final String className, final Object... ctorArgs); @Override public <V> ScheduledFuture<V> schedule(final Callable<V> callable, final Scheduler scheduler) { return queue(new DominoFutureTask<V>(wrap(callable), scheduler)); } @SuppressWarnings({ "unchecked", "rawtypes" }) @Override public ScheduledFuture<?> schedule(final Runnable runnable, final Scheduler scheduler) { return queue(new DominoFutureTask(wrap(runnable), null, scheduler)); } @SuppressWarnings({ "unchecked", "rawtypes" }) @Override public ScheduledFuture<?> scheduleTasklet(final String moduleName, final String className, final Scheduler scheduler, final Object... ctorArgs) { return queue(new DominoFutureTask(wrap(moduleName, className, ctorArgs), scheduler)); } @SuppressWarnings({ "rawtypes", "unchecked" }) @Override public ScheduledFuture<?> runTasklet(final String moduleName, final String className, final Object... ctorArgs) { return queue(new DominoFutureTask(wrap(moduleName, className, ctorArgs), new PeriodicScheduler(0, 0L, TimeUnit.NANOSECONDS))); } // @Override // protected <T> RunnableFuture<T> newTaskFor(final Callable<T> callable) { // return super.newTaskFor(wrap(callable)); // } // // @Override // protected <T> RunnableFuture<T> newTaskFor(final Runnable runnable, final T type) { // return super.newTaskFor(wrap(runnable), type); // }; @Override protected <T> RunnableFuture<T> newTaskFor(final Callable<T> callable) { return new DominoFutureTask<T>(wrap(callable), new PeriodicScheduler(0, 0, TimeUnit.MILLISECONDS)); } @Override protected <T> RunnableFuture<T> newTaskFor(final Runnable runnable, final T type) { return new DominoFutureTask<T>(wrap(runnable), type, new PeriodicScheduler(0, 0, TimeUnit.MILLISECONDS)); }; }