tdlib-session-container/src/main/java/it/tdlight/reactiveapi/rsocket/ConsumerConnection.java

197 lines
8.1 KiB
Java
Raw Normal View History

2022-10-06 19:06:35 +02:00
package it.tdlight.reactiveapi.rsocket;
import io.rsocket.Payload;
import it.tdlight.reactiveapi.Deserializer;
2022-10-06 19:06:35 +02:00
import it.tdlight.reactiveapi.Timestamped;
import java.time.Duration;
2022-10-07 00:48:10 +02:00
import java.util.Optional;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
2022-10-06 19:06:35 +02:00
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
2022-10-07 00:48:10 +02:00
import reactor.core.publisher.SignalType;
2022-10-06 19:06:35 +02:00
import reactor.core.publisher.Sinks;
import reactor.core.publisher.Sinks.EmitFailureHandler;
import reactor.core.publisher.Sinks.Empty;
2022-10-11 20:08:40 +02:00
import reactor.core.publisher.Sinks.Many;
2022-10-06 19:06:35 +02:00
import reactor.core.scheduler.Schedulers;
2022-10-11 20:08:40 +02:00
import reactor.util.concurrent.Queues;
2022-10-06 19:06:35 +02:00
2022-10-07 00:48:10 +02:00
public class ConsumerConnection<T> {
2022-10-06 19:06:35 +02:00
2022-10-07 00:48:10 +02:00
private static final Logger LOG = LogManager.getLogger(ConsumerConnection.class);
2022-10-06 19:06:35 +02:00
2022-10-07 00:48:10 +02:00
private final String channel;
2022-10-11 20:08:40 +02:00
private final int bufferSize;
private Many<Flux<Payload>> remotes = Sinks.many().replay().all();
private int remoteCount = 0;
2022-10-06 19:06:35 +02:00
2022-10-07 00:48:10 +02:00
private Deserializer<T> local;
2022-10-06 19:06:35 +02:00
2022-10-07 00:48:10 +02:00
private boolean connectedState = false;
private Empty<Void> connectedSink = Sinks.empty();
private Optional<Throwable> localTerminationState = null;
private Empty<Void> localTerminationSink = Sinks.empty();
2022-10-06 19:06:35 +02:00
2022-10-11 20:08:40 +02:00
public ConsumerConnection(String channel, int bufferSize) {
2022-10-07 00:48:10 +02:00
this.channel = channel;
2022-10-11 20:08:40 +02:00
this.bufferSize = bufferSize;
2022-10-07 00:48:10 +02:00
if (LOG.isDebugEnabled()) LOG.debug("{} Create new blank connection", this.printStatus());
}
2022-10-06 19:06:35 +02:00
2022-10-07 00:48:10 +02:00
private String printStatus() {
return "[\"%s\" (%d)%s%s%s]".formatted(channel,
System.identityHashCode(this),
local != null ? ", local" : "",
2022-10-11 20:08:40 +02:00
remoteCount > 0 ? (remoteCount > 1 ? ", " + remoteCount + " remotes" : ", 1 remote") : "",
2022-10-07 00:48:10 +02:00
connectedState ? ((localTerminationState != null) ? (localTerminationState.isPresent() ? ", done with error" : ", done") : ", connected") : ", waiting"
);
2022-10-06 19:06:35 +02:00
}
public synchronized Flux<Timestamped<T>> connectLocal() {
2022-10-07 00:48:10 +02:00
if (LOG.isDebugEnabled()) LOG.debug("{} Local is asking to connect", this.printStatus());
return Mono.defer(() -> {
synchronized (ConsumerConnection.this) {
return connectedSink.asMono();
}
}).publishOn(Schedulers.parallel()).thenMany(Flux.defer(() -> {
2022-10-06 19:06:35 +02:00
synchronized (ConsumerConnection.this) {
2022-10-07 00:48:10 +02:00
if (LOG.isDebugEnabled()) LOG.debug("{} Local is connected", this.printStatus());
2022-10-11 20:08:40 +02:00
return Flux.merge(remotes.asFlux().map(remote -> {
return remote.doOnError(ex -> {
synchronized (ConsumerConnection.this) {
if (remoteCount <= 1) {
if (remoteCount > 0 && localTerminationState == null) {
localTerminationState = Optional.of(ex);
if (LOG.isDebugEnabled()) {
LOG.debug("%s Local connection ended with failure".formatted(this.printStatus()), ex);
}
if (remoteCount <= 1) {
var sink = localTerminationSink;
reset();
sink.emitError(ex, EmitFailureHandler.FAIL_FAST);
if (LOG.isDebugEnabled()) {
LOG.debug("%s Local connection ended with failure, emitted termination failure".formatted(this.printStatus()));
}
}
}
} else {
remoteCount--;
if (LOG.isDebugEnabled()) {
LOG.debug("%s Local connection ended with failure, but at least one remote is still online".formatted(
this.printStatus()));
}
}
}
}).doFinally(s -> {
if (s != SignalType.ON_ERROR) {
synchronized (ConsumerConnection.this) {
if (LOG.isDebugEnabled()) LOG.debug("{} Remote connection ending with status {}", this.printStatus(), s);
if (remoteCount <= 1) {
if (remoteCount > 0 && localTerminationState == null) {
assert connectedState;
localTerminationState = Optional.empty();
if (s == SignalType.CANCEL) {
localTerminationSink.emitError(new CancelledChannelException(), EmitFailureHandler.FAIL_FAST);
} else {
localTerminationSink.emitEmpty(EmitFailureHandler.FAIL_FAST);
}
}
reset();
if (LOG.isDebugEnabled()) LOG.debug("{} Remote connection ended with status {}, emitted termination complete", this.printStatus(), s);
} else {
remoteCount--;
if (LOG.isDebugEnabled()) LOG.debug("{} Remote connection ended with status {}, but at least one remote is still online", this.printStatus(), s);
}
}
}
}).onErrorResume(ex -> {
synchronized (ConsumerConnection.this) {
if (remoteCount <= 1) {
return Flux.error(ex);
} else {
return Flux.empty();
}
}
});
}), Integer.MAX_VALUE, bufferSize)
.transform(remote -> RSocketUtils.deserialize(remote, local))
2022-10-07 00:48:10 +02:00
.map(element -> new Timestamped<>(System.currentTimeMillis(), element));
2022-10-06 19:06:35 +02:00
}
2022-10-11 20:08:40 +02:00
}));
2022-10-06 19:06:35 +02:00
}
public synchronized Mono<Void> connectRemote() {
2022-10-07 00:48:10 +02:00
if (LOG.isDebugEnabled()) LOG.debug("{} Remote is asking to connect", this.printStatus());
return Mono.defer(() -> {
2022-10-06 19:06:35 +02:00
synchronized (ConsumerConnection.this) {
2022-10-07 00:48:10 +02:00
return connectedSink.asMono();
2022-10-06 19:06:35 +02:00
}
2022-10-07 00:48:10 +02:00
}).publishOn(Schedulers.parallel()).then(Mono.defer(() -> {
synchronized (ConsumerConnection.this) {
if (LOG.isDebugEnabled()) LOG.debug("{} Remote is connected", this.printStatus());
return localTerminationSink.asMono().publishOn(Schedulers.parallel());
}
2022-10-11 20:08:40 +02:00
}));
2022-10-06 19:06:35 +02:00
}
2022-10-11 20:08:40 +02:00
public synchronized void reset() {
2022-10-07 00:48:10 +02:00
if (LOG.isDebugEnabled()) LOG.debug("{} Reset started", this.printStatus());
if (connectedState) {
if (localTerminationState == null) {
if (LOG.isDebugEnabled()) LOG.debug("{} The previous connection is still marked as open but not terminated, interrupting it", this.printStatus());
var ex = new InterruptedException();
localTerminationState = Optional.of(ex);
localTerminationSink.emitError(ex, EmitFailureHandler.busyLooping(Duration.ofMillis(100)));
if (LOG.isDebugEnabled()) LOG.debug("{} The previous connection has been interrupted", this.printStatus());
}
} else {
if (LOG.isDebugEnabled()) LOG.debug("{} The previous connection is still marked as waiting for a connection, interrupting it", this.printStatus());
localTerminationState = Optional.empty();
localTerminationSink.emitEmpty(EmitFailureHandler.busyLooping(Duration.ofMillis(100)));
if (LOG.isDebugEnabled()) LOG.debug("{} The previous connection has been interrupted", this.printStatus());
}
2022-10-06 19:06:35 +02:00
local = null;
2022-10-11 20:08:40 +02:00
remoteCount = 0;
remotes.emitComplete(EmitFailureHandler.FAIL_FAST);
remotes = Sinks.many().replay().all();
2022-10-07 00:48:10 +02:00
connectedState = false;
connectedSink = Sinks.empty();
localTerminationState = null;
localTerminationSink = Sinks.empty();
if (LOG.isDebugEnabled()) LOG.debug("{} Reset ended", this.printStatus());
2022-10-06 19:06:35 +02:00
}
public synchronized void registerRemote(Flux<Payload> remote) {
2022-10-07 00:48:10 +02:00
if (LOG.isDebugEnabled()) LOG.debug("{} Remote is trying to register", this.printStatus());
2022-10-11 20:08:40 +02:00
this.remoteCount++;
this.remotes.emitNext(remote, EmitFailureHandler.FAIL_FAST);
2022-10-07 00:48:10 +02:00
if (LOG.isDebugEnabled()) LOG.debug("{} Remote registered", this.printStatus());
2022-10-06 19:06:35 +02:00
onChanged();
}
2022-10-11 20:08:40 +02:00
public synchronized Throwable registerLocal(Deserializer<T> local) {
2022-10-07 00:48:10 +02:00
if (LOG.isDebugEnabled()) LOG.debug("{} Local is trying to register", this.printStatus());
2022-10-06 19:06:35 +02:00
if (this.local != null) {
2022-10-07 00:48:10 +02:00
if (LOG.isDebugEnabled()) LOG.debug("{} Local was already registered", this.printStatus());
2022-10-11 20:08:40 +02:00
return new IllegalStateException("Local is already registered");
2022-10-06 19:06:35 +02:00
}
this.local = local;
2022-10-07 00:48:10 +02:00
if (LOG.isDebugEnabled()) LOG.debug("{} Local registered", this.printStatus());
2022-10-06 19:06:35 +02:00
onChanged();
2022-10-11 20:08:40 +02:00
return null;
2022-10-06 19:06:35 +02:00
}
private synchronized void onChanged() {
2022-10-07 00:48:10 +02:00
if (LOG.isDebugEnabled()) LOG.debug("{} Checking connection changes", this.printStatus());
2022-10-11 20:08:40 +02:00
if (local != null && remoteCount > 0) {
2022-10-07 00:48:10 +02:00
connectedState = true;
if (LOG.isDebugEnabled()) LOG.debug("{} Connected successfully! Emitting connected event", this.printStatus());
2022-10-11 20:08:40 +02:00
connectedSink.emitEmpty(EmitFailureHandler.FAIL_FAST);
2022-10-07 00:48:10 +02:00
if (LOG.isDebugEnabled()) LOG.debug("{} Connected successfully! Emitted connected event", this.printStatus());
} else {
if (LOG.isDebugEnabled()) LOG.debug("{} Still not connected", this.printStatus());
2022-10-06 19:06:35 +02:00
}
}
}