com.sri.ai.util.concurrent.BranchAndMerge Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aic-util Show documentation
Show all versions of aic-util Show documentation
SRI International's AIC Utility Library (for Java 1.6+)
/*
* Copyright (c) 2013, SRI International
* All rights reserved.
* Licensed under the The BSD 3-Clause License;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://opensource.org/licenses/BSD-3-Clause
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* Neither the name of the aic-util nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.sri.ai.util.concurrent;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import com.google.common.annotations.Beta;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.sri.ai.util.AICUtilConfiguration;
/**
* A general purpose utility service for simplifying performing concurrent
* execution of work that can be split into sub-tasks or into smaller pieces
* recursively (i.e. very similar to JDK 7's Fork/Join ExecutorService concept
* but without explicitly detailing the mechanism - Note: may switch to using
* this internally in a later iteration).
*
* @author oreilly
*
*/
@Beta
public class BranchAndMerge {
// Used to identify worker threads used by the Branch And Merge utility
// service.
private static final String _threadIdentifierPrefix = "Branch-And-Merge-";
private static int _sharedExecutorNumberWorkerThreads = 0;
private static AtomicInteger _sharedExecutorActiveWorkerThreads = new AtomicInteger();
// Note: _sharedExecutorService must be initialized after everything else.
private static ListeningExecutorService _sharedExecutorService = newExecutorService();
/**
* The result of branching and then merging the results from a collection of
* Callables.
*
* @author oreilly
*
* @param
* the type of the result to be returned when all the Callable
* results are merged together.
*/
public static interface Result {
/**
*
* @return true if a failure occurred when calling a collection of
* Callable objects.
*/
boolean failureOccurred();
/**
*
* @return the result from branching and then merging a collection of
* Callable objects.
*/
T getResult();
}
/**
* Reset the shared Branch and Merge Utility service (i.e. drop and reset up
* any worker threads it is configured to use.).
*
* Note: Only call this method at a safe point in your logic (i.e.
* when no other thread should be using this service).
*/
public static void reset() {
// If currently up, shut it down before resetting it up.
if (_sharedExecutorService != null) {
_sharedExecutorService.shutdown();
}
_sharedExecutorService = newExecutorService();
}
/**
* Branch and merge call, the results from calling a list of tasks are
* collected into a list (order of results match those of the tasks).
*
* @param tasks
* a list of tasks that are to be branched.
* @return a list of the results returned by the branched tasks. The
* ordering of the results will correspond to the ordering of the
* tasks that generated them (null results are allowed, either
* intentionally or due to a failure when calling a task).
* @param the type of the results.
*/
public static Result> execute(List extends Callable> tasks) {
return execute(tasks, new NoResultsTransform());
}
/**
* Branch and merge call, the results from calling a list of tasks are
* collected into a list (order of results match those of the tasks).
*
* @param tasks
* a list of tasks that are to be branched.
* @param cancelOutstandingOnSuccess
* a predicate that returns true if all other outstanding tasks
* should be cancelled if a task returns a specified success
* value (i.e. can be used for short circuiting logic - e.g. when
* dealing with conjunctions or disjunctions of tasks).
* @return a list of the results returned by the branched tasks. The
* ordering of the results will correspond to the ordering of the
* tasks that generated them (null results are allowed, either
* intentionally or due to a failure when calling a task).
* @param the type of the results.
*/
public static Result> execute(
List extends Callable> tasks,
Predicate cancelOutstandingOnSuccess) {
return execute(tasks, cancelOutstandingOnSuccess,
new NoResultsTransform());
}
/**
* Branch and merge call, the results from calling a list of tasks are
* collected into a list (order of results match those of the tasks).
*
* @param tasks
* a list of tasks that are to be branched.
* @param cancelOutstandingOnSuccess
* a predicate that returns true if all other outstanding tasks
* should be cancelled if a task returns a specified success
* value (i.e. can be used for short circuiting logic - e.g. when
* dealing with conjunctions or disjunctions of tasks).
* @param cancelOutstandingOnFailure
* a predicate that returns true if all outstanding tasks should
* be cancelled if an failure occurs when executing one of the
* tasks.
* @return a list of the results returned by the branched tasks. The
* ordering of the results will correspond to the ordering of the
* tasks that generated them (null results are allowed, either
* intentionally or due to a failure when calling a task).
* @param the type of the results.
*/
public static Result> execute(
List extends Callable> tasks,
Predicate cancelOutstandingOnSuccess,
Predicate cancelOutstandingOnFailure) {
return execute(tasks, cancelOutstandingOnSuccess,
cancelOutstandingOnFailure, new NoResultsTransform());
}
/**
* Branch and merge call, the results from calling a list of tasks are
* passed thru to a transformation function that will create a desired merge
* result for the call.
*
* @param tasks
* a list of tasks that are to be branched.
* @param transformResults
* a function that will be called with a list of all of the
* results returned by the branched tasks (order will match that
* of the tasks themselves). This function should be able to
* handle null results as these can be legal, either
* intentionally returned by the task or as the result of a
* failure when calling the task.
* @return the result returned from applying transformResults on the list of
* results collected from the branched tasks.
* @param the type of the result.
* @param the type of the transformed result.
*/
public static Result execute(List extends Callable> tasks,
Function, T> transformResults) {
return execute(tasks, new CancelOutstandingOnSuccess(false),
transformResults);
}
/**
* Branch and merge call, the results from calling a list of tasks are
* passed thru to a transformation function that will create a desired merge
* result for the call.
*
* @param tasks
* a list of tasks that are to be branched.
* @param cancelOutstandingOnSuccess
* a predicate that returns true if all other outstanding tasks
* should be cancelled if a task returns a specified success
* value (i.e. can be used for short circuiting logic - e.g. when
* dealing with conjunctions or disjunctions of tasks).
* @param transformResults
* a function that will be called with a list of all of the
* results returned by the branched tasks (order will match that
* of the tasks themselves). This function should be able to
* handle null results as these can be legal, either
* intentionally returned by the task or as the result of a
* failure when calling the task.
* @return the result returned from applying transformResults on the list of
* results collected from the branched tasks.
* @param the type of the result.
* @param the type of the transformed result.
*/
public static Result execute(List extends Callable> tasks,
Predicate cancelOutstandingOnSuccess,
Function, T> transformResults) {
return execute(tasks, cancelOutstandingOnSuccess,
new CancelOutstandingOnFailure(true), transformResults);
}
/**
* Branch and merge call, the results from calling a list of tasks are
* passed thru to a transformation function that will create a desired merge
* result for the call.
*
* @param tasks
* a list of tasks that are to be branched.
* @param cancelOutstandingOnSuccess
* a predicate that returns true if all other outstanding tasks
* should be cancelled if a task returns a specified success
* value (i.e. can be used for short circuiting logic - e.g. when
* dealing with conjunctions or disjunctions of tasks).
* @param cancelOutstandingOnFailure
* a predicate that returns true if all outstanding tasks should
* be cancelled if an failure occurs when executing one of the
* tasks.
* @param transformResults
* a function that will be called with a list of all of the
* results returned by the branched tasks (order will match that
* of the tasks themselves). This function should be able to
* handle null results as these can be legal, either
* intentionally returned by the task or as the result of a
* failure when calling the task.
* @return the result returned from applying transformResults on the list of
* results collected from the branched tasks.
* @param the type of the result.
* @param the type of the transformed result.
*/
public static Result execute(List extends Callable> tasks,
Predicate cancelOutstandingOnSuccess,
Predicate cancelOutstandingOnFailure,
Function, T> transformResults) {
Result result = null;
boolean executeConcurrent = true;
if (_sharedExecutorService == null) {
executeConcurrent = false;
}
else {
// Increase the # of active worker threads
int activeThreads = _sharedExecutorActiveWorkerThreads.addAndGet(tasks.size());
// If I've exceeded the number of available worker threads
// and I'm being called from one of these worker threads,
// then want to execute this sequentially on the worker
// thread. Otherwise I could very easily end up in deadlock
// situations very easily.
if (activeThreads > _sharedExecutorNumberWorkerThreads
&& Thread.currentThread().getName().startsWith(_threadIdentifierPrefix)) {
// TODO: this is currently a very simplistic/non-optimal
// approach as it doesn't leverage using a subset of
// available worker threads and completing the task in
// two parts by having some of the tasks branched and
// some of them run sequentially on the current thread.
executeConcurrent = false;
// Ensure I reduce the active worker threads as I'm actually
// going to execute sequentially on the current worker thread.
_sharedExecutorActiveWorkerThreads.addAndGet(tasks.size() * -1);
}
}
if (executeConcurrent) {
//System.out.println("BranchAndMerge Concurrent "+_sharedExecutorActiveWorkerThreads+" of "+_sharedExecutorNumberWorkerThreads + " for " + tasks.size() + " tasks at " + System.currentTimeMillis());
result = executeConcurrent(tasks, cancelOutstandingOnSuccess,
cancelOutstandingOnFailure, transformResults);
}
else {
//System.out.println("BranchAndMerge Sequential "+_sharedExecutorActiveWorkerThreads+" of "+_sharedExecutorNumberWorkerThreads + " for " + tasks.size() + " tasks at " + System.currentTimeMillis());
result = executeSequential(tasks, cancelOutstandingOnSuccess,
cancelOutstandingOnFailure, transformResults);
}
return result;
}
//
// PRIVATE METHODS
//
private static ListeningExecutorService newExecutorService() {
ListeningExecutorService result = null;
int nThreads = 0; // i.e. not allowed by default
// Running concurrently is optional.
if (AICUtilConfiguration.isBranchAndMergeThreadingEnabled()) {
if (AICUtilConfiguration
.isBranchAndMergeUseNumberProcessorsForThreadPoolSize()) {
nThreads = Runtime.getRuntime().availableProcessors();
int delta = AICUtilConfiguration
.getBranchAndMergeDeltaNumberProcessorsForThreadPoolSize();
nThreads += delta;
if (nThreads < 1) {
nThreads = 1;
}
}
else {
nThreads = AICUtilConfiguration
.getBranchAndMergeFixedThreadPoolSize();
}
}
if (nThreads > 0) {
ThreadFactory threadFactory = new ThreadFactoryBuilder()
// Do this so its simple to identify worker threads
.setNameFormat(_threadIdentifierPrefix + "%s")
// This is a service so want the worker threads
// to be Daemons so that the JVM can exit normally
// without needing any special calls to this service.
.setDaemon(true).build();
// We'll use fixed pool size for the worker threads as this
// makes it easier to determine degrading.
ExecutorService executorService = Executors.newFixedThreadPool(
nThreads, threadFactory);
result = MoreExecutors.listeningDecorator(executorService);
_sharedExecutorNumberWorkerThreads = nThreads;
_sharedExecutorActiveWorkerThreads = new AtomicInteger();
}
return result;
}
private static Result executeConcurrent(
List extends Callable> tasks,
Predicate cancelOutstandingOnSuccess,
Predicate cancelOutstandingOnFailure,
Function, T> transformResults) {
boolean failureOccurred = false;
T result = null;
try {
List> invokedFutures = _sharedExecutorService.invokeAll(tasks);
List> lfutures = new ArrayList>();
for (Future future : invokedFutures) {
lfutures.add((ListenableFuture) future);
}
CancelOutstandingCallback cancelOutstandingCallback = new CancelOutstandingCallback(
cancelOutstandingOnSuccess, cancelOutstandingOnFailure,
lfutures);
for (ListenableFuture future : lfutures) {
Futures.addCallback(future, cancelOutstandingCallback);
}
ListenableFuture> resultsFuture = Futures
.successfulAsList(lfutures);
List resultValues = resultsFuture.get();
if (cancelOutstandingCallback.failureOccurred()) {
failureOccurred = true;
// If I don't cancel outstanding on failure
// then I'll attempt to create a result
if (!cancelOutstandingOnFailure.apply(null)) {
result = transformResults.apply(resultValues);
}
}
else {
result = transformResults.apply(resultValues);
}
} catch (Throwable t) {
failureOccurred = true;
}
return new DefaultResult(failureOccurred, result);
}
private static Result executeSequential(
List extends Callable> tasks,
Predicate cancelOutstandingOnSuccess,
Predicate cancelOutstandingOnFailure,
Function, T> transformResults) {
boolean failureOccurred = false;
T result = null;
List resultValues = new ArrayList(tasks.size());
// Ensure all set to null initially
for (int i = 0; i < tasks.size(); i++) {
resultValues.add(null);
}
// Now attempt to get the values
for (int i = 0; i < tasks.size(); i++) {
try {
V value = tasks.get(i).call();
resultValues.set(i, value);
if (cancelOutstandingOnSuccess.apply(value)) {
break;
}
} catch (Throwable t) {
failureOccurred = true;
if (cancelOutstandingOnFailure.apply(t)) {
break;
}
}
}
if (failureOccurred) {
// If I don't cancel outstanding on failure
// then I'll attempt to create a result
if (!cancelOutstandingOnFailure.apply(null)) {
result = transformResults.apply(resultValues);
}
}
else {
result = transformResults.apply(resultValues);
}
return new DefaultResult(failureOccurred, result);
}
//
// PRIVATE CLASSES
//
private static class DefaultResult implements Result {
private boolean failureOccurred = false;
private T result = null;
public DefaultResult(boolean failureOccurred, T result) {
this.failureOccurred = failureOccurred;
this.result = result;
}
@Override
public boolean failureOccurred() {
return failureOccurred;
}
@Override
public T getResult() {
return result;
}
}
private static class CancelOutstandingCallback implements
FutureCallback {
private Predicate cancelOutstandingOnSuccess = null;
private Predicate cancelOutstandingOnFailure = null;
private Iterable> futures = null;
private boolean failureOccurred = false;
public CancelOutstandingCallback(
Predicate cancelOutstandingOnSuccess,
Predicate cancelOutstandingOnFailure,
Iterable> futures) {
this.cancelOutstandingOnSuccess = cancelOutstandingOnSuccess;
this.cancelOutstandingOnFailure = cancelOutstandingOnFailure;
this.futures = futures;
}
@Override
public void onSuccess(V result) {
_sharedExecutorActiveWorkerThreads.addAndGet(-1);
if (cancelOutstandingOnSuccess.apply(result)) {
cancel();
}
}
@Override
public void onFailure(Throwable t) {
_sharedExecutorActiveWorkerThreads.addAndGet(-1);
if (!(t instanceof CancellationException)) {
failureOccurred = true;
if (cancelOutstandingOnFailure.apply(t)) {
cancel();
}
}
}
public boolean failureOccurred() {
return failureOccurred;
}
//
// PRIVATE METHODS
//
private void cancel() {
for (ListenableFuture future : futures) {
if (!future.isCancelled()) {
future.cancel(true);
}
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy