/*
* This file borrows its skeleton from the DefaultChannelFuture in Netty.
*
* Copyright 2012 The Netty Project
*
* The Netty Project 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 edu.washington.escience.myria.util.concurrent;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Preconditions;
import edu.washington.escience.myria.DbException;
/**
* This class provides default implementations to the {@link OperationFuture}.
* <p>
* It defines the following behaviors:
*
* <ol>
* <li>All listeners executed by the order they get added.</li>
* <li>All completed state listeners are executed by the thread caused the state change.</li>
* <li>All listeners added after the future entered into a completed state are executed by the thread adding them.</li>
* <li>States are denoted by an integer. All odd integers denote completed states. And all even integers denote
* uncompleted states.</li>
* </ol>
*
*
* @param <T> possible operation result when operation is successfully conducted.
*/
public abstract class OperationFutureBase<T> implements OperationFuture {
/**
* logger.
* */
private static final Logger LOGGER = LoggerFactory.getLogger(OperationFutureBase.class);
/**
* if the future is cancellable.
* */
private final boolean cancellable;
/**
* The first listener of all the list of listeners.
* */
private OperationFutureListener firstListener;
/**
* All other listeners.
* */
private List<OperationFutureListener> otherListeners;
/**
* The first pre-notify listener of all the list of listeners. A pre-notify listener will get executed right after the
* operation is completed and before the threads who are waiting on this future are wakedup.
* */
private OperationFutureListener firstPreListener;
/**
* All other pre-notify listeners.
* */
private List<OperationFutureListener> otherPreListeners;
/**
* Possible operation result.
* */
private volatile T result;
/**
* If the action fails, what's the cause.
* */
private volatile Throwable cause;
/**
* Thread safe. Guarded by this.
* */
private int waiters;
/** Initial state. */
protected static final int READY = 0;
/** State value representing that the operation is done. */
protected static final int DONE = 1;
/** State value representing that the task is cancelled. */
protected static final int CANCELL = DONE | (1 << 1);
/** State value representing that the task succeeded. */
protected static final int SUCCEED = DONE | (1 << 2);
/** State value representing that the task failed. */
protected static final int FAIL = DONE | (1 << 3);
/**
* the state of the future. Two types of states:
* <ol>
* <li>1. Non-done states. These are temp states, can get changed.</li>
* <li>2. Done states. Once the future enters a done state, the state cannot get changed.</li>
* </ol>
* */
private final AtomicInteger state = new AtomicInteger(READY);
/**
* Check if the state is a done state. A done state is a state with the DONE bit set.
*
* @param stateV the state to check.
* @return if the state is a done state.
* */
protected final boolean isDoneState(final int stateV) {
return (stateV & DONE) != 0;
}
/**
* Atomically change state. Note that if current state is in any of done states, the set will constantly fail.
*
* @param expected expected current state
* @param update the new state to set
* @return if the set succeeds.
* */
protected final boolean compareAndSetState(final int expected, final int update) {
if (isDone()) {
return false;
}
if (this.state.compareAndSet(expected, update)) {
return true;
}
return false;
}
/**
* Wake up all waiters and notify listeners.
* */
protected final void wakeupWaitersAndNotifyListeners() {
notifyPreListeners();
synchronized (this) {
// Allow only once.
if (waiters > 0) {
notifyAll();
}
}
notifyListeners();
}
/**
* @param doneState a done state
* @return if the set succeeds.
* */
protected final boolean setDoneState(final int doneState) {
Preconditions.checkArgument(isDoneState(doneState));
while (true) {
int oldState = state.get();
if (!isDoneState(oldState)) {
if (this.state.compareAndSet(oldState, doneState)) {
return true;
}
} else {
return false;
}
}
}
/**
* Creates a new instance.
*
* @param cancellable {@code true} if and only if this future can be canceled
*/
public OperationFutureBase(final boolean cancellable) {
this.cancellable = cancellable;
}
/**
* @return if the operation is cancellable.
* */
protected final boolean isCancellable() {
return this.cancellable;
}
@Override
public final boolean isDone() {
return (this.state.get() & DONE) != 0;
}
@Override
public final boolean isSuccess() {
return (this.state.get() == SUCCEED);
}
@Override
public final Throwable getCause() {
return cause;
}
@Override
public final synchronized boolean isCancelled() {
return this.state.get() == CANCELL;
}
/**
* Adds the specified listener to this future. The specified listener is notified when this future is
* {@linkplain #isDone() done}. If this future is already completed, the specified listener is notified immediately.
*
* @param listener the listener.
*/
protected final void addListener0(final OperationFutureListener listener) {
if (listener == null) {
throw new NullPointerException("listener");
}
boolean notifyNow = false;
synchronized (this) {
if (isDone()) {
notifyNow = true;
} else {
if (firstListener == null) {
firstListener = listener;
} else {
if (otherListeners == null) {
otherListeners = new ArrayList<OperationFutureListener>(1);
}
otherListeners.add(listener);
}
}
}
if (notifyNow) {
notifyListener(listener);
}
}
/**
* A pre-notify listener will get executed right after the operation is completed and before the threads who are
* waiting on this future are wakedup. If the future is already completed, the specified listener is notified
* immediately.
*
* Adds the specified listener to this future. If the future is already completed, the specified listener is notified
* immediately.
*
* @param listener the listener.
* */
protected final void addPreListener0(final OperationFutureListener listener) {
if (listener == null) {
throw new NullPointerException("listener");
}
boolean notifyNow = false;
synchronized (this) {
if (isDone()) {
notifyNow = true;
} else {
if (firstPreListener == null) {
firstPreListener = listener;
} else {
if (otherPreListeners == null) {
otherPreListeners = new ArrayList<OperationFutureListener>(1);
}
otherPreListeners.add(listener);
}
}
}
if (notifyNow) {
notifyListener(listener);
}
}
/**
* Removes the specified listener from this future. The specified listener is no longer notified when this future is
* {@linkplain #isDone() done}. If the specified listener is not associated with this future, this method does nothing
* and returns silently.
*
* @param listener the listener to be removed.
*/
protected final void removeListener0(final OperationFutureListener listener) {
if (listener == null) {
throw new NullPointerException("listener");
}
if (isDone()) {
// Do nothing if already done.
return;
}
synchronized (this) {
if (listener == firstListener) {
if (otherListeners != null && !otherListeners.isEmpty()) {
firstListener = otherListeners.remove(0);
} else {
firstListener = null;
}
} else if (otherListeners != null) {
otherListeners.remove(listener);
}
}
}
/**
* Waits for this future until it is done, and rethrows the cause of the failure if this future failed. If the cause
* of the failure is a checked exception, it is wrapped with a new {@link DbException} before being thrown.
*
* @throws InterruptedException if interrupted.
* @throws DbException if any other error occurs.
*/
protected final void sync0() throws InterruptedException, DbException {
await();
rethrowIfFailed0();
}
/**
* Waits for this future until it is done, and rethrows the cause of the failure if this future failed. If the cause
* of the failure is a checked exception, it is wrapped with a new {@link DbException} before being thrown.
*
* @throws DbException if any error occurs.
*/
protected final void syncUninterruptibly0() throws DbException {
awaitUninterruptibly();
rethrowIfFailed0();
}
/**
* .
*
* @throws DbException any error will be wrapped into a DbException
* */
private void rethrowIfFailed0() throws DbException {
Throwable causeLocal = getCause();
if (causeLocal == null) {
return;
}
if (causeLocal instanceof RuntimeException) {
throw (RuntimeException) causeLocal;
}
if (causeLocal instanceof Error) {
throw (Error) causeLocal;
}
throw new DbException(causeLocal);
}
/**
* Waits for this future to be completed.
*
* @throws InterruptedException if the current thread was interrupted
*/
protected final void await0() throws InterruptedException {
if (Thread.interrupted()) {
throw new InterruptedException();
}
synchronized (this) {
while (!isDone()) {
waiters++;
try {
wait();
} finally {
waiters--;
}
}
}
}
@Override
public final boolean await(final long timeout, final TimeUnit unit) throws InterruptedException {
return await0(unit.toNanos(timeout), true);
}
/**
* Waits for this future to be completed without interruption. This method catches an {@link InterruptedException} and
* discards it silently.
*
*/
protected final void awaitUninterruptibly0() {
boolean interrupted = false;
synchronized (this) {
while (!isDone()) {
waiters++;
try {
wait();
} catch (InterruptedException e) {
interrupted = true;
} finally {
waiters--;
}
}
}
if (interrupted) {
Thread.currentThread().interrupt();
}
}
@Override
public final boolean awaitUninterruptibly(final long timeout, final TimeUnit unit) {
try {
return await0(unit.toNanos(timeout), false);
} catch (InterruptedException e) {
throw new InternalError();
}
}
/**
* Wait the action to be done for at most timeoutNanos nanoseconds.
*
* @param timeoutNanos timeout in nanoseconds
* @param interruptable true to throw the InterruptedException if interrupted, otherwise just set the interrupted bit.
* @throws InterruptedException if interrupted and is interruptable.
* @return if the action is done within timeout.
*/
private boolean await0(final long timeoutNanos, final boolean interruptable)
throws InterruptedException {
if (interruptable && Thread.interrupted()) {
throw new InterruptedException();
}
long startTime = 0;
if (timeoutNanos > 0) {
startTime = System.nanoTime();
}
long waitTime = timeoutNanos;
boolean interrupted = false;
try {
synchronized (this) {
if (isDone() || waitTime <= 0) {
return isDone();
}
waiters++;
try {
for (; ; ) {
try {
long ms = TimeUnit.NANOSECONDS.toMillis(waitTime);
wait(ms, (int) (waitTime - TimeUnit.MILLISECONDS.toNanos(ms)));
} catch (InterruptedException e) {
if (interruptable) {
throw e;
} else {
interrupted = true;
}
}
if (isDone()) {
return true;
} else {
waitTime = timeoutNanos - (System.nanoTime() - startTime);
if (waitTime <= 0) {
return isDone();
}
}
}
} finally {
waiters--;
}
}
} finally {
if (interrupted) {
Thread.currentThread().interrupt();
}
}
}
/**
* Marks this future as a success and notifies all listeners.
*
* @param result operation result, null if no result is generated
* @return {@code true} if and only if successfully marked this future as a success. Otherwise {@code false} because
* this future is already marked as either a success or a failure.
*/
protected final boolean setSuccess0(final T result) {
if (!this.setDoneState(SUCCEED)) {
return false;
}
this.result = result;
this.wakeupWaitersAndNotifyListeners();
return true;
}
/**
* @return operation result. Null if the operation doesn't generate result.
* */
protected final synchronized T getResult() {
return this.result;
}
/**
* Marks this future as a failure and notifies all listeners.
*
* @param cause the cause.
* @return {@code true} if and only if successfully marked this future as a failure. Otherwise {@code false} because
* this future is already marked as either a success or a failure.
*/
protected final boolean setFailure0(final Throwable cause) {
if (!this.setDoneState(FAIL)) {
return false;
}
this.cause = cause;
this.wakeupWaitersAndNotifyListeners();
return true;
}
@Override
public final boolean cancel() {
if (!cancellable) {
return false;
}
if (!this.setDoneState(CANCELL)) {
return false;
}
boolean canceled = doCancel();
this.wakeupWaitersAndNotifyListeners();
return canceled;
}
/**
* Do the actual cancel operations.
*
* @return true if cancellation is successful.
* */
protected boolean doCancel() {
return true;
};
/**
* notify the listeners.
* */
private void notifyListeners() {
// This method doesn't need synchronization because:
// 1) This method is always called after synchronized (this) block.
// Hence any listener list modification happens-before this method.
// 2) This method is called only when 'done' is true. Once 'done'
// becomes true, the listener list is never modified - see add/removeListener()
if (firstListener != null) {
notifyListener(firstListener);
firstListener = null;
if (otherListeners != null) {
for (OperationFutureListener l : otherListeners) {
notifyListener(l);
}
otherListeners = null;
}
}
}
/**
* notify the listeners.
* */
private void notifyPreListeners() {
// This method doesn't need synchronization because:
// 1) This method is always called after synchronized (this) block.
// Hence any listener list modification happens-before this method.
// 2) This method is called only when 'done' is true. Once 'done'
// becomes true, the listener list is never modified - see add/removeListener()
if (firstPreListener != null) {
notifyListener(firstPreListener);
firstPreListener = null;
if (otherPreListeners != null) {
for (OperationFutureListener l : otherPreListeners) {
notifyListener(l);
}
otherPreListeners = null;
}
}
}
/**
* Notify a single listener.
*
* @param l the listener to be notified.
* */
private void notifyListener(final OperationFutureListener l) {
try {
l.operationComplete(this);
} catch (Exception t) {
if (LOGGER.isWarnEnabled()) {
LOGGER.warn(
"An exception was thrown by " + OperationFutureListener.class.getSimpleName() + '.', t);
}
} catch (Throwable t) {
LOGGER.info(
"A throwable was thrown by " + OperationFutureListener.class.getSimpleName() + '.', t);
throw t;
}
}
}