io.datakernel.datastream.processor.StreamLateBinder Maven / Gradle / Ivy
package io.datakernel.datastream.processor;
import io.datakernel.datastream.*;
import io.datakernel.promise.Promise;
import io.datakernel.promise.SettablePromise;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Set;
import static io.datakernel.common.Utils.nullify;
import static io.datakernel.datastream.StreamCapability.LATE_BINDING;
/**
* If stream consumer is not immediately wired, on next eventloop cycle it will error out.
* This is because consumers request suppliers to start producing items on the next cycle after they're wired.
*
* This transformer solves that by storing a data receiver from consumer produce request if it is not wired
* and when it is actually wired request his new supplier to produce into that stored receiver.
*/
public final class StreamLateBinder implements StreamTransformer {
private final AbstractStreamConsumer input = new Input();
private final AbstractStreamSupplier output = new Output();
private int countdown = 2;
@Nullable
private StreamDataAcceptor pendingAcceptor;
@Nullable
private Throwable pendingException;
@Nullable
private SettablePromise pendingEndOfStreamAck;
// region creators
private StreamLateBinder() {
}
public static StreamLateBinder create() {
return new StreamLateBinder<>();
}
// endregion
private class Input extends AbstractStreamConsumer {
@Override
protected void onStarted() {
if (--countdown == 0) {
startInputOutput();
}
}
@Override
protected Promise onEndOfStream() {
if (countdown == 0) {
return output.sendEndOfStream();
} else {
pendingEndOfStreamAck = new SettablePromise<>();
return pendingEndOfStreamAck;
}
}
@Override
protected void onError(Throwable e) {
if (countdown == 0) {
output.close(e);
} else {
pendingException = e;
}
}
@Override
public Set getCapabilities() {
return extendCapabilities(output.getConsumer(), LATE_BINDING);
}
}
private class Output extends AbstractStreamSupplier {
@Override
protected void onStarted() {
if (--countdown == 0) {
startInputOutput();
}
}
@Override
protected void onProduce(@NotNull StreamDataAcceptor dataAcceptor) {
if (countdown == 0) {
input.getSupplier().resume(dataAcceptor);
} else {
pendingAcceptor = dataAcceptor;
}
}
@Override
protected void onSuspended() {
if (countdown == 0) {
input.getSupplier().suspend();
} else {
pendingAcceptor = null;
}
}
@Override
protected void onError(Throwable e) {
if (countdown == 0) {
input.close(e);
} else {
pendingException = e;
}
}
@Override
public Set getCapabilities() {
return extendCapabilities(input.getSupplier(), LATE_BINDING);
}
}
private void startInputOutput() {
if (pendingException != null) {
input.close(pendingException);
output.close(pendingException);
pendingAcceptor = null;
pendingEndOfStreamAck = nullify(pendingEndOfStreamAck, SettablePromise::setException, pendingException);
pendingException = null;
}
if (pendingEndOfStreamAck != null) {
output.sendEndOfStream()
.whenComplete(pendingEndOfStreamAck);
pendingAcceptor = null;
}
if (pendingAcceptor != null) {
input.getSupplier().resume(pendingAcceptor);
pendingAcceptor = null;
}
}
@Override
public StreamConsumer getInput() {
return input;
}
@Override
public StreamSupplier getOutput() {
return output;
}
}