/***************************************************************************
* Copyright (c) 2012-2013 VMware, Inc. All Rights Reserved.
* 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.vmware.aurora.composition.concurrent;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.Semaphore;
import javax.annotation.CheckReturnValue;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import com.google.gson.internal.Pair;
import com.vmware.aurora.util.AuAssert;
/**
* A helper class that provides methods to execute multiple stored procedures in
* parallel by pooled threads.
*
* @author Xin Li (xinli)
*
*/
@ThreadSafe
public class Scheduler {
public interface ProgressCallback {
/**
* Execution progress update callback interface. When a stored procedure
* is finished (either successfully or unsuccessfully), this callback
* method will be called with three arguments. This method should return
* as soon as possible.
*
* @param sp
* The stored procedure finished
* @param result
* Execution result
* @param compensate
* Whether the stored procedure is a compensation stored
* procedure
* @param total
* If <code>compensate</code> is false, <code>total</code> is
* the number of stored procedures submitted with the stored
* procedure just finished; otherwise, <code>total</code> is the
* number of compensation stored procedures to execute, and
* might be less than the number of compensation stored
* procedures submitted with the compensation stored procedure
* just finished.
*/
public void progressUpdate(Callable<Void> sp, ExecutionResult result,
boolean compensate, int total);
}
private static PriorityThreadPoolExecutor executor = null;
private static class StoredProcedureCallable implements Callable<Void> {
private final Callable<Void> sp;
private final Semaphore semaphore;
private final ConcurrentLinkedQueue<Integer> queue;
private final int idx;
private StoredProcedureCallable(Callable<Void> sp, Semaphore semaphore,
ConcurrentLinkedQueue<Integer> queue, int idx) {
this.sp = sp;
this.semaphore = semaphore;
this.queue = queue;
this.idx = idx;
}
@Override
public Void call() throws Exception {
try {
sp.call();
} finally {
queue.add(Integer.valueOf(idx));
semaphore.release();
}
return null;
}
}
/**
* Initilize the scheduler and its thread pool. Must be called before
* submitting first task.
*
* @param poolSize
*/
@GuardedBy("this")
synchronized public static void init(int... poolSize) {
if (executor == null) {
executor = new PriorityThreadPoolExecutor(poolSize);
}
}
/**
* Attempts to terminate all threads in the pool.
*
* @param immediate
* if true, all executing threads will be interrupted; otherwise
* wait them to finish
*
*/
@GuardedBy("this")
synchronized public static void shutdown(boolean immediate) {
if (executor == null)
return;
if (immediate) {
executor.shutdownNow();
} else {
executor.shutdown();
}
executor = null;
}
private static ExecutionResult[] executeStoredProcedures(Priority priority,
Callable<Void>[] storedProcedures, ProgressCallback callback,
boolean compensate) throws InterruptedException {
AuAssert.check(storedProcedures != null && storedProcedures.length > 0);
int len = storedProcedures.length;
ExecutionResult[] ret = new ExecutionResult[len];
@SuppressWarnings("unchecked")
Future<Void>[] futures = new Future[len];
Semaphore semaphore = new Semaphore(0);
ConcurrentLinkedQueue<Integer> queue =
new ConcurrentLinkedQueue<Integer>();
int total = 0;
for (int i = 0; i < len; ++i) {
if (storedProcedures[i] != null) {
futures[i] =
executor.submit(priority, new StoredProcedureCallable(
storedProcedures[i], semaphore, queue, i));
++total;
}
}
int finished = 0;
while (finished < total) {
semaphore.acquire();
++finished;
int idx = queue.remove().intValue();
Throwable throwable = null;
try {
futures[idx].get();
} catch (ExecutionException ex) {
throwable = ex.getCause();
}
ret[idx] = new ExecutionResult(true, throwable);
if (callback != null) {
callback.progressUpdate(storedProcedures[idx], ret[idx],
compensate, total);
}
}
for (int i = 0; i < len; ++i) {
if (futures[i] == null) {
ret[i] = new ExecutionResult(false, null);
}
}
return ret;
}
/**
* Submit stored procedures to execute. Each stored procedure will be
* executed in a separate transaction ({@link Transaction}) by a thread in
* the thread pool.
*
* @param priority
* Task priority
* @param storedProcedures
* Tasks to execute.
* @param callback
* Progress update callback, might be null.
* @return An array of <tt>ExecutionResult</tt> that encapsulates execution
* result. The length of the array is the same as input parameter
* <tt>storedProcedures</tt>, and the execution result of the stored
* procedure in the array <tt>storeProcedures</tt> can be fetched in
* the return array using the same subscript.
* @throws InterruptedException
* if execution is interrupted by another thread
*/
@CheckReturnValue
public static ExecutionResult[] executeStoredProcedures(Priority priority,
Callable<Void>[] storedProcedures, ProgressCallback callback)
throws InterruptedException {
return executeStoredProcedures(priority, storedProcedures, callback,
false);
}
/**
* Submit stored procedures to execute. Each stored procedure will be
* executed in a separate transaction ({@link Transaction}) by a thread in
* the thread pool.
*
* If the total number of failed stored procedures exceeds
* <tt>numberOfFailuresAllowed</tt>, the corresponding compensation stored
* procedures will be executed in a new transaction.
*
* @param priority
* Task priority
* @param storedProcedures
* Stored procedures to execute and their corresponding
* compensation stored procedures. The stored procedures are the
* first element of the array element, and the second element of
* each array element is the corresponding compensation stored
* procedure for the first element. The compensation stored
* procedures will be executed if the total number of failed store
* procedures exceeds <tt>numberOfFailuresAllowed</tt>, even for
* failed stored procedures.
* @param numberOfFailuresAllowed
* Number of failed stored procedures that are allowed, otherwise,
* the compensation stored procedures will be executed, even for
* failed stored procedures.
* @param callback
* Progress update callback, might be null.
* @return An array of pair of two <tt>ExecutionResult</tt>, the first is for
* stored procedure, and the other is for the corresponding
* compensation stored procedure, both are not null, but the
* <tt>finished</tt> part of second might be false, if the
* compensation stored procedure is not executed.
* @throws InterruptedException
* if execution is interrupted by another thread
*/
@SuppressWarnings("unchecked")
@CheckReturnValue
public static Pair<ExecutionResult, ExecutionResult>[] executeStoredProcedures(
Priority priority,
Pair<? extends Callable<Void>, ? extends Callable<Void>>[] storedProcedures,
int numberOfFailuresAllowed, ProgressCallback callback)
throws InterruptedException {
AuAssert.check(storedProcedures != null && storedProcedures.length > 0
&& numberOfFailuresAllowed >= 0
&& numberOfFailuresAllowed <= storedProcedures.length);
int len = storedProcedures.length;
Pair<ExecutionResult, ExecutionResult>[] ret = new Pair[len];
Callable<Void>[] forwardExecution = new Callable[len], rollbackExecution =
null;
ExecutionResult[] forwardExecutionResult = null, rollbackExecutionResult =
null;
for (int i = 0; i < len; ++i) {
forwardExecution[i] = storedProcedures[i].first;
}
forwardExecutionResult =
executeStoredProcedures(priority, forwardExecution, callback, false);
boolean compensateAll =
((len - Util.getNumberOfSuccessfulExecution(forwardExecutionResult)) > numberOfFailuresAllowed);
rollbackExecution = new Callable[len];
for (int i = 0; i < len; ++i) {
// We should compensate only when <code>compensateAll</tt> is true
// but since transaction layer now won't rollback a failed transaction automatically,
// so we have to compensate failed transactions by ourselves.
if (compensateAll || forwardExecutionResult[i].throwable != null) {
rollbackExecution[i] = storedProcedures[i].second;
}
}
// Call compensation stored procedures
rollbackExecutionResult =
executeStoredProcedures(priority, rollbackExecution, callback, true);
for (int i = 0; i < len; ++i) {
ret[i] =
new Pair<ExecutionResult, ExecutionResult>(
forwardExecutionResult[i], rollbackExecutionResult[i]);
}
return ret;
}
}