diff --git a/src/main/java/rx/AsyncEmitter.java b/src/main/java/rx/AsyncEmitter.java
new file mode 100644
index 0000000000..6235db4f60
--- /dev/null
+++ b/src/main/java/rx/AsyncEmitter.java
@@ -0,0 +1,82 @@
+/**
+ * Copyright 2016 Netflix, 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 rx;
+
+import rx.annotations.Experimental;
+
+/**
+ * Abstraction over a RxJava Subscriber that allows associating
+ * a resource with it and exposes the current number of downstream
+ * requested amount.
+ *
+ * The onNext, onError and onCompleted methods should be called
+ * in a sequential manner, just like the Observer's methods. The
+ * other methods are threadsafe.
+ *
+ * @param the value type to emit
+ */
+@Experimental
+public interface AsyncEmitter extends Observer {
+
+ /**
+ * Sets a Subscription on this emitter; any previous Subscription
+ * or Cancellation will be unsubscribed/cancelled.
+ * @param s the subscription, null is allowed
+ */
+ void setSubscription(Subscription s);
+
+ /**
+ * Sets a Cancellable on this emitter; any previous Subscription
+ * or Cancellation will be unsubscribed/cancelled.
+ * @param c the cancellable resource, null is allowed
+ */
+ void setCancellation(Cancellable c);
+ /**
+ * The current outstanding request amount.
+ *
This method it threadsafe.
+ * @return the current outstanding request amount
+ */
+ long requested();
+
+ /**
+ * A functional interface that has a single close method
+ * that can throw.
+ */
+ interface Cancellable {
+
+ /**
+ * Cancel the action or free a resource.
+ * @throws Exception on error
+ */
+ void cancel() throws Exception;
+ }
+
+ /**
+ * Options to handle backpressure in the emitter.
+ */
+ enum BackpressureMode {
+ NONE,
+
+ ERROR,
+
+ BUFFER,
+
+ DROP,
+
+ LATEST
+ }
+}
diff --git a/src/main/java/rx/Observable.java b/src/main/java/rx/Observable.java
index 98b47ec796..b0c405db55 100644
--- a/src/main/java/rx/Observable.java
+++ b/src/main/java/rx/Observable.java
@@ -1680,6 +1680,50 @@ public static Observable from(T[] array) {
return create(new OnSubscribeFromArray(array));
}
+ /**
+ * Provides an API (via a cold Observable) that bridges the reactive world with the callback-style,
+ * generally non-backpressured world.
+ *
+ * You should call the AsyncEmitter's onNext, onError and onCompleted methods in a serialized fashion. The
+ * rest of its methods are threadsafe.
+ *
+ * @param asyncEmitter the emitter that is called when a Subscriber subscribes to the returned {@code Observable}
+ * @param backpressure the backpressure mode to apply if the downstream Subscriber doesn't request (fast) enough
+ * @return the new Observable instance
+ * @see AsyncEmitter
+ * @see AsyncEmitter.BackpressureMode
+ * @see AsyncEmitter.Cancellable
+ */
+ @Experimental
+ public static Observable fromAsync(Action1> asyncEmitter, AsyncEmitter.BackpressureMode backpressure) {
+ return create(new OnSubscribeFromAsync(asyncEmitter, backpressure));
+ }
+
/**
* Returns an Observable that, when an observer subscribes to it, invokes a function you specify and then
* emits the value returned from that function.
diff --git a/src/main/java/rx/internal/operators/BackpressureUtils.java b/src/main/java/rx/internal/operators/BackpressureUtils.java
index 5d63ab7c27..d627ef22b5 100644
--- a/src/main/java/rx/internal/operators/BackpressureUtils.java
+++ b/src/main/java/rx/internal/operators/BackpressureUtils.java
@@ -69,14 +69,14 @@ public static long getAndAddRequest(AtomicLongFieldUpdater requested, T o
}
/**
- * Adds {@code n} to {@code requested} and returns the value prior to addition once the
+ * Adds {@code n} (not validated) to {@code requested} and returns the value prior to addition once the
* addition is successful (uses CAS semantics). If overflows then sets
* {@code requested} field to {@code Long.MAX_VALUE}.
*
* @param requested
* atomic long that should be updated
* @param n
- * the number of requests to add to the requested count
+ * the number of requests to add to the requested count, positive (not validated)
* @return requested value just prior to successful addition
*/
public static long getAndAddRequest(AtomicLong requested, long n) {
@@ -413,4 +413,17 @@ public static long produced(AtomicLong requested, long n) {
}
}
}
+
+ /**
+ * Validates the requested amount and returns true if it is positive.
+ * @param n the requested amount
+ * @return true if n is positive
+ * @throws IllegalArgumentException if n is negative
+ */
+ public static boolean validate(long n) {
+ if (n < 0) {
+ throw new IllegalArgumentException("n >= 0 required but it was " + n);
+ }
+ return n != 0L;
+ }
}
diff --git a/src/main/java/rx/internal/operators/OnSubscribeFromAsync.java b/src/main/java/rx/internal/operators/OnSubscribeFromAsync.java
new file mode 100644
index 0000000000..1aa42954a1
--- /dev/null
+++ b/src/main/java/rx/internal/operators/OnSubscribeFromAsync.java
@@ -0,0 +1,534 @@
+/**
+ * Copyright 2016 Netflix, 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 rx.internal.operators;
+
+import java.util.Queue;
+import java.util.concurrent.atomic.*;
+
+import rx.*;
+import rx.Observable.OnSubscribe;
+import rx.exceptions.*;
+import rx.functions.Action1;
+import rx.internal.util.RxRingBuffer;
+import rx.internal.util.atomic.SpscUnboundedAtomicArrayQueue;
+import rx.internal.util.unsafe.*;
+import rx.plugins.RxJavaHooks;
+import rx.subscriptions.SerialSubscription;
+
+public final class OnSubscribeFromAsync implements OnSubscribe {
+
+ final Action1> asyncEmitter;
+
+ final AsyncEmitter.BackpressureMode backpressure;
+
+ public OnSubscribeFromAsync(Action1> asyncEmitter, AsyncEmitter.BackpressureMode backpressure) {
+ this.asyncEmitter = asyncEmitter;
+ this.backpressure = backpressure;
+ }
+
+ @Override
+ public void call(Subscriber super T> t) {
+ BaseAsyncEmitter emitter;
+
+ switch (backpressure) {
+ case NONE: {
+ emitter = new NoneAsyncEmitter(t);
+ break;
+ }
+ case ERROR: {
+ emitter = new ErrorAsyncEmitter(t);
+ break;
+ }
+ case DROP: {
+ emitter = new DropAsyncEmitter(t);
+ break;
+ }
+ case LATEST: {
+ emitter = new LatestAsyncEmitter(t);
+ break;
+ }
+ default: {
+ emitter = new BufferAsyncEmitter(t, RxRingBuffer.SIZE);
+ break;
+ }
+ }
+
+ t.add(emitter);
+ t.setProducer(emitter);
+ asyncEmitter.call(emitter);
+
+ }
+
+ static final class CancellableSubscription
+ extends AtomicReference
+ implements Subscription {
+
+ /** */
+ private static final long serialVersionUID = 5718521705281392066L;
+
+ public CancellableSubscription(AsyncEmitter.Cancellable cancellable) {
+ super(cancellable);
+ }
+
+ @Override
+ public boolean isUnsubscribed() {
+ return get() == null;
+ }
+
+ @Override
+ public void unsubscribe() {
+ if (get() != null) {
+ AsyncEmitter.Cancellable c = getAndSet(null);
+ if (c != null) {
+ try {
+ c.cancel();
+ } catch (Exception ex) {
+ Exceptions.throwIfFatal(ex);
+ RxJavaHooks.onError(ex);
+ }
+ }
+ }
+ }
+ }
+
+ static abstract class BaseAsyncEmitter
+ extends AtomicLong
+ implements AsyncEmitter, Producer, Subscription {
+ /** */
+ private static final long serialVersionUID = 7326289992464377023L;
+
+ final Subscriber super T> actual;
+
+ final SerialSubscription serial;
+
+ public BaseAsyncEmitter(Subscriber super T> actual) {
+ this.actual = actual;
+ this.serial = new SerialSubscription();
+ }
+
+ @Override
+ public void onCompleted() {
+ if (actual.isUnsubscribed()) {
+ return;
+ }
+ try {
+ actual.onCompleted();
+ } finally {
+ serial.unsubscribe();
+ }
+ }
+
+ @Override
+ public void onError(Throwable e) {
+ if (actual.isUnsubscribed()) {
+ return;
+ }
+ try {
+ actual.onError(e);
+ } finally {
+ serial.unsubscribe();
+ }
+ }
+
+ @Override
+ public final void unsubscribe() {
+ serial.unsubscribe();
+ onUnsubscribed();
+ }
+
+ void onUnsubscribed() {
+ // default is no-op
+ }
+
+ @Override
+ public final boolean isUnsubscribed() {
+ return serial.isUnsubscribed();
+ }
+
+ @Override
+ public final void request(long n) {
+ if (BackpressureUtils.validate(n)) {
+ BackpressureUtils.getAndAddRequest(this, n);
+ onRequested();
+ }
+ }
+
+ void onRequested() {
+ // default is no-op
+ }
+
+ @Override
+ public final void setSubscription(Subscription s) {
+ serial.set(s);
+ }
+
+ @Override
+ public final void setCancellation(AsyncEmitter.Cancellable c) {
+ setSubscription(new CancellableSubscription(c));
+ }
+
+ @Override
+ public final long requested() {
+ return get();
+ }
+ }
+
+ static final class NoneAsyncEmitter extends BaseAsyncEmitter {
+
+ /** */
+ private static final long serialVersionUID = 3776720187248809713L;
+
+ public NoneAsyncEmitter(Subscriber super T> actual) {
+ super(actual);
+ }
+
+ @Override
+ public void onNext(T t) {
+ if (actual.isUnsubscribed()) {
+ return;
+ }
+
+ actual.onNext(t);
+
+ for (;;) {
+ long r = get();
+ if (r == 0L || compareAndSet(r, r - 1)) {
+ return;
+ }
+ }
+ }
+
+ }
+
+ static abstract class NoOverflowBaseAsyncEmitter extends BaseAsyncEmitter {
+
+ /** */
+ private static final long serialVersionUID = 4127754106204442833L;
+
+ public NoOverflowBaseAsyncEmitter(Subscriber super T> actual) {
+ super(actual);
+ }
+
+ @Override
+ public final void onNext(T t) {
+ if (actual.isUnsubscribed()) {
+ return;
+ }
+
+ if (get() != 0) {
+ actual.onNext(t);
+ BackpressureUtils.produced(this, 1);
+ } else {
+ onOverflow();
+ }
+ }
+
+ abstract void onOverflow();
+ }
+
+ static final class DropAsyncEmitter extends NoOverflowBaseAsyncEmitter {
+
+ /** */
+ private static final long serialVersionUID = 8360058422307496563L;
+
+ public DropAsyncEmitter(Subscriber super T> actual) {
+ super(actual);
+ }
+
+ @Override
+ void onOverflow() {
+ // nothing to do
+ }
+
+ }
+
+ static final class ErrorAsyncEmitter extends NoOverflowBaseAsyncEmitter {
+
+ /** */
+ private static final long serialVersionUID = 338953216916120960L;
+
+ public ErrorAsyncEmitter(Subscriber super T> actual) {
+ super(actual);
+ }
+
+ @Override
+ void onOverflow() {
+ onError(new MissingBackpressureException("fromAsync: could not emit value due to lack of requests"));
+ }
+
+ }
+
+ static final class BufferAsyncEmitter extends BaseAsyncEmitter {
+
+ /** */
+ private static final long serialVersionUID = 2427151001689639875L;
+
+ final Queue