org.apache.hadoop.hbase.client.ResultBoundedCompletionService Maven / Gradle / Ivy
The newest version!
/**
*
* 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 org.apache.hadoop.hbase.client;
import java.util.ArrayList;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.RunnableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.hbase.classification.InterfaceAudience;
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
import org.apache.htrace.Trace;
/**
* A completion service for the RpcRetryingCallerFactory.
* Keeps the list of the futures, and allows to cancel them all.
* This means as well that it can be used for a small set of tasks only.
*
Implementation is not Thread safe.
*
* CompletedTasks is implemented as a queue, the entry is added based on the time order. I.e,
* when the first task completes (whether it is a success or failure), it is added as a first
* entry in the queue, the next completed task is added as a second entry in the queue, ...
* When iterating through the queue, we know it is based on time order. If the first
* completed task succeeds, it is returned. If it is failure, the iteration goes on until it
* finds a success.
*/
@InterfaceAudience.Private
public class ResultBoundedCompletionService {
private static final Log LOG = LogFactory.getLog(ResultBoundedCompletionService.class);
private final RpcRetryingCallerFactory retryingCallerFactory;
private final Executor executor;
private final QueueingFuture[] tasks; // all the tasks
private final ArrayList completedTasks; // completed tasks
private volatile boolean cancelled = false;
class QueueingFuture implements RunnableFuture {
private final RetryingCallable future;
private T result = null;
private ExecutionException exeEx = null;
private volatile boolean cancelled = false;
private final int callTimeout;
private final RpcRetryingCaller retryingCaller;
private boolean resultObtained = false;
private final int replicaId; // replica id
public QueueingFuture(RetryingCallable future, int callTimeout, int id) {
this.future = future;
this.callTimeout = callTimeout;
this.retryingCaller = retryingCallerFactory.newCaller();
this.replicaId = id;
}
@SuppressWarnings("unchecked")
@Override
public void run() {
try {
if (!cancelled) {
result = this.retryingCaller.callWithRetries(future, callTimeout);
resultObtained = true;
}
} catch (Throwable t) {
exeEx = new ExecutionException(t);
} finally {
synchronized (tasks) {
// If this wasn't canceled then store the result.
if (!cancelled) {
completedTasks.add(QueueingFuture.this);
}
// Notify just in case there was someone waiting and this was canceled.
// That shouldn't happen but better safe than sorry.
tasks.notify();
}
}
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
if (resultObtained || exeEx != null) return false;
retryingCaller.cancel();
if (future instanceof Cancellable) ((Cancellable)future).cancel();
cancelled = true;
return true;
}
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public boolean isDone() {
return resultObtained || exeEx != null;
}
@Override
public T get() throws InterruptedException, ExecutionException {
try {
return get(1000, TimeUnit.DAYS);
} catch (TimeoutException e) {
throw new RuntimeException("You did wait for 1000 days here?", e);
}
}
@Override
public T get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
synchronized (tasks) {
if (resultObtained) {
return result;
}
if (exeEx != null) {
throw exeEx;
}
unit.timedWait(tasks, timeout);
}
if (resultObtained) {
return result;
}
if (exeEx != null) {
throw exeEx;
}
throw new TimeoutException("timeout=" + timeout + ", " + unit);
}
public int getReplicaId() {
return replicaId;
}
public ExecutionException getExeEx() {
return exeEx;
}
}
@SuppressWarnings("unchecked")
public ResultBoundedCompletionService(
RpcRetryingCallerFactory retryingCallerFactory, Executor executor,
int maxTasks) {
this.retryingCallerFactory = retryingCallerFactory;
this.executor = executor;
this.tasks = new QueueingFuture[maxTasks];
this.completedTasks = new ArrayList<>(maxTasks);
}
public void submit(RetryingCallable task, int callTimeout, int id) {
QueueingFuture newFuture = new QueueingFuture(task, callTimeout, id);
executor.execute(Trace.wrap(newFuture));
tasks[id] = newFuture;
}
public QueueingFuture take() throws InterruptedException {
synchronized (tasks) {
while (!cancelled && (completedTasks.size() < 1)) tasks.wait();
}
return completedTasks.get(0);
}
/**
* Poll for the first completed task whether it is a success or execution exception.
*
* @param timeout - time to wait before it times out
* @param unit - time unit for timeout
*/
public QueueingFuture poll(long timeout, TimeUnit unit) throws InterruptedException {
return pollForSpecificCompletedTask(timeout, unit, 0);
}
/**
* Poll for the first successfully completed task whose completed order is in startIndex,
* endIndex(exclusive) range
*
* @param timeout - time to wait before it times out
* @param unit - time unit for timeout
* @param startIndex - start index, starting from 0, inclusive
* @param endIndex - end index, exclusive
*
* @return If within timeout time, there is no successfully completed task, return null; If all
* tasks get execution exception, it will throw out the last execution exception,
* otherwise return the first successfully completed task's result.
*/
public QueueingFuture pollForFirstSuccessfullyCompletedTask(long timeout, TimeUnit unit,
int startIndex, int endIndex)
throws InterruptedException, CancellationException, ExecutionException {
QueueingFuture f;
long start, duration;
for (int i = startIndex; i < endIndex; i ++) {
start = EnvironmentEdgeManager.currentTime();
f = pollForSpecificCompletedTask(timeout, unit, i);
duration = EnvironmentEdgeManager.currentTime() - start;
// Even with operationTimeout less than 0, still loop through the rest as there could
// be other completed tasks before operationTimeout.
timeout -= duration;
if (f == null) {
return null;
} else if (f.getExeEx() != null) {
// we continue here as we need to loop through all the results.
if (LOG.isDebugEnabled()) {
LOG.debug("Replica " + ((f == null) ? 0 : f.getReplicaId()) + " returns " +
f.getExeEx().getCause());
}
if (i == (endIndex - 1)) {
// Rethrow this exception
throw f.getExeEx();
}
continue;
}
return f;
}
// impossible to reach
return null;
}
/**
* Poll for the Nth completed task (index starts from 0 (the 1st), 1 (the second)...)
*
* @param timeout - time to wait before it times out
* @param unit - time unit for timeout
* @param index - the index(th) completed task, index starting from 0
*/
private QueueingFuture pollForSpecificCompletedTask(long timeout, TimeUnit unit, int index)
throws InterruptedException {
if (index < 0) {
return null;
}
synchronized (tasks) {
if (!cancelled && (completedTasks.size() <= index)) unit.timedWait(tasks, timeout);
if (completedTasks.size() <= index) return null;
}
return completedTasks.get(index);
}
public void cancelAll() {
// Grab the lock on tasks so that cancelled is visible everywhere
synchronized (tasks) {
cancelled = true;
}
for (QueueingFuture future : tasks) {
if (future != null) future.cancel(true);
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy