| /* |
| * Copyright (C) 2013 The Android Open Source Project |
| * |
| * 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 com.android.ide.common.internal; |
| |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Sets; |
| |
| import java.util.List; |
| import java.util.Set; |
| import java.util.concurrent.Callable; |
| import java.util.concurrent.CompletionService; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.ExecutorCompletionService; |
| import java.util.concurrent.ExecutorService; |
| import java.util.concurrent.Executors; |
| import java.util.concurrent.Future; |
| |
| /** |
| * A utility wrapper around a {@link CompletionService} using an ThreadPoolExecutor so that it |
| * is possible to wait on all the tasks. |
| * |
| * Tasks are submitted as {@link Callable} with {@link #execute(java.util.concurrent.Callable)}. |
| * |
| * After executing all tasks, it is possible to wait on them with |
| * {@link #waitForTasksWithQuickFail(boolean)}, or {@link #waitForAllTasks()}. |
| * |
| * This class is not Thread safe! |
| */ |
| public class WaitableExecutor<T> { |
| |
| private final ExecutorService mExecutorService; |
| private final CompletionService<T> mCompletionService; |
| private final Set<Future<T>> mFutureSet = Sets.newHashSet(); |
| |
| /** |
| * Creates an executor that will use at most <var>nThreads</var> threads. |
| * @param nThreads the number of threads, or zero for default count (which is number of core) |
| */ |
| public WaitableExecutor(int nThreads) { |
| if (nThreads < 1) { |
| nThreads = Runtime.getRuntime().availableProcessors(); |
| } |
| |
| mExecutorService = Executors.newFixedThreadPool(nThreads); |
| mCompletionService = new ExecutorCompletionService<T>(mExecutorService); |
| } |
| |
| /** |
| * Creates an executor that will use at most 1 thread per core. |
| */ |
| public WaitableExecutor() { |
| mExecutorService = null; |
| mCompletionService = new ExecutorCompletionService<T>(ExecutorSingleton.getExecutor()); |
| } |
| |
| /** |
| * Submits a Callable for execution. |
| * |
| * @param runnable the callable to run. |
| */ |
| public void execute(Callable<T> runnable) { |
| mFutureSet.add(mCompletionService.submit(runnable)); |
| } |
| |
| /** |
| * Waits for all tasks to be executed. If a tasks throws an exception, it will be thrown from |
| * this method inside the ExecutionException, preventing access to the result of the other |
| * threads. |
| * |
| * If you want to get the results of all tasks (result and/or exception), use |
| * {@link #waitForAllTasks()} |
| * |
| * @param cancelRemaining if true, and a task fails, cancel all remaining tasks. |
| * |
| * @return a list of all the return values from the tasks. |
| * |
| * @throws InterruptedException if this thread was interrupted. Not if the tasks were interrupted. |
| */ |
| public List<T> waitForTasksWithQuickFail(boolean cancelRemaining) throws InterruptedException, |
| LoggedErrorException { |
| List<T> results = Lists.newArrayListWithCapacity(mFutureSet.size()); |
| try { |
| while (!mFutureSet.isEmpty()) { |
| Future<T> future = mCompletionService.take(); |
| |
| assert mFutureSet.contains(future); |
| mFutureSet.remove(future); |
| |
| // Get the result from the task. If the task threw an exception, |
| // this will throw it, wrapped in an ExecutionException, caught below. |
| results.add(future.get()); |
| } |
| } catch (ExecutionException e) { |
| if (cancelRemaining) { |
| cancelAllTasks(); |
| } |
| |
| // get the original exception adn throw that one. |
| Throwable cause = e.getCause(); |
| if (cause instanceof LoggedErrorException) { |
| throw (LoggedErrorException) cause; |
| } else { |
| throw new RuntimeException(cause); |
| } |
| } finally { |
| if (mExecutorService != null) { |
| mExecutorService.shutdownNow(); |
| } |
| } |
| |
| return results; |
| } |
| |
| public static final class TaskResult<T> { |
| public T value; |
| public Throwable exception; |
| |
| static <T> TaskResult<T> withValue(T value) { |
| TaskResult<T> result = new TaskResult<T>(null); |
| result.value = value; |
| return result; |
| } |
| |
| TaskResult(Throwable cause) { |
| exception = cause; |
| } |
| } |
| |
| /** |
| * Waits for all tasks to be executed, and returns a {@link TaskResult} for each, containing |
| * either the result or the exception thrown by the task. |
| * |
| * If a task is cancelled (and it threw InterruptedException) then the result for the task |
| * is *not* included. |
| * |
| * @return a list of all the return values from the tasks. |
| * |
| * @throws InterruptedException if this thread was interrupted. Not if the tasks were interrupted. |
| */ |
| public List<TaskResult<T>> waitForAllTasks() throws InterruptedException { |
| List<TaskResult<T>> results = Lists.newArrayListWithCapacity(mFutureSet.size()); |
| try { |
| while (!mFutureSet.isEmpty()) { |
| Future<T> future = mCompletionService.take(); |
| |
| assert mFutureSet.contains(future); |
| mFutureSet.remove(future); |
| |
| // Get the result from the task. |
| try { |
| results.add(TaskResult.withValue(future.get())); |
| } catch (ExecutionException e) { |
| // the original exception thrown by the task is the cause of this one. |
| Throwable cause = e.getCause(); |
| |
| //noinspection StatementWithEmptyBody |
| if (cause instanceof InterruptedException) { |
| // if the task was cancelled we probably don't care about its result. |
| } else { |
| // there was an error. |
| results.add(new TaskResult<T>(cause)); |
| } |
| } |
| } |
| } finally { |
| if (mExecutorService != null) { |
| mExecutorService.shutdownNow(); |
| } |
| } |
| |
| return results; |
| } |
| |
| /** |
| * Cancel all remaining tasks. |
| */ |
| public void cancelAllTasks() { |
| for (Future<T> future : mFutureSet) { |
| future.cancel(true /*mayInterruptIfRunning*/); |
| } |
| } |
| } |