/*
* Copyright (c) 2013-2017 Cinchapi 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 com.cinchapi.concourse.server.concurrent;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import javax.annotation.concurrent.ThreadSafe;
import com.cinchapi.concourse.time.Time;
import com.google.common.base.Throwables;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
/**
* A {@link BlockingExecutorService} is a wrapper around a normal
* ExecutorService that allows caller threads to specify a batch of tasks to
* complete before proceeding.
* <p>
* This service is designed to be used by multiple concurrent threads. While
* each call to the {@link #execute(Callable...) execute} methods will block the
* calling thread until all the specified tasks have completed, there are no
* guarantees made about the order in which tasks will execute. That is because
* all tasks execute asynchronously (just as they would in most other
* ExecutorServices), but this wrapper has logic to make the calling thread
* block until all of the tasks it cares about have executed.
* </p>
* <p>
* Since tasks execute asynchronously, threads do not interfere with one
* another. It is possible for thread A to submit tasks to the service before
* thread B but have thread B's tasks finish before thread A's.
* </p>
* <p>
* <strong>NOTE:</strong> All the threads used by this service are daemon
* threads, so they don't need to be stopped explicitly. The downside to this is
* that it is possible for the JVM to shutdown while this service is in the
* middle of executing task. This shouldn't be a problem in reality, since
* calling threads block while tasks are executing.
* </p>
*
* @author Jeff Nelson
*/
@ThreadSafe
public class BlockingExecutorService {
/**
* Return a new {@link BlockingExecutorService} that uses a system specified
* thread name prefix.
*
* @return the BlockingExecutorService
*/
public static BlockingExecutorService create() {
return create("blocking-executor-service-" + Time.now());
}
/**
* Return a new {@link BlockingExecutorService} that uses the specified
* {@code threadNamePrefix}.
*
* @param threadNamePrefix
* @return the BlockingExecutorService
*/
public static BlockingExecutorService create(String threadNamePrefix) {
return new BlockingExecutorService(threadNamePrefix);
}
/**
* Spin (and therefore block the current thread) until all the tasks
* represented by the {@code futures} are done and the results are
* available.
*
* @param futures
*/
private static void waitForCompletion(Future<?>... futures) {
for (Future<?> future : futures) {
try {
future.get();
}
catch (InterruptedException | ExecutionException e) {
throw Throwables.propagate(e);
}
}
}
/**
* The underlying Executor that actually accomplishes task execution.
*/
private final ExecutorService executor;
/**
* Construct a new instance.
*
* @param threadNamePrefix
*/
private BlockingExecutorService(String threadNamePrefix) {
this.executor = Executors
.newCachedThreadPool(new ThreadFactoryBuilder().setDaemon(true)
.setNameFormat(threadNamePrefix + "-%d").build());
}
/**
* Execute all the tasks in the {@code batch} and block until all of them
* complete. Afterwards, return the {@link Future} objects that represent
* the results of execution. The results are returned in the same order that
* the tasks are specified.
* <p>
* Please note that this method will block the calling thread until all the
* tasks in the batch have completed, but it has no affect on other threads.
* Therefore, it is possible that another thread can submit a batch of tasks
* to the service and have them finish before the current thread's task do.
* </p>
*
* @param batch
* @return the Future results of each task in the batch
*/
public List<Future<?>> execute(Callable<?>... batch) {
Future<?>[] futures = new Future<?>[batch.length];
for (int i = 0; i < futures.length; ++i) {
futures[i] = executor.submit(batch[i]);
}
waitForCompletion(futures);
return Lists.newArrayList(futures);
}
/**
* Execute all the tasks in the {@code batch} and block until all of them
* complete.
* <p>
* Please note that this method will block the calling thread until all the
* tasks in the batch have completed, but it has no affect on other threads.
* Therefore, it is possible that another thread can submit a batch of tasks
* to the service and have them finish before the current thread's task do.
* </p>
*
* @param batch
*/
public void execute(Runnable... batch) {
Future<?>[] futures = new Future<?>[batch.length];
for (int i = 0; i < futures.length; ++i) {
futures[i] = executor.submit(batch[i]);
}
waitForCompletion(futures);
}
}