/*
* 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.ignite.internal.util.future;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.concurrent.locks.LockSupport;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.IgniteLogger;
import org.apache.ignite.internal.IgniteFutureCancelledCheckedException;
import org.apache.ignite.internal.IgniteFutureTimeoutCheckedException;
import org.apache.ignite.internal.IgniteInternalFuture;
import org.apache.ignite.internal.IgniteInterruptedCheckedException;
import org.apache.ignite.internal.util.tostring.GridToStringExclude;
import org.apache.ignite.internal.util.typedef.internal.A;
import org.apache.ignite.internal.util.typedef.internal.S;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.lang.IgniteClosure;
import org.apache.ignite.lang.IgniteInClosure;
import org.jetbrains.annotations.Nullable;
/**
* Future adapter.
*/
public class GridFutureAdapter<R> implements IgniteInternalFuture<R> {
/** Done state representation. */
private static final String DONE = "DONE";
/** Initial state. */
private static final Node INIT = new Node(null);
/** Cancelled state. */
private static final Object CANCELLED = new Object();
/** */
private static final AtomicReferenceFieldUpdater<GridFutureAdapter, Object> stateUpdater =
AtomicReferenceFieldUpdater.newUpdater(GridFutureAdapter.class, Object.class, "state");
/*
* https://bugs.openjdk.java.net/browse/JDK-8074773
*/
static {
@SuppressWarnings("unused")
Class<?> ensureLoaded = LockSupport.class;
}
/**
* Stack node.
*/
private static final class Node {
/** */
private final Object val;
/** */
private volatile Node next;
/**
* @param val Node value.
*/
Node(Object val) {
this.val = val;
}
}
/** */
private static final class ErrorWrapper {
/** */
private final Throwable error;
/**
* @param error Error.
*/
ErrorWrapper(Throwable error) {
this.error = error;
}
/** {@inheritDoc} */
@Override public String toString() {
return String.valueOf(error);
}
}
/** */
private boolean ignoreInterrupts;
/** */
@GridToStringExclude
private volatile Object state = INIT;
/**
* Determines whether the future will ignore interrupts.
*/
public void ignoreInterrupts() {
ignoreInterrupts = true;
}
/** {@inheritDoc} */
@Override public Throwable error() {
Object state0 = state;
if (state0 != null && state0.getClass() == ErrorWrapper.class)
return ((ErrorWrapper)state0).error;
return null;
}
/** {@inheritDoc} */
@SuppressWarnings("unchecked")
@Override public R result() {
Object state0 = state;
if(state0 == null || // It is DONE state
(state0.getClass() != Node.class && // It is not INIT state
state0.getClass() != ErrorWrapper.class && // It is not FAILED
state0 != CANCELLED)) // It is not CANCELLED
return (R)state0;
return null;
}
/** {@inheritDoc} */
@Override public R get() throws IgniteCheckedException {
return get0(ignoreInterrupts);
}
/** {@inheritDoc} */
@Override public R getUninterruptibly() throws IgniteCheckedException {
return get0(true);
}
/** {@inheritDoc} */
@Override public R get(long timeout) throws IgniteCheckedException {
// Do not replace with static import, as it may not compile.
return get(timeout, TimeUnit.MILLISECONDS);
}
/** {@inheritDoc} */
@Override public R get(long timeout, TimeUnit unit) throws IgniteCheckedException {
A.ensure(timeout >= 0, "timeout cannot be negative: " + timeout);
A.notNull(unit, "unit");
return get0(unit.toNanos(timeout));
}
/**
* Internal get routine.
*
* @param ignoreInterrupts Whether to ignore interrupts.
* @return Result.
* @throws IgniteCheckedException If failed.
*/
private R get0(boolean ignoreInterrupts) throws IgniteCheckedException {
if (isDone() || !registerWaiter(Thread.currentThread()))
return resolve();
boolean interrupted = false;
try {
while (true) {
LockSupport.park();
if (Thread.interrupted()) {
interrupted = true;
if (!ignoreInterrupts) {
unregisterWaiter(Thread.currentThread());
throw new IgniteInterruptedCheckedException("Got interrupted while waiting for future to complete.");
}
}
if (isDone())
return resolve();
}
}
finally {
if (interrupted)
Thread.currentThread().interrupt();
}
}
/**
* @param nanosTimeout Timeout (nanoseconds).
* @return Result.
* @throws IgniteFutureTimeoutCheckedException If timeout reached before computation completed.
* @throws IgniteCheckedException If error occurred.
*/
@Nullable private R get0(long nanosTimeout) throws IgniteCheckedException {
if (isDone() || !registerWaiter(Thread.currentThread()))
return resolve();
long deadlineNanos = System.nanoTime() + nanosTimeout;
boolean interrupted = false;
try {
long nanosTimeout0 = nanosTimeout;
while (nanosTimeout0 > 0) {
LockSupport.parkNanos(nanosTimeout0);
nanosTimeout0 = deadlineNanos - System.nanoTime();
if (Thread.interrupted()) {
interrupted = true;
if (!ignoreInterrupts) {
unregisterWaiter(Thread.currentThread());
throw new IgniteInterruptedCheckedException("Got interrupted while waiting for future to complete.");
}
}
if (isDone())
return resolve();
}
}
finally {
if (interrupted)
Thread.currentThread().interrupt();
}
unregisterWaiter(Thread.currentThread());
throw new IgniteFutureTimeoutCheckedException("Timeout was reached before computation completed.");
}
/**
* Resolves the value to result or exception.
*
* @return Result.
* @throws IgniteCheckedException If resolved to exception.
*/
@SuppressWarnings("unchecked")
private R resolve() throws IgniteCheckedException {
if(state == CANCELLED)
throw new IgniteFutureCancelledCheckedException("Future was cancelled: " + this);
if(state == null || state.getClass() != ErrorWrapper.class)
return (R)state;
throw U.cast(((ErrorWrapper)state).error);
}
/**
* @param waiter Waiter to register.
* @return {@code True} if was registered successfully.
*/
private boolean registerWaiter(Object waiter) {
Node node = null;
while (true) {
final Object oldState = state;
if (isDone(oldState))
return false;
if(node == null)
node = new Node(waiter);
if(oldState != INIT && oldState.getClass() == Node.class)
node.next = (Node)oldState;
if (compareAndSetState(oldState, node))
return true;
}
}
/**
* @param waiter Waiter to unregister.
*/
private void unregisterWaiter(Thread waiter) {
Node prev = null;
Object cur = state;
while (cur != null) {
if(cur.getClass() != Node.class)
return;
Object curWaiter = ((Node)cur).val;
Node next = ((Node)cur).next;
if (curWaiter == waiter) {
if (prev == null) {
Object n = next == null ? INIT : next;
cur = compareAndSetState(cur, n) ? null : state;
}
else {
prev.next = next;
cur = null;
}
}
else {
prev = (Node)cur;
cur = next;
}
}
}
/**
* @param exp Expected state.
* @param newState New state.
* @return {@code True} if success
*/
private boolean compareAndSetState(Object exp, Object newState) {
return stateUpdater.compareAndSet(this, exp, newState);
}
/**
* @param head Head of waiters stack.
*/
@SuppressWarnings("unchecked")
private void unblockAll(Node head) {
while (head != null) {
unblock(head.val);
head = head.next;
}
}
/**
* @param waiter Waiter to unblock
*/
private void unblock(Object waiter) {
if(waiter instanceof Thread)
LockSupport.unpark((Thread)waiter);
else
notifyListener((IgniteInClosure<? super IgniteInternalFuture<R>>)waiter);
}
/** {@inheritDoc} */
@Override public void listen(IgniteInClosure<? super IgniteInternalFuture<R>> lsnr) {
if (!registerWaiter(lsnr))
notifyListener(lsnr);
}
/** {@inheritDoc} */
@Override public <T> IgniteInternalFuture<T> chain(final IgniteClosure<? super IgniteInternalFuture<R>, T> doneCb) {
return new ChainFuture<>(this, doneCb, null);
}
/** {@inheritDoc} */
@Override public <T> IgniteInternalFuture<T> chain(final IgniteClosure<? super IgniteInternalFuture<R>, T> doneCb,
Executor exec) {
return new ChainFuture<>(this, doneCb, exec);
}
/**
* @return Logger instance.
*/
@Nullable public IgniteLogger logger() {
return null;
}
/**
* Notifies listener.
*
* @param lsnr Listener.
*/
private void notifyListener(IgniteInClosure<? super IgniteInternalFuture<R>> lsnr) {
assert lsnr != null;
try {
lsnr.apply(this);
}
catch (IllegalStateException e) {
U.error(logger(), "Failed to notify listener (is grid stopped?) [fut=" + this +
", lsnr=" + lsnr + ", err=" + e.getMessage() + ']', e);
}
catch (RuntimeException | Error e) {
U.error(logger(), "Failed to notify listener: " + lsnr, e);
throw e;
}
}
/**
* Default no-op implementation that always returns {@code false}.
* Futures that do support cancellation should override this method
* and call {@link #onCancelled()} callback explicitly if cancellation
* indeed did happen.
*/
@Override public boolean cancel() throws IgniteCheckedException {
return false;
}
/** {@inheritDoc} */
@Override public boolean isDone() {
return isDone(state);
}
/**
* @param state State to check.
* @return {@code True} if future is done.
*/
private boolean isDone(Object state) {
return state == null || state.getClass() != Node.class;
}
/**
* @return {@code True} if future is completed with exception.
*/
public boolean isFailed() {
Object state0 = state;
return state0 != null && state0.getClass() == ErrorWrapper.class;
}
/** {@inheritDoc} */
@Override public boolean isCancelled() {
return state == CANCELLED;
}
/**
* Callback to notify that future is finished with {@code null} result.
* This method must delegate to {@link #onDone(Object, Throwable)} method.
*
* @return {@code True} if result was set by this call.
*/
public final boolean onDone() {
return onDone(null, null);
}
/**
* Callback to notify that future is finished.
* This method must delegate to {@link #onDone(Object, Throwable)} method.
*
* @param res Result.
* @return {@code True} if result was set by this call.
*/
public final boolean onDone(@Nullable R res) {
return onDone(res, null);
}
/**
* Callback to notify that future is finished.
* This method must delegate to {@link #onDone(Object, Throwable)} method.
*
* @param err Error.
* @return {@code True} if result was set by this call.
*/
public final boolean onDone(@Nullable Throwable err) {
return onDone(null, err);
}
/**
* Callback to notify that future is finished. Note that if non-{@code null} exception is passed in
* the result value will be ignored.
*
* @param res Optional result.
* @param err Optional error.
* @return {@code True} if result was set by this call.
*/
public boolean onDone(@Nullable R res, @Nullable Throwable err) {
return onDone(res, err, false);
}
/**
* @param res Result.
* @param err Error.
* @param cancel {@code True} if future is being cancelled.
* @return {@code True} if result was set by this call.
*/
protected boolean onDone(@Nullable R res, @Nullable Throwable err, boolean cancel) {
Object newState = cancel ? CANCELLED : err != null ? new ErrorWrapper(err) : res;
while (true) {
final Object oldState = state;
if (isDone(oldState))
return false;
if (compareAndSetState(oldState, newState)) {
if(oldState != INIT)
unblockAll((Node)oldState);
return true;
}
}
}
/**
* Callback to notify that future is cancelled.
*
* @return {@code True} if cancel flag was set by this call.
*/
public boolean onCancelled() {
return onDone(null, null, true);
}
/** {@inheritDoc} */
@SuppressWarnings("StringEquality")
@Override public String toString() {
Object state0 = state;
String stateStr = stateStr(state0);
String resStr = stateStr == DONE ? String.valueOf(state0) : null;
return S.toString(
GridFutureAdapter.class, this,
"state", stateStr, false,
"res", resStr, true,
"hash", System.identityHashCode(this), false);
}
/**
* @param s State.
* @return State string representation.
*/
private String stateStr(Object s) {
return s == CANCELLED ? "CANCELLED" : s != null && s.getClass() == Node.class ? "INIT" : DONE;
}
/**
*
*/
private static class ChainFuture<R, T> extends GridFutureAdapter<T> {
/** */
private GridFutureAdapter<R> fut;
/** */
private IgniteClosure<? super IgniteInternalFuture<R>, T> doneCb;
/**
* @param fut Future.
* @param doneCb Closure.
* @param cbExec Optional executor to run callback.
*/
ChainFuture(
GridFutureAdapter<R> fut,
IgniteClosure<? super IgniteInternalFuture<R>, T> doneCb,
@Nullable Executor cbExec
) {
this.fut = fut;
this.doneCb = doneCb;
fut.listen(new GridFutureChainListener<>(this, doneCb, cbExec));
}
/** {@inheritDoc} */
@Override public String toString() {
return "ChainFuture [orig=" + fut + ", doneCb=" + doneCb + ']';
}
}
}