All Downloads are FREE. Search and download functionalities are using the official Maven repository.

reactor.rx.Promise Maven / Gradle / Ivy

/*
 * Copyright (c) 2011-2014 Pivotal Software, Inc.
 *
 *  Licensed 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 reactor.rx;

import org.reactivestreams.Processor;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import reactor.Environment;
import reactor.core.Dispatcher;
import reactor.core.dispatch.SynchronousDispatcher;
import reactor.core.dispatch.TailRecurseDispatcher;
import reactor.core.support.NonBlocking;
import reactor.fn.Consumer;
import reactor.fn.Function;
import reactor.fn.Supplier;
import reactor.rx.action.Action;
import reactor.rx.broadcast.BehaviorBroadcaster;
import reactor.rx.subscription.PushSubscription;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * A {@code Promise} is a stateful event container that accepts a single value or error. In addition to {@link #get()
 * getting} or {@link #await() awaiting} the value, consumers can be registered to the outbound {@link #stream()} or via
 * , consumers can be registered to be notified of {@link
 * #onError(Consumer) notified an error}, {@link #onSuccess(Consumer) a value}, or {@link #onComplete(Consumer) both}.
 * 

* A promise also provides methods for composing actions with the future value much like a {@link reactor.rx.Stream}. * However, where * a {@link reactor.rx.Stream} can process many values, a {@code Promise} processes only one value or error. * * @param the type of the value that will be made available * @author Jon Brisbin * @author Stephane Maldini * @see Promises/A+ specification */ public class Promise implements Supplier, Processor, Consumer, NonBlocking { private final ReentrantLock lock = new ReentrantLock(); private final long defaultTimeout; private final Condition pendingCondition; private final Dispatcher dispatcher; private final Environment environment; Action outboundStream; public static enum FinalState { ERROR, COMPLETE } FinalState finalState = null; private O value; private Throwable error; private boolean hasBlockers = false; protected Subscription subscription; /** * Creates a new unfulfilled promise. *

* The {@code dispatcher} is used when notifying the Promise's consumers, determining the thread on which they are * called. The given {@code env} is used to determine the default await timeout. The * default await timeout will be 30 seconds. This Promise will consumer errors from its {@code parent} such that if * the parent completes in error then so too will this Promise. */ public Promise() { this(SynchronousDispatcher.INSTANCE, null); } /** * Creates a new unfulfilled promise. *

* The {@code dispatcher} is used when notifying the Promise's consumers, determining the thread on which they are * called. The given {@code env} is used to determine the default await timeout. If {@code env} is {@code null} the * default await timeout will be 30 seconds. This Promise will consumer errors from its {@code parent} such that if * the parent completes in error then so too will this Promise. * * @param dispatcher The Dispatcher to run any downstream subscribers * @param env The Environment, if any, from which the default await timeout is obtained */ public Promise(Dispatcher dispatcher, @Nullable Environment env) { this.dispatcher = dispatcher; this.environment = env; this.defaultTimeout = env != null ? env.getLongProperty("reactor.await.defaultTimeout", 30000L) : 30000L; this.pendingCondition = lock.newCondition(); } /** * Creates a new promise that has been fulfilled with the given {@code value}. *

* The {@code observable} is used when notifying the Promise's consumers. The given {@code env} is used to determine * the default await timeout. If {@code env} is {@code null} the default await timeout will be 30 seconds. * * @param value The value that fulfills the promise * @param dispatcher The Dispatcher to run any downstream subscribers * @param env The Environment, if any, from which the default await timeout is obtained */ public Promise(O value, Dispatcher dispatcher, @Nullable Environment env) { this(dispatcher, env); finalState = FinalState.COMPLETE; this.value = value; } /** * Creates a new promise that has failed with the given {@code error}. *

* The {@code observable} is used when notifying the Promise's consumers, determining the thread on which they are * called. The given {@code env} is used to determine the default await timeout. If {@code env} is {@code null} the * default await timeout will be 30 seconds. * * @param error The error the completed the promise * @param dispatcher The Dispatcher to run any downstream subscribers * @param env The Environment, if any, from which the default await timeout is obtained */ public Promise(Throwable error, Dispatcher dispatcher, @Nullable Environment env) { this(dispatcher, env); finalState = FinalState.ERROR; this.error = error; } /** * Assign a {@link Consumer} that will either be invoked later, when the {@code Promise} is completed by either * setting a value or propagating an error, or, if this {@code Promise} has already been fulfilled, is immediately * scheduled to be executed on the current {@link reactor.core.Dispatcher}. * * @param onComplete the completion {@link Consumer} * @return {@literal the new Promise} */ public Promise onComplete(@Nonnull final Consumer> onComplete) { if (dispatcher == SynchronousDispatcher.INSTANCE || TailRecurseDispatcher.class == dispatcher.getClass()) { lock.lock(); try { if (finalState == FinalState.ERROR) { onComplete.accept(this); return Promises.error(environment, dispatcher, error); } else if (finalState == FinalState.COMPLETE) { onComplete.accept(this); return Promises.success(environment, dispatcher, value); } } catch (Throwable t) { return Promises.error(environment, dispatcher, t); } finally { lock.unlock(); } } return stream().lift(new Supplier>() { @Override public Action get() { return new Action() { @Override protected void doNext(O ev) { onComplete.accept(Promise.this); broadcastNext(ev); broadcastComplete(); } @Override protected void doError(Throwable ev) { onComplete.accept(Promise.this); broadcastError(ev); } @Override protected void doComplete() { onComplete.accept(Promise.this); broadcastComplete(); } }; } }).next(); } /** * Only forward onError and onComplete signals into the returned stream. * * @return {@literal new Promise} */ public final Promise after() { if (dispatcher == SynchronousDispatcher.INSTANCE || TailRecurseDispatcher.class == dispatcher.getClass()) { lock.lock(); try { if (finalState == FinalState.COMPLETE) { return Promises.success(environment, dispatcher, null); } } catch (Throwable t) { return Promises.error(environment, dispatcher, t); } finally { lock.unlock(); } } return stream().after().next(); } /** * Assign a {@link Consumer} that will either be invoked later, when the {@code Promise} is successfully completed * with * a value, or, if this {@code Promise} has already been fulfilled, is immediately scheduled to be executed on the * current {@link Dispatcher}. * * @param onSuccess the success {@link Consumer} * @return {@literal the new Promise} */ public Promise onSuccess(@Nonnull final Consumer onSuccess) { if (dispatcher == SynchronousDispatcher.INSTANCE || TailRecurseDispatcher.class == dispatcher.getClass()) { lock.lock(); try { if (finalState == FinalState.COMPLETE) { if (value != null) { onSuccess.accept(value); } return this; } } catch (Throwable t) { return Promises.error(environment, dispatcher, t); } finally { lock.unlock(); } } return stream().observe(onSuccess).next(); } /** * Assign a {@link Function} that will either be invoked later, when the {@code Promise} is successfully completed * with * a value, or, if this {@code Promise} has already been fulfilled, is immediately scheduled to be executed on the * current {@link Dispatcher}. * * @param transformation the function to apply on signal to the transformed Promise * @return {@literal the new Promise} */ public Promise map(@Nonnull final Function transformation) { if (dispatcher == SynchronousDispatcher.INSTANCE || TailRecurseDispatcher.class == dispatcher.getClass()) { lock.lock(); try { if (finalState == FinalState.ERROR) { return Promises.error(environment, dispatcher, error); } else if (finalState == FinalState.COMPLETE) { return Promises.success(environment, dispatcher, value != null ? transformation.apply(value) : null); } } catch (Throwable t) { return Promises.error(environment, dispatcher, t); } finally { lock.unlock(); } } return stream().map(transformation).next(); } /** * Assign a {@link Function} that will either be invoked later, when the {@code Promise} is successfully completed * with * a value, or, if this {@code Promise} has already been fulfilled, is immediately scheduled to be executed on the * current {@link Dispatcher}. *

* FlatMap is typically used to listen for a delayed/async publisher, e.g. promise.flatMap( data -> Promise.success * (data) ). * The result is merged directly on the returned stream. * * @param transformation the function to apply on signal to the supplied Promise that will be merged back. * @return {@literal the new Promise} */ public Promise flatMap(@Nonnull final Function> transformation) { if (dispatcher == SynchronousDispatcher.INSTANCE || TailRecurseDispatcher.class == dispatcher.getClass()) { lock.lock(); try { if (finalState == FinalState.ERROR) { return Promises.error(environment, dispatcher, error); } else if (finalState == FinalState.COMPLETE) { if (value != null) { Promise successPromise = Promises.ready(environment, dispatcher); transformation.apply(value).subscribe(successPromise); return successPromise; } else { return Promises.success(environment, dispatcher, null); } } } catch (Throwable t) { return Promises.error(environment, dispatcher, t); } finally { lock.unlock(); } } return stream().flatMap(transformation).next(); } /** * Assign a {@link Consumer} that will either be invoked later, when the {@code Promise} is completed with an error, * or, if this {@code Promise} has already been fulfilled, is immediately scheduled to be executed on the current * {@link Dispatcher}. The error is recovered and materialized as the next signal to the returned stream. * * @param onError the error {@link Consumer} * @return {@literal the new Promise} */ public Promise onError(@Nonnull final Consumer onError) { if (dispatcher == SynchronousDispatcher.INSTANCE || TailRecurseDispatcher.class == dispatcher.getClass()) { lock.lock(); try { if (finalState == FinalState.ERROR) { onError.accept(error); return this; }else if(finalState == FinalState.COMPLETE){ return this; } } catch (Throwable t) { return Promises.error(environment, dispatcher, t); } finally { lock.unlock(); } } return stream().when(Throwable.class, onError).next(); } /** * Indicates whether this {@code Promise} has been completed with either an error or a value * * @return {@code true} if this {@code Promise} is complete, {@code false} otherwise. * @see #isPending() */ public boolean isComplete() { lock.lock(); try { return finalState != null; } finally { lock.unlock(); } } /** * Indicates whether this {@code Promise} has yet to be completed with a value or an error. * * @return {@code true} if this {@code Promise} is still pending, {@code false} otherwise. * @see #isComplete() */ public boolean isPending() { lock.lock(); try { return finalState == null; } finally { lock.unlock(); } } /** * Indicates whether this {@code Promise} has been successfully completed a value. * * @return {@code true} if this {@code Promise} is successful, {@code false} otherwise. */ public boolean isSuccess() { lock.lock(); try { return finalState == FinalState.COMPLETE; } finally { lock.unlock(); } } /** * Indicates whether this {@code Promise} has been completed with an error. * * @return {@code true} if this {@code Promise} was completed with an error, {@code false} otherwise. */ public boolean isError() { lock.lock(); try { return finalState == FinalState.ERROR; } finally { lock.unlock(); } } /** * Block the calling thread, waiting for the completion of this {@code Promise}. A default timeout as specified in * Reactor's {@link Environment} properties using the key {@code reactor.await.defaultTimeout} is used. The * default is * 30 seconds. If the promise is completed with an error a RuntimeException that wraps the error is thrown. * * @return true if complete without error * @throws InterruptedException if the thread is interruped while awaiting completion * @throws RuntimeException if the promise is completed with an error */ public boolean awaitSuccess() throws InterruptedException { return awaitSuccess(defaultTimeout, TimeUnit.MILLISECONDS); } /** * Block the calling thread for the specified time, waiting for the completion of this {@code Promise}. * * @param timeout the timeout value * @param unit the {@link TimeUnit} of the timeout value * @return true if complete without error * completed * @throws InterruptedException if the thread is interruped while awaiting completion */ public boolean awaitSuccess(long timeout, TimeUnit unit) throws InterruptedException { await(timeout, unit); return isSuccess(); } /** * Block the calling thread, waiting for the completion of this {@code Promise}. A default timeout as specified in * Reactor's {@link Environment} properties using the key {@code reactor.await.defaultTimeout} is used. The * default is * 30 seconds. If the promise is completed with an error a RuntimeException that wraps the error is thrown. * * @return the value of this {@code Promise} or {@code null} if the timeout is reached and the {@code Promise} has * not * completed * @throws InterruptedException if the thread is interruped while awaiting completion * @throws RuntimeException if the promise is completed with an error */ public O await() throws InterruptedException { return await(defaultTimeout, TimeUnit.MILLISECONDS); } /** * Block the calling thread for the specified time, waiting for the completion of this {@code Promise}. * * @param timeout the timeout value * @param unit the {@link TimeUnit} of the timeout value * @return the value of this {@code Promise} or {@code null} if the timeout is reached and the {@code Promise} has * not * completed * @throws InterruptedException if the thread is interruped while awaiting completion */ public O await(long timeout, TimeUnit unit) throws InterruptedException { if (!isPending()) { return get(); } lock.lock(); try { hasBlockers = true; if (timeout >= 0) { long msTimeout = TimeUnit.MILLISECONDS.convert(timeout, unit); long endTime = System.currentTimeMillis() + msTimeout; while (finalState == null && (System.currentTimeMillis()) < endTime) { this.pendingCondition.await(200, TimeUnit.MILLISECONDS); } } else { while (finalState == null) { this.pendingCondition.await(200, TimeUnit.MILLISECONDS); } } } finally { hasBlockers = false; lock.unlock(); } return get(); } /** * Block the calling thread, waiting for the completion of this {@code Promise}. A default timeout as specified in * Reactor's {@link Environment} properties using the key {@code reactor.await.defaultTimeout} is used. The * default is * 30 seconds. If the promise is completed with an error a RuntimeException that wraps the error is thrown. * * @return the value of this {@code Promise} or {@code null} if the timeout is reached and the {@code Promise} has * not * completed * @throws RuntimeException if the promise is completed with an error */ public O poll() { return poll(defaultTimeout, TimeUnit.MILLISECONDS); } /** * Block the calling thread for the specified time, waiting for the completion of this {@code Promise}. If the * promise * is completed with an error a RuntimeException that wraps the error is thrown. * * @param timeout the timeout value * @param unit the {@link TimeUnit} of the timeout value * @return the value of this {@code Promise} or {@code null} if the timeout is reached and the {@code Promise} has * not * completed */ public O poll(long timeout, TimeUnit unit) { try { return await(timeout, unit); } catch (InterruptedException ie) { Thread.currentThread().interrupt(); return null; } } /** * Returns the value that completed this promise. Returns {@code null} if the promise has not been completed. If the * promise is completed with an error a RuntimeException that wraps the error is thrown. * * @return the value that completed the promise, or {@code null} if it has not been completed * @throws RuntimeException if the promise was completed with an error */ @Override public O get() { lock.lock(); try { if (finalState == FinalState.COMPLETE) { return value; } else if (finalState == FinalState.ERROR) { if (RuntimeException.class.isInstance(error)) { throw (RuntimeException) error; } else { throw new RuntimeException(error); } } else { return null; } } finally { lock.unlock(); } } /** * Return the error (if any) that has completed this {@code Promise}. Returns {@code null} if the promise has not * been * completed, or was completed with a value. * * @return the error (if any) */ public Throwable reason() { lock.lock(); try { return error; } finally { lock.unlock(); } } public Stream stream() { lock.lock(); try { if (outboundStream == null) { outboundStream = BehaviorBroadcaster.first(value, environment, dispatcher).capacity(1); if (isSuccess()) { outboundStream.onComplete(); } else if (isError()) { outboundStream.onError(error); } } } finally { lock.unlock(); } return outboundStream; } @Override public void subscribe(final Subscriber subscriber) { stream().subscribe(subscriber); } public Environment getEnvironment() { return environment; } @Override public void onSubscribe(Subscription subscription) { this.subscription = subscription; subscription.request(Long.MAX_VALUE); } @Override public void onNext(O element) { valueAccepted(element); } @Override public void onComplete() { completeAccepted(); } @Override public void onError(Throwable cause) { errorAccepted(cause); } @Override public void accept(O o) { valueAccepted(o); } public StreamUtils.StreamVisitor debug() { Action debugged = findOldestStream(); if (subscription == null || debugged == null) { return outboundStream != null ? outboundStream.debug() : null; } return debugged.debug(); } @SuppressWarnings("unchecked") public Action findOldestStream() { Subscription sub = subscription; Action that = null; while (sub != null && PushSubscription.class.isAssignableFrom(sub.getClass()) && ((PushSubscription) sub).getPublisher() != null && Action.class.isAssignableFrom(((PushSubscription) sub).getPublisher().getClass()) ) { that = (Action) ((PushSubscription) sub).getPublisher(); sub = that.getSubscription(); } return that; } protected void errorAccepted(Throwable error) { lock.lock(); try { if (!isPending()) { if (isSuccess()) throw new IllegalStateException(finalState.toString() + " : " + value, error); else throw new IllegalStateException(finalState.toString(), error); } this.error = error; this.finalState = FinalState.ERROR; if (subscription != null) { subscription.cancel(); } if (outboundStream != null) { outboundStream.onError(error); } if (hasBlockers) { pendingCondition.signalAll(); hasBlockers = false; } } finally { lock.unlock(); } } protected void valueAccepted(O value) { lock.lock(); try { if (!isPending()) { if (isError()) throw new IllegalStateException(value + " >> " + finalState.toString(), error); else if (isSuccess()) throw new IllegalStateException(value + " >> " + finalState.toString() + " : " + value); else throw new IllegalStateException(value + " >> " + finalState.toString()); } this.value = value; this.finalState = FinalState.COMPLETE; if (subscription != null) { subscription.cancel(); } if (outboundStream != null) { if(value != null) { outboundStream.onNext(value); } outboundStream.onComplete(); } if (hasBlockers) { pendingCondition.signalAll(); hasBlockers = false; } } finally { lock.unlock(); } } protected void completeAccepted() { lock.lock(); try { if (isPending()) { valueAccepted(null); } if (subscription != null) { subscription.cancel(); subscription = null; } } finally { lock.unlock(); } } @Override public boolean isReactivePull(Dispatcher dispatcher, long producerCapacity) { return true; } @Override public long getCapacity() { return 1; } @Override public String toString() { lock.lock(); try { return "Promise{" + "value=" + value + (finalState != null ? ", state=" + finalState : "") + ", error=" + error + '}'; } finally { lock.unlock(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy