/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 gobblin.util.executors; import java.util.Iterator; import java.util.List; 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; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.util.concurrent.Futures; import gobblin.util.Either; import gobblin.util.ExecutorsUtils; import javax.annotation.Nullable; import lombok.extern.slf4j.Slf4j; /** * Executes tasks in an {@link Iterator}. Tasks need not be generated until they can be executed. * @param <T> */ @Slf4j public class IteratorExecutor<T> { private final CompletionService<T> completionService; private final int numThreads; private final ExecutorService executor; private final Iterator<Callable<T>> iterator; private boolean executed; public IteratorExecutor(Iterator<Callable<T>> runnableIterator, int numThreads, ThreadFactory threadFactory) { this.numThreads = numThreads; this.iterator = runnableIterator; this.executor = ExecutorsUtils.loggingDecorator(Executors.newFixedThreadPool(numThreads, threadFactory)); this.completionService = new ExecutorCompletionService<>(this.executor); this.executed = false; } /** * Execute the tasks in the task {@link Iterator}. Blocks until all tasks are completed. * * <p> * Note: this method only guarantees tasks have finished, not that they have finished successfully. It is the caller's * responsibility to verify the returned futures are successful. Also see {@link #executeAndGetResults()} for different * semantics. * </p> * * @return a list of completed futures. * @throws InterruptedException */ public List<Future<T>> execute() throws InterruptedException { List<Future<T>> futures = Lists.newArrayList(); try { if (this.executed) { throw new RuntimeException(String.format("This %s has already been executed.", IteratorExecutor.class.getSimpleName())); } int activeTasks = 0; while (this.iterator.hasNext()) { try { futures.add(this.completionService.submit(this.iterator.next())); activeTasks++; } catch (Exception exception) { // if this.iterator.next fails, add an immediate fail future futures.add(Futures.<T>immediateFailedFuture(exception)); } if (activeTasks == this.numThreads) { this.completionService.take(); activeTasks--; } } while (activeTasks > 0) { this.completionService.take(); activeTasks--; } } finally { ExecutorsUtils.shutdownExecutorService(this.executor, Optional.of(log), 10, TimeUnit.SECONDS); this.executed = true; } return futures; } /** * Execute the tasks in the task {@link Iterator}. Blocks until all tasks are completed. Gets the results of each * task, and for each task returns either its result or the thrown {@link ExecutionException}. * * @return a list containing for each task either its result or the {@link ExecutionException} thrown, in the same * order as the input {@link Iterator}. * @throws InterruptedException */ public List<Either<T, ExecutionException>> executeAndGetResults() throws InterruptedException { List<Either<T, ExecutionException>> results = Lists.newArrayList(); List<Future<T>> futures = execute(); for (Future<T> future : futures) { try { results.add(Either.<T, ExecutionException>left(future.get())); } catch (ExecutionException ee) { results.add(Either.<T, ExecutionException>right(ee)); } } return results; } /** * Utility method that checks whether all tasks succeeded from the output of {@link #executeAndGetResults()}. * @return true if all tasks succeeded. */ public static <T> boolean verifyAllSuccessful(List<Either<T, ExecutionException>> results) { return Iterables.all(results, new Predicate<Either<T, ExecutionException>>() { @Override public boolean apply(@Nullable Either<T, ExecutionException> input) { return input instanceof Either.Left; } }); } /** * Log failures in the output of {@link #executeAndGetResults()}. * @param results output of {@link #executeAndGetResults()} * @param useLogger logger to log the messages into. * @param atMost will log at most this many errors. */ public static <T> void logFailures(List<Either<T, ExecutionException>> results, Logger useLogger, int atMost) { Logger actualLogger = useLogger == null ? log : useLogger; Iterator<Either<T, ExecutionException>> it = results.iterator(); int printed = 0; while (it.hasNext()) { Either<T, ExecutionException> nextResult = it.next(); if (nextResult instanceof Either.Right) { ExecutionException exc = ((Either.Right<T, ExecutionException>) nextResult).getRight(); actualLogger.error("Iterator executor failure.", exc); printed++; if (printed >= atMost) { return; } } } } }