// 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); } }