/** * Copyright 2010 JBoss Inc * * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.drools.concurrent; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * This class wraps up an externally managed executor service, meaning that the * life cycle of the service is not managed by Drools. So, we intercept calls to * shutdown() and shutdownNow() to not shutdown the external pool. Also, we need * to maintain a list of tasks submitted to the external pool, so that they can * be properly cancelled on a shutdown. * * @author etirelli */ public class ExternalExecutorService implements java.util.concurrent.ExecutorService { // this is an atomic reference to avoid additional locking private AtomicReference<ExecutorService> delegate; // the instance responsible for tracking tasks that still need to be // executed private TaskManager taskManager; // guarded by lock private boolean shutdown; private ReentrantLock lock; private Condition isShutdown; public ExternalExecutorService(java.util.concurrent.ExecutorService delegate) { this.delegate = new AtomicReference<ExecutorService>(delegate); this.shutdown = false; this.lock = new ReentrantLock(); this.isShutdown = this.lock.newCondition(); this.taskManager = new TaskManager(); } public void waitUntilEmpty() { this.taskManager.waitUntilEmpty(); } /** * Always returns true, if a shutdown was requested, since the life cycle of * this executor is externally maintained. */ public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { try { lock.lockInterruptibly(); if (!this.shutdown) { isShutdown.await(); } return shutdown; } finally { lock.unlock(); } } /** * {@inheritDoc} */ public void execute(Runnable command) { ExecutorService service = delegate.get(); if (service != null) { service.execute(taskManager.trackTask(command)); return; } throw new RejectedExecutionException( "Execution service is terminated. No more tasks can be executed."); } /** * {@inheritDoc} */ public List invokeAll(Collection tasks, long timeout, TimeUnit unit) throws InterruptedException { ExecutorService service = delegate.get(); if (service != null) { return service.invokeAll(taskManager.trackTasks(tasks), timeout, unit); } throw new RejectedExecutionException( "Execution service is terminated. No more tasks can be executed."); } /** * {@inheritDoc} */ public List invokeAll(Collection tasks) throws InterruptedException { ExecutorService service = delegate.get(); if (service != null) { return service.invokeAll(taskManager.trackTasks(tasks)); } throw new RejectedExecutionException( "Execution service is terminated. No more tasks can be executed."); } /** * {@inheritDoc} */ public Object invokeAny(Collection tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { ExecutorService service = delegate.get(); if (service != null) { // since the tasks are either executed or cancelled, there is no // need to track them return service.invokeAny(tasks, timeout, unit); } throw new RejectedExecutionException( "Execution service is terminated. No more tasks can be executed."); } /** * {@inheritDoc} */ public Object invokeAny(Collection tasks) throws InterruptedException, ExecutionException { ExecutorService service = delegate.get(); if (service != null) { // since the tasks are either executed or cancelled, there is no // need to track them return service.invokeAny(tasks); } throw new RejectedExecutionException( "Execution service is terminated. No more tasks can be executed."); } /** * {@inheritDoc} */ public boolean isShutdown() { lock.lock(); try { return shutdown; } finally { lock.unlock(); } } /** * {@inheritDoc} */ public boolean isTerminated() { lock.lock(); try { // for an externally managed service, shutdown and terminated have // the same semantics return shutdown; } finally { lock.unlock(); } } /** * {@inheritDoc} */ public void shutdown() { lock.lock(); try { shutdown = true; delegate.set(null); taskManager.cleanUpTasks(); isShutdown.signalAll(); } finally { lock.unlock(); } } /** * {@inheritDoc} */ public List<Runnable> shutdownNow() { shutdown(); // not possible to return a proper list of not executed tasks return Collections.emptyList(); } /** * {@inheritDoc} */ public <T> Future<T> submit(Callable<T> task) { ExecutorService service = delegate.get(); if (service != null) { return service.submit(taskManager.trackTask(task)); } throw new RejectedExecutionException( "Execution service is terminated. No more tasks can be executed."); } /** * {@inheritDoc} */ public <T> Future<T> submit(Runnable task, T result) { ExecutorService service = delegate.get(); if (service != null) { return service.submit(taskManager.trackTask(task), result); } throw new RejectedExecutionException( "Execution service is terminated. No more tasks can be executed."); } /** * {@inheritDoc} */ public Future<?> submit(Runnable task) { ExecutorService service = delegate.get(); if (service != null) { return service.submit(taskManager.trackTask(task)); } throw new RejectedExecutionException( "Execution service is terminated. No more tasks can be executed."); } /** * Interface that defines the methods to be implemented by a task observer. * These methods are called whenever the observable task starts executing, * finishes executing, or raises a Throwable exception. * * @author etirelli */ protected static interface TaskObserver { public void beforeTaskStarts(Runnable task, Thread thread); public void beforeTaskStarts(Callable<?> task, Thread thread); public void afterTaskFinishes(Runnable task, Thread thread); public void afterTaskFinishes(Callable<?> task, Thread thread); public void taskExceptionRaised(Runnable task, Thread thread, Throwable t); } /** * An implementation of the TaskObserver interface that keeps a map of * submitted, but not executed tasks. Whenever one of the ObservableTasks is * executed, it is removed from the map. * * @author etirelli */ protected static class TaskManager implements TaskObserver { // maps Task->ObservableTask private final Map<Object, ObservableTask> tasks; private Lock lock = new ReentrantLock(); private Condition empty = lock.newCondition(); public TaskManager() { this.tasks = new ConcurrentHashMap<Object, ObservableTask>(); } public void waitUntilEmpty() { // System.out.println("Will wait for empty..."); lock.lock(); try { if (!tasks.isEmpty()) { // System.out.println("Not empty yet..."); try { // wait until it is empty empty.await(); // System.out.println("it is now"); } catch (InterruptedException e) { // System.out.println("interruped..."); Thread.currentThread().interrupt(); } } else { // System.out.println("Already empty..."); } } finally { lock.unlock(); } } public void cleanUpTasks() { for (ObservableTask task : tasks.values()) { task.cancel(); } tasks.clear(); } /** * Creates an ObservableRunnable instance for the given task and tracks * the task execution * * @param task * the task to track * * @return the observable instance of the given task */ public Runnable trackTask(Runnable task) { // System.out.println("Tracking task = "+System.identityHashCode( // task )+" : "+task); ObservableRunnable obs = new ObservableRunnable(task, this); tasks.put(task, obs); return obs; } /** * Creates an ObservableCallable<T> instance for the given task and * tracks the task execution * * @param task * the task to track * * @return the observable instance of the given task */ public <T> Callable<T> trackTask(Callable<T> task) { ObservableCallable<T> obs = new ObservableCallable<T>(task, this); tasks.put(task, obs); return obs; } /** * Creates an ObservableCallable<T> instance for each of the given taks * and track their execution * * @param tasksToTrack * the collection of tasks to track * * @return the collection of ObservableCallable<T> tasks */ public Collection trackTasks( Collection tasksToTrack) { Collection results = new ArrayList( tasksToTrack.size()); for (Callable<Runnable> task : (Collection<Callable<Runnable>>)tasksToTrack) { results.add(trackTask(task)); } return results; } public void afterTaskFinishes(Runnable task, Thread thread) { lock.lock(); try { // System.out.println("Task finished = "+System.identityHashCode( // task )+" : "+task); this.tasks.remove(task); if (this.tasks.isEmpty()) { empty.signalAll(); } } finally { lock.unlock(); } } public void afterTaskFinishes(Callable<?> task, Thread thread) { lock.lock(); try { this.tasks.remove(task); if (this.tasks.isEmpty()) { empty.signalAll(); } } finally { lock.unlock(); } } public void beforeTaskStarts(Runnable task, Thread thread) { // nothing to do for now } public void beforeTaskStarts(Callable<?> task, Thread thread) { // nothing to do for now } public void taskExceptionRaised(Runnable task, Thread thread, Throwable t) { // nothing to do for now } } /** * A super interface for ObservableTasks * * @author etirelli */ protected static interface ObservableTask { public static enum TaskType { CALLABLE, RUNNABLE } /** * Returns the type of this ObservableTask: either RUNNABLE or CALLABLE * * @return */ public TaskType getType(); /** * Prevents the execution of the ObservableTask if it did not started * executing yet. */ public void cancel(); } /** * This class is a wrapper around a Runnable task that will notify a * listener when the task starts executing and when it finishes executing. * * @author etirelli */ protected static final class ObservableRunnable implements Runnable, ObservableTask { private final Runnable delegate; private final TaskObserver handler; private volatile boolean cancel; public ObservableRunnable(Runnable delegate, TaskObserver handler) { this.delegate = delegate; this.handler = handler; this.cancel = false; } public void run() { if (!cancel) { try { handler.beforeTaskStarts(delegate, Thread.currentThread()); delegate.run(); } catch (Throwable t) { handler.taskExceptionRaised(delegate, Thread .currentThread(), t); } finally { handler.afterTaskFinishes(delegate, Thread.currentThread()); } } } public TaskType getType() { return TaskType.RUNNABLE; } public void cancel() { this.cancel = true; } } /** * This class is a wrapper around a Callable<V> task that will notify a * listener when the task starts executing and when it finishes executing. * * @author etirelli */ protected static final class ObservableCallable<V> implements Callable<V>, ObservableTask { private final Callable<V> delegate; private final TaskObserver handler; private volatile boolean cancel; public ObservableCallable(Callable<V> delegate, TaskObserver handler) { this.delegate = delegate; this.handler = handler; } public V call() throws Exception { if (!cancel) { try { handler.beforeTaskStarts(delegate, Thread.currentThread()); V result = delegate.call(); return result; } finally { handler.afterTaskFinishes(delegate, Thread.currentThread()); } } return null; } public TaskType getType() { return TaskType.CALLABLE; } public void cancel() { this.cancel = true; } } }