// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.sdk.util;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicReference;
import org.chromium.sdk.CallbackSemaphore;
import org.chromium.sdk.RelayOk;
import org.chromium.sdk.SyncCallback;
/**
* Represents a result of asynchronous operation. Unlike {@link Future}, the result may be obtained
* both synchronously (blocking getter method) and asynchronously.
* <p>The class provides a low-level service and should be used with {@link AtomicReference} class.
* {@link AsyncFutureRef} offers a slightly more convenient interface for the price of extra object
* (you may find it significant when there are tons of operations).
* <p>The owner of the future operation must have a permanent field of type
* {@code AtomicReference<AsyncFuture>}. It will consequently have the following values:
* <ol>
* <li>null -- operation is idle (hasn't been started yet); user will start it some time later;
* <li>{@link AsyncFuture} instance that is performing the operation;
* <li>{@link AsyncFuture} stub instance that simply keeps a result value and returns it very
* quickly;
* </ol>
* The user only creates an empty {@link AtomicReference} object. Not before the result value is
* actually needed the other objects are created and the operation is started. This is typically
* happens in some getter. The method should:
* <ul>
* <li>check the reference; if it still holds null value, the user should initialize it by
* {@link AsyncFuture#initializeReference} method; at this moment the operation is actually
* prepared and starts; if several threads are doing this simultaneously only one operation
* will be actually started;
* <li>read {@link AsyncFuture} instance from the reference -- is will be not null at this moment;
* <li>get the result from it by {@link #getSync()} or {@link #getAsync} methods.
* </ul>
* @param <T> type of the future result
*/
public abstract class AsyncFuture<T> {
/**
* Initializes the reference with the a new instance of {@link AsyncFuture} if the reference
* still holds null. This has a semantics of starting the operation. If the reference already
* holds non-null value, the method does nothing.
* <p>This method is thread-safe.
*/
public static <T> void initializeReference(AtomicReference<AsyncFuture<T>> ref,
Operation<T> operation) {
initializeReference(ref, operation, false);
}
/**
* Initializes the reference with the a new instance of {@link AsyncFuture}. This
* always works even if the reference has already been initialized. This has a semantics of
* re-starting the operation and obtaining the new result after this call.
* <p>This method is thread-safe.
*/
public static <T> void reinitializeReference(AtomicReference<AsyncFuture<T>> ref,
Operation<T> operation) {
initializeReference(ref, operation, true);
}
/**
* Initializes the reference with the a new instance of {@link AsyncFuture} that already
* holds a result. This method skips the calculation phase and may be needed to support
* some trivial cases.
* <p>This method is thread-safe.
*/
public static <T> void initializeTrivial(AtomicReference<AsyncFuture<T>> ref, T result) {
boolean updated;
updated = ref.compareAndSet(null, new Done<T>(result));
}
/**
* Operation may work synchronously. This method will block in this case.
*/
public static <T> void initializeReference(AtomicReference<AsyncFuture<T>> ref,
Operation<T> operation, boolean forceRefresh) {
// Creating worker not yet started (with fake relayOk).
Working<T> working = new Working<T>(ref);
boolean updated;
if (forceRefresh) {
// We exposed worker not yet started now.
ref.set(working);
updated = true;
} else {
// We possibly exposed worker not yet started now.
updated = ref.compareAndSet(null, working);
}
if (updated) {
// Make sure we started worker.
RelayOk relayOk = working.start(operation);
// It is important that this method returns RelayOk.
// RelayOk symbolize we have started operation as we had to.
}
}
/**
* Returns the operation result. If the result is not ready yet, the method will block until
* the operation finished.
* @see #isDone()
* @return the operation result
*/
public abstract T getSync() throws MethodIsBlockingException;
/**
* Obtains the operation result. The result is passed to callback immediately (synchronously) or
* asynchronosuly later.
* @param callback may be null
* @param syncCallback may be null
*/
public abstract RelayOk getAsync(Callback<? super T> callback, SyncCallback syncCallback);
/**
* Returns whether the operation is done. If the method returns true, the following calls
* to {@link #getSync()} will return immediately. The following calls to {@link #getSync()}
* of other instance of {@link AsyncFuture} that is held in the reference will also be
* non-blocking, until {@link #reinitializeReference} is called with this reference.
*/
public abstract boolean isDone();
/**
* A callback used in operation and in {@link AsyncFuture#getAsync} method.
*/
public interface Callback<RES> {
void done(RES res);
}
/**
* An operation that asynchronously results in a value of type RES.
*/
public interface Operation<RES> {
/**
* Starts the operation. The method can be blocking and perform the entire operation
* or its part. In this case the corresponding call to {@link AsyncFuture#initializeReference}
* or {@link AsyncFuture#reinitializeReference} will be blocking as well.
*/
RelayOk start(Callback<RES> callback, SyncCallback syncCallback);
}
/**
* Helper class that presents operation meant to be executed synchronously in the current thread
* as an asynchronous {@link Operation} suitable for {@link AsyncFuture}. User implements
* {@link #runSync()} method and passes the object returned form {@link #asAsyncOperation()}
* to {@link AsyncFuture}. Immediately after this he must call {@link #execute()} method
* (because some threads may have already got blocked on {@link AsyncFuture#getSync()} method).
* However the user {@link #runSync()} method may not be actually called if {@link AsyncFuture}
* didn't started the operation for some reason.
*/
public static abstract class SyncOperation<RES> {
private Callback<RES> callback = null;
private SyncCallback syncCallback;
/**
* User must call this method immediately after he passed the object to {@link AsyncFuture}.
* Failure to do this may cause that some threads remain blocked waiting for result.
* @throws MethodIsBlockingException as the {@link #runSync()} is blocking
*/
public void execute() throws MethodIsBlockingException {
if (callback == null) {
// We haven't been started. Probably because asynch operation happened to be started
// and completed in another thread. Silently do not execute.
return;
}
try {
RES res = runSync();
callback.done(res);
} finally {
syncCallback.callbackDone(null);
}
}
/**
* @return Operation<RES> type suitable for {@link AsyncFuture#initializeReference} and
* {@link AsyncFuture#reinitializeReference} methods.
*/
public Operation<RES> asAsyncOperation() {
return new Operation<RES>() {
@Override
public RelayOk start(Callback<RES> callback, SyncCallback syncCallback) {
SyncOperation.this.callback = callback;
SyncOperation.this.syncCallback = syncCallback;
return USER_PROMISES_TO_CALL_EXECUTE;
}
};
}
/**
* Does whatever tasks the operation requires. It may also fail with no special precautions.
* @throws MethodIsBlockingException method may deal with blocking operations
*/
protected abstract RES runSync() throws MethodIsBlockingException;
private static final RelayOk USER_PROMISES_TO_CALL_EXECUTE = new RelayOk() {
};
}
private static class Working<T> extends AsyncFuture<T> {
private final AtomicReference<AsyncFuture<T>> ref;
private final List<CallbackPair<T>> callbacks = new ArrayList<CallbackPair<T>>(1);
private boolean resultReady = false;
private T result;
private Exception startFailure;
public Working(AtomicReference<AsyncFuture<T>> ref) {
this.ref = ref;
}
public RelayOk start(Operation<T> operation) {
RuntimeException e = null;
boolean relayed = false;
try {
RelayOk relayOk;
relayOk = startOrFail(operation);
relayed = true;
return relayOk;
} catch (RuntimeException ex) {
e = ex;
throw ex;
} catch (Error er) {
// Dont't interfere with severe problems
e = null;
throw er;
} finally {
// Make sure we started worker.
if (!relayed) {
startFailureIsReady(e);
}
}
}
private RelayOk startOrFail(Operation<T> operation) {
Callback<T> callback = new Callback<T>() {
@Override
public void done(T res) {
resultIsReady(res);
}
};
SyncCallback syncCallback = new SyncCallback() {
@Override
public void callbackDone(RuntimeException e) {
resultIsReadySync(e);
}
};
return operation.start(callback, syncCallback);
}
@Override
public RelayOk getAsync(Callback<? super T> callback, SyncCallback syncCallback) {
synchronized (this) {
if (!resultReady) {
callbacks.add(new CallbackPair<T>(callback, syncCallback));
return OPERATION_SHOULD_BE_RUNNING_RELAY_OK;
}
}
return deliverResultImmediately(getResultOrFail(), callback, syncCallback);
}
// We added callback to the chain. Will be called later.
private static final RelayOk OPERATION_SHOULD_BE_RUNNING_RELAY_OK = new RelayOk() {};
@Override
public T getSync() throws MethodIsBlockingException {
synchronized (this) {
if (resultReady) {
return getResultOrFail();
}
}
class CallbackImpl implements Callback<T> {
private T res;
@Override
public synchronized void done(T res) {
this.res = res;
}
synchronized T get() {
return res;
}
}
CallbackImpl callback = new CallbackImpl();
CallbackSemaphore callbackSemaphore = new CallbackSemaphore();
RelayOk relayOk = getAsync(callback, callbackSemaphore);
callbackSemaphore.acquireDefault(relayOk);
return callback.get();
}
@Override
public boolean isDone() {
synchronized (this) {
return resultReady;
}
}
private void resultIsReady(T result) {
Done<T> resultDone = new Done<T>(result);
boolean updated = ref.compareAndSet(this, resultDone);
if (!updated) {
throw new IllegalStateException();
}
synchronized (this) {
this.resultReady = true;
this.result = result;
}
for (CallbackPair<T> pair : callbacks) {
if (pair.callback != null) {
pair.callback.done(result);
}
}
}
private void resultIsReadySync(RuntimeException e) {
// Double-check that result is marked ready.
synchronized (this) {
this.resultReady = true;
}
for (CallbackPair<T> pair : callbacks) {
if (pair.syncCallback != null) {
pair.syncCallback.callbackDone(e);
}
}
}
private T getResultOrFail() {
if (startFailure == null) {
return result;
} else {
throw new RuntimeException("Failed to start operation", startFailure);
}
}
void startFailureIsReady(RuntimeException cause) {
synchronized (this) {
this.resultReady = true;
this.result = null;
this.startFailure = cause;
}
for (CallbackPair<T> pair : callbacks) {
if (pair.syncCallback != null) {
pair.syncCallback.callbackDone(cause);
}
}
}
private static class CallbackPair<RES> {
final Callback<? super RES> callback;
final SyncCallback syncCallback;
CallbackPair(Callback<? super RES> callback, SyncCallback syncCallback) {
this.callback = callback;
this.syncCallback = syncCallback;
}
}
}
private static class Done<T> extends AsyncFuture<T> {
private final T result;
public Done(T result) {
this.result = result;
}
@Override
public T getSync() {
return result;
}
@Override
public RelayOk getAsync(Callback<? super T> callback, SyncCallback syncCallback) {
return deliverResultImmediately(result, callback, syncCallback);
}
@Override
public boolean isDone() {
return true;
}
}
private static <T> RelayOk deliverResultImmediately(T result,
Callback<T> callback, SyncCallback syncCallback) {
if (callback != null) {
callback.done(result);
}
return RelaySyncCallback.finish(syncCallback);
}
}