io.vertx.rx.java.ReadStreamSubscriber Maven / Gradle / Ivy
package io.vertx.rx.java;
import io.vertx.core.Handler;
import io.vertx.core.streams.ReadStream;
import rx.Observable;
import rx.Subscriber;
import java.util.ArrayDeque;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* An RxJava {@code Subscriber} that turns an {@code Observable} into a {@link ReadStream}.
*
* The stream implements the {@link #pause()} and {@link #resume()} operation by maintaining
* a buffer of {@link #BUFFER_SIZE} elements between the {@code Observable} and the {@code ReadStream}.
*
* When the subscriber is created it requests {@code 0} elements to activate the subscriber's back-pressure.
* Setting the handler initially on the {@code ReadStream} triggers a request of {@link #BUFFER_SIZE} elements.
* When the item buffer is half empty, new elements are requested to fill the buffer back to {@link #BUFFER_SIZE}
* elements.
*
* The {@link #endHandler(Handler)} is called when the {@code Observable} is completed or has failed and
* no pending elements, emitted before the completion or failure, are still in the buffer, i.e the handler
* is not called when the stream is paused.
*
* @author Julien Viet
*/
public class ReadStreamSubscriber extends Subscriber implements ReadStream {
private static final Runnable NOOP_ACTION = () -> {};
private static final Throwable DONE_SENTINEL = new Throwable();
public static final int BUFFER_SIZE = 16;
public static ReadStream asReadStream(Observable observable, Function adapter) {
return new ReadStreamSubscriber<>(adapter, observable::subscribe);
}
private final Function adapter;
private Handler endHandler;
private Handler exceptionHandler;
private Handler elementHandler;
private long demand = Long.MAX_VALUE;
private Throwable completed;
private ArrayDeque pending = new ArrayDeque<>();
private int requested = 0;
private Consumer> doSubscribe;
private Thread emitting;
public ReadStreamSubscriber(Function adapter, Consumer> doSubscribe) {
this.adapter = adapter;
this.doSubscribe = doSubscribe;
request(0);
}
@Override
public ReadStream handler(Handler handler) {
Runnable action;
synchronized (this) {
elementHandler = handler;
if (handler != null) {
action = () -> doSubscribe.accept(this);
} else {
action = this::unsubscribe;
}
}
action.run();
serializedCheckStatus();
return this;
}
@Override
public ReadStream pause() {
synchronized (this) {
demand = 0L;
}
return this;
}
@Override
public ReadStream fetch(long amount) {
if (amount < 0L) {
throw new IllegalArgumentException("Invalid amount: " + amount);
}
synchronized (this) {
demand += amount;
if (demand < 0L) {
demand = Long.MAX_VALUE;
}
}
serializedCheckStatus();
return this;
}
@Override
public ReadStream resume() {
return fetch(Long.MAX_VALUE);
}
/** Ensure checkStatus is never called concurrently. */
private void serializedCheckStatus() {
synchronized (this) {
if (emitting != null && emitting != Thread.currentThread()) {
return;
}
emitting = Thread.currentThread();
}
try {
checkStatus();
} finally {
synchronized (this) {
emitting = null;
}
}
}
private void checkStatus() {
Runnable action = NOOP_ACTION;
while (true) {
J adapted;
Handler handler;
synchronized (this) {
if (demand > 0L && (handler = elementHandler) != null && pending.size() > 0) {
if (demand != Long.MAX_VALUE) {
demand--;
}
requested--;
R item = pending.poll();
adapted = adapter.apply(item);
} else {
if (completed != null) {
if (pending.isEmpty()) {
Handler onError;
Throwable result;
if (completed != DONE_SENTINEL) {
onError = exceptionHandler;
result = completed;
exceptionHandler = null;
} else {
onError = null;
result = null;
}
Handler onCompleted = endHandler;
endHandler = null;
action = () -> {
try {
if (onError != null) {
onError.handle(result);
}
} finally {
if (onCompleted != null) {
onCompleted.handle(null);
}
}
};
}
} else if (elementHandler != null && requested < BUFFER_SIZE / 2) {
int request = BUFFER_SIZE - requested;
action = () -> request(request);
requested = BUFFER_SIZE;
}
break;
}
}
handler.handle(adapted);
}
action.run();
}
@Override
public ReadStream endHandler(Handler handler) {
synchronized (this) {
if (completed == null || pending.size() > 0) {
endHandler = handler;
} else {
if (handler != null) {
throw new IllegalStateException();
}
}
}
return this;
}
@Override
public ReadStream exceptionHandler(Handler handler) {
synchronized (this) {
if (completed == null || pending.size() > 0) {
exceptionHandler = handler;
} else {
if (handler != null) {
throw new IllegalStateException();
}
}
}
return this;
}
@Override
public void onCompleted() {
onError(DONE_SENTINEL);
}
@Override
public void onError(Throwable e) {
synchronized (this) {
if (completed != null) {
return;
}
completed = e;
}
serializedCheckStatus();
}
@Override
public void onNext(R item) {
synchronized (this) {
pending.add(item);
}
serializedCheckStatus();
}
}