Accept multiple tdlib clients
This commit is contained in:
parent
a8510ed336
commit
24b4387b08
@ -13,14 +13,18 @@ import reactor.core.publisher.SignalType;
|
|||||||
import reactor.core.publisher.Sinks;
|
import reactor.core.publisher.Sinks;
|
||||||
import reactor.core.publisher.Sinks.EmitFailureHandler;
|
import reactor.core.publisher.Sinks.EmitFailureHandler;
|
||||||
import reactor.core.publisher.Sinks.Empty;
|
import reactor.core.publisher.Sinks.Empty;
|
||||||
|
import reactor.core.publisher.Sinks.Many;
|
||||||
import reactor.core.scheduler.Schedulers;
|
import reactor.core.scheduler.Schedulers;
|
||||||
|
import reactor.util.concurrent.Queues;
|
||||||
|
|
||||||
public class ConsumerConnection<T> {
|
public class ConsumerConnection<T> {
|
||||||
|
|
||||||
private static final Logger LOG = LogManager.getLogger(ConsumerConnection.class);
|
private static final Logger LOG = LogManager.getLogger(ConsumerConnection.class);
|
||||||
|
|
||||||
private final String channel;
|
private final String channel;
|
||||||
private Flux<Payload> remote;
|
private final int bufferSize;
|
||||||
|
private Many<Flux<Payload>> remotes = Sinks.many().replay().all();
|
||||||
|
private int remoteCount = 0;
|
||||||
|
|
||||||
private Deserializer<T> local;
|
private Deserializer<T> local;
|
||||||
|
|
||||||
@ -29,8 +33,9 @@ public class ConsumerConnection<T> {
|
|||||||
private Optional<Throwable> localTerminationState = null;
|
private Optional<Throwable> localTerminationState = null;
|
||||||
private Empty<Void> localTerminationSink = Sinks.empty();
|
private Empty<Void> localTerminationSink = Sinks.empty();
|
||||||
|
|
||||||
public ConsumerConnection(String channel) {
|
public ConsumerConnection(String channel, int bufferSize) {
|
||||||
this.channel = channel;
|
this.channel = channel;
|
||||||
|
this.bufferSize = bufferSize;
|
||||||
if (LOG.isDebugEnabled()) LOG.debug("{} Create new blank connection", this.printStatus());
|
if (LOG.isDebugEnabled()) LOG.debug("{} Create new blank connection", this.printStatus());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,7 +43,7 @@ public class ConsumerConnection<T> {
|
|||||||
return "[\"%s\" (%d)%s%s%s]".formatted(channel,
|
return "[\"%s\" (%d)%s%s%s]".formatted(channel,
|
||||||
System.identityHashCode(this),
|
System.identityHashCode(this),
|
||||||
local != null ? ", local" : "",
|
local != null ? ", local" : "",
|
||||||
remote != null ? ", remote" : "",
|
remoteCount > 0 ? (remoteCount > 1 ? ", " + remoteCount + " remotes" : ", 1 remote") : "",
|
||||||
connectedState ? ((localTerminationState != null) ? (localTerminationState.isPresent() ? ", done with error" : ", done") : ", connected") : ", waiting"
|
connectedState ? ((localTerminationState != null) ? (localTerminationState.isPresent() ? ", done with error" : ", done") : ", connected") : ", waiting"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -52,38 +57,68 @@ public class ConsumerConnection<T> {
|
|||||||
}).publishOn(Schedulers.parallel()).thenMany(Flux.defer(() -> {
|
}).publishOn(Schedulers.parallel()).thenMany(Flux.defer(() -> {
|
||||||
synchronized (ConsumerConnection.this) {
|
synchronized (ConsumerConnection.this) {
|
||||||
if (LOG.isDebugEnabled()) LOG.debug("{} Local is connected", this.printStatus());
|
if (LOG.isDebugEnabled()) LOG.debug("{} Local is connected", this.printStatus());
|
||||||
return RSocketUtils.deserialize(remote, local)
|
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))
|
||||||
.map(element -> new Timestamped<>(System.currentTimeMillis(), element));
|
.map(element -> new Timestamped<>(System.currentTimeMillis(), element));
|
||||||
}
|
}
|
||||||
})).doOnError(ex -> {
|
}));
|
||||||
synchronized (ConsumerConnection.this) {
|
|
||||||
if (remote != null && localTerminationState == null) {
|
|
||||||
localTerminationState = Optional.of(ex);
|
|
||||||
if (LOG.isDebugEnabled()) LOG.debug("%s Local connection ended with failure, emitting termination failure".formatted(this.printStatus()), ex);
|
|
||||||
var sink = localTerminationSink;
|
|
||||||
reset(true);
|
|
||||||
sink.emitError(ex, EmitFailureHandler.busyLooping(Duration.ofMillis(100)));
|
|
||||||
if (LOG.isDebugEnabled()) LOG.debug("%s Local connection ended with failure, emitted termination failure".formatted(this.printStatus()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).doFinally(s -> {
|
|
||||||
if (s != SignalType.ON_ERROR) {
|
|
||||||
synchronized (ConsumerConnection.this) {
|
|
||||||
if (remote != null && localTerminationState == null) {
|
|
||||||
assert connectedState;
|
|
||||||
localTerminationState = Optional.empty();
|
|
||||||
LOG.debug("{} Remote connection ended with status {}, emitting termination complete", this::printStatus, () -> s);
|
|
||||||
if (s == SignalType.CANCEL) {
|
|
||||||
localTerminationSink.emitError(new CancelledChannelException(), EmitFailureHandler.busyLooping(Duration.ofMillis(100)));
|
|
||||||
} else {
|
|
||||||
localTerminationSink.emitEmpty(EmitFailureHandler.busyLooping(Duration.ofMillis(100)));
|
|
||||||
}
|
|
||||||
LOG.debug("{} Remote connection ended with status {}, emitted termination complete", this::printStatus, () -> s);
|
|
||||||
}
|
|
||||||
reset(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized Mono<Void> connectRemote() {
|
public synchronized Mono<Void> connectRemote() {
|
||||||
@ -97,16 +132,10 @@ public class ConsumerConnection<T> {
|
|||||||
if (LOG.isDebugEnabled()) LOG.debug("{} Remote is connected", this.printStatus());
|
if (LOG.isDebugEnabled()) LOG.debug("{} Remote is connected", this.printStatus());
|
||||||
return localTerminationSink.asMono().publishOn(Schedulers.parallel());
|
return localTerminationSink.asMono().publishOn(Schedulers.parallel());
|
||||||
}
|
}
|
||||||
})).doFinally(s -> {
|
}));
|
||||||
if (s != SignalType.ON_ERROR) {
|
|
||||||
synchronized (ConsumerConnection.this) {
|
|
||||||
//reset(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void reset(boolean resettingFromRemote) {
|
public synchronized void reset() {
|
||||||
if (LOG.isDebugEnabled()) LOG.debug("{} Reset started", this.printStatus());
|
if (LOG.isDebugEnabled()) LOG.debug("{} Reset started", this.printStatus());
|
||||||
if (connectedState) {
|
if (connectedState) {
|
||||||
if (localTerminationState == null) {
|
if (localTerminationState == null) {
|
||||||
@ -123,7 +152,9 @@ public class ConsumerConnection<T> {
|
|||||||
if (LOG.isDebugEnabled()) LOG.debug("{} The previous connection has been interrupted", this.printStatus());
|
if (LOG.isDebugEnabled()) LOG.debug("{} The previous connection has been interrupted", this.printStatus());
|
||||||
}
|
}
|
||||||
local = null;
|
local = null;
|
||||||
remote = null;
|
remoteCount = 0;
|
||||||
|
remotes.emitComplete(EmitFailureHandler.FAIL_FAST);
|
||||||
|
remotes = Sinks.many().replay().all();
|
||||||
connectedState = false;
|
connectedState = false;
|
||||||
connectedSink = Sinks.empty();
|
connectedSink = Sinks.empty();
|
||||||
localTerminationState = null;
|
localTerminationState = null;
|
||||||
@ -133,32 +164,30 @@ public class ConsumerConnection<T> {
|
|||||||
|
|
||||||
public synchronized void registerRemote(Flux<Payload> remote) {
|
public synchronized void registerRemote(Flux<Payload> remote) {
|
||||||
if (LOG.isDebugEnabled()) LOG.debug("{} Remote is trying to register", this.printStatus());
|
if (LOG.isDebugEnabled()) LOG.debug("{} Remote is trying to register", this.printStatus());
|
||||||
if (this.remote != null) {
|
this.remoteCount++;
|
||||||
if (LOG.isDebugEnabled()) LOG.debug("{} Remote was already registered", this.printStatus());
|
this.remotes.emitNext(remote, EmitFailureHandler.FAIL_FAST);
|
||||||
throw new IllegalStateException("Remote is already registered");
|
|
||||||
}
|
|
||||||
this.remote = remote;
|
|
||||||
if (LOG.isDebugEnabled()) LOG.debug("{} Remote registered", this.printStatus());
|
if (LOG.isDebugEnabled()) LOG.debug("{} Remote registered", this.printStatus());
|
||||||
onChanged();
|
onChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void registerLocal(Deserializer<T> local) {
|
public synchronized Throwable registerLocal(Deserializer<T> local) {
|
||||||
if (LOG.isDebugEnabled()) LOG.debug("{} Local is trying to register", this.printStatus());
|
if (LOG.isDebugEnabled()) LOG.debug("{} Local is trying to register", this.printStatus());
|
||||||
if (this.local != null) {
|
if (this.local != null) {
|
||||||
if (LOG.isDebugEnabled()) LOG.debug("{} Local was already registered", this.printStatus());
|
if (LOG.isDebugEnabled()) LOG.debug("{} Local was already registered", this.printStatus());
|
||||||
throw new IllegalStateException("Local is already registered");
|
return new IllegalStateException("Local is already registered");
|
||||||
}
|
}
|
||||||
this.local = local;
|
this.local = local;
|
||||||
if (LOG.isDebugEnabled()) LOG.debug("{} Local registered", this.printStatus());
|
if (LOG.isDebugEnabled()) LOG.debug("{} Local registered", this.printStatus());
|
||||||
onChanged();
|
onChanged();
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void onChanged() {
|
private synchronized void onChanged() {
|
||||||
if (LOG.isDebugEnabled()) LOG.debug("{} Checking connection changes", this.printStatus());
|
if (LOG.isDebugEnabled()) LOG.debug("{} Checking connection changes", this.printStatus());
|
||||||
if (local != null && remote != null) {
|
if (local != null && remoteCount > 0) {
|
||||||
connectedState = true;
|
connectedState = true;
|
||||||
if (LOG.isDebugEnabled()) LOG.debug("{} Connected successfully! Emitting connected event", this.printStatus());
|
if (LOG.isDebugEnabled()) LOG.debug("{} Connected successfully! Emitting connected event", this.printStatus());
|
||||||
connectedSink.emitEmpty(EmitFailureHandler.busyLooping(Duration.ofMillis(100)));
|
connectedSink.emitEmpty(EmitFailureHandler.FAIL_FAST);
|
||||||
if (LOG.isDebugEnabled()) LOG.debug("{} Connected successfully! Emitted connected event", this.printStatus());
|
if (LOG.isDebugEnabled()) LOG.debug("{} Connected successfully! Emitted connected event", this.printStatus());
|
||||||
} else {
|
} else {
|
||||||
if (LOG.isDebugEnabled()) LOG.debug("{} Still not connected", this.printStatus());
|
if (LOG.isDebugEnabled()) LOG.debug("{} Still not connected", this.printStatus());
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package it.tdlight.reactiveapi.rsocket;
|
package it.tdlight.reactiveapi.rsocket;
|
||||||
|
|
||||||
|
import static reactor.util.concurrent.Queues.XS_BUFFER_SIZE;
|
||||||
|
|
||||||
import com.google.common.net.HostAndPort;
|
import com.google.common.net.HostAndPort;
|
||||||
import io.rsocket.ConnectionSetupPayload;
|
import io.rsocket.ConnectionSetupPayload;
|
||||||
import io.rsocket.Payload;
|
import io.rsocket.Payload;
|
||||||
@ -32,6 +34,7 @@ public class MyRSocketServer implements RSocketChannelManager, RSocket {
|
|||||||
|
|
||||||
private final Logger logger = LogManager.getLogger(this.getClass());
|
private final Logger logger = LogManager.getLogger(this.getClass());
|
||||||
|
|
||||||
|
private final int bufferSize;
|
||||||
private final Mono<CloseableChannel> serverCloseable;
|
private final Mono<CloseableChannel> serverCloseable;
|
||||||
|
|
||||||
protected final Map<String, ConsumerConnection<?>> consumerRegistry = new ConcurrentHashMap<>();
|
protected final Map<String, ConsumerConnection<?>> consumerRegistry = new ConcurrentHashMap<>();
|
||||||
@ -39,6 +42,11 @@ public class MyRSocketServer implements RSocketChannelManager, RSocket {
|
|||||||
protected final Map<String, ProducerConnection<?>> producerRegistry = new ConcurrentHashMap<>();
|
protected final Map<String, ProducerConnection<?>> producerRegistry = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
public MyRSocketServer(HostAndPort baseHost) {
|
public MyRSocketServer(HostAndPort baseHost) {
|
||||||
|
this(baseHost, XS_BUFFER_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MyRSocketServer(HostAndPort baseHost, int bufferSize) {
|
||||||
|
this.bufferSize = bufferSize;
|
||||||
var serverMono = RSocketServer
|
var serverMono = RSocketServer
|
||||||
.create(new SocketAcceptor() {
|
.create(new SocketAcceptor() {
|
||||||
@Override
|
@Override
|
||||||
@ -71,7 +79,8 @@ public class MyRSocketServer implements RSocketChannelManager, RSocket {
|
|||||||
return Mono.error(new CancelledChannelException("Metadata is wrong"));
|
return Mono.error(new CancelledChannelException("Metadata is wrong"));
|
||||||
}
|
}
|
||||||
var channel = firstValue.getDataUtf8();
|
var channel = firstValue.getDataUtf8();
|
||||||
var conn = MyRSocketServer.this.consumerRegistry.computeIfAbsent(channel, ConsumerConnection::new);
|
var conn = MyRSocketServer.this.consumerRegistry.computeIfAbsent(channel,
|
||||||
|
ch -> new ConsumerConnection<>(ch, bufferSize));
|
||||||
conn.registerRemote(flux.skip(1));
|
conn.registerRemote(flux.skip(1));
|
||||||
return conn.connectRemote().then(Mono.fromSupplier(() -> DefaultPayload.create("ok", "result")));
|
return conn.connectRemote().then(Mono.fromSupplier(() -> DefaultPayload.create("ok", "result")));
|
||||||
} else {
|
} else {
|
||||||
@ -84,7 +93,8 @@ public class MyRSocketServer implements RSocketChannelManager, RSocket {
|
|||||||
public @NotNull Flux<Payload> requestStream(@NotNull Payload payload) {
|
public @NotNull Flux<Payload> requestStream(@NotNull Payload payload) {
|
||||||
var channel = payload.getDataUtf8();
|
var channel = payload.getDataUtf8();
|
||||||
return Flux.defer(() -> {
|
return Flux.defer(() -> {
|
||||||
var conn = MyRSocketServer.this.producerRegistry.computeIfAbsent(channel, ProducerConnection::new);
|
var conn = MyRSocketServer.this.producerRegistry.computeIfAbsent(channel,
|
||||||
|
ch -> new ProducerConnection<>(ch, bufferSize));
|
||||||
conn.registerRemote();
|
conn.registerRemote();
|
||||||
return conn.connectRemote();
|
return conn.connectRemote();
|
||||||
});
|
});
|
||||||
@ -105,8 +115,12 @@ public class MyRSocketServer implements RSocketChannelManager, RSocket {
|
|||||||
public Flux<Timestamped<K>> consumeMessages() {
|
public Flux<Timestamped<K>> consumeMessages() {
|
||||||
return serverCloseable.flatMapMany(x -> {
|
return serverCloseable.flatMapMany(x -> {
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
var conn = (ConsumerConnection<K>) consumerRegistry.computeIfAbsent(channelName, ConsumerConnection::new);
|
var conn = (ConsumerConnection<K>) consumerRegistry.computeIfAbsent(channelName,
|
||||||
conn.registerLocal(deserializer);
|
ch -> new ConsumerConnection<>(ch, bufferSize));
|
||||||
|
Throwable ex = conn.registerLocal(deserializer);
|
||||||
|
if (ex != null) {
|
||||||
|
return Flux.error(ex);
|
||||||
|
}
|
||||||
return conn.connectLocal();
|
return conn.connectLocal();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -123,12 +137,13 @@ public class MyRSocketServer implements RSocketChannelManager, RSocket {
|
|||||||
logger.error("Failed to create codec for channel \"{}\"", channelName, ex);
|
logger.error("Failed to create codec for channel \"{}\"", channelName, ex);
|
||||||
throw new IllegalStateException("Failed to create codec for channel " + channelName);
|
throw new IllegalStateException("Failed to create codec for channel " + channelName);
|
||||||
}
|
}
|
||||||
return new EventProducer<K>() {
|
return new EventProducer<>() {
|
||||||
@Override
|
@Override
|
||||||
public Mono<Void> sendMessages(Flux<K> eventsFlux) {
|
public Mono<Void> sendMessages(Flux<K> eventsFlux) {
|
||||||
return serverCloseable.flatMap(x -> {
|
return serverCloseable.flatMap(x -> {
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
var conn = (ProducerConnection<K>) producerRegistry.computeIfAbsent(channelName, ProducerConnection::new);
|
var conn = (ProducerConnection<K>) producerRegistry.computeIfAbsent(channelName,
|
||||||
|
ch -> new ProducerConnection<>(ch, bufferSize));
|
||||||
conn.registerLocal(eventsFlux.transform(flux -> RSocketUtils.serialize(flux, serializer)));
|
conn.registerLocal(eventsFlux.transform(flux -> RSocketUtils.serialize(flux, serializer)));
|
||||||
return conn.connectLocal();
|
return conn.connectLocal();
|
||||||
});
|
});
|
||||||
|
@ -7,12 +7,11 @@ import org.apache.logging.log4j.LogManager;
|
|||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import reactor.core.publisher.Signal;
|
|
||||||
import reactor.core.publisher.SignalType;
|
import reactor.core.publisher.SignalType;
|
||||||
import reactor.core.publisher.Sinks;
|
import reactor.core.publisher.Sinks;
|
||||||
import reactor.core.publisher.Sinks.EmitFailureHandler;
|
import reactor.core.publisher.Sinks.EmitFailureHandler;
|
||||||
import reactor.core.publisher.Sinks.EmitResult;
|
|
||||||
import reactor.core.publisher.Sinks.Empty;
|
import reactor.core.publisher.Sinks.Empty;
|
||||||
|
import reactor.core.publisher.Sinks.Many;
|
||||||
import reactor.core.scheduler.Schedulers;
|
import reactor.core.scheduler.Schedulers;
|
||||||
|
|
||||||
public class ProducerConnection<T> {
|
public class ProducerConnection<T> {
|
||||||
@ -20,8 +19,8 @@ public class ProducerConnection<T> {
|
|||||||
private static final Logger LOG = LogManager.getLogger(ProducerConnection.class);
|
private static final Logger LOG = LogManager.getLogger(ProducerConnection.class);
|
||||||
|
|
||||||
private final String channel;
|
private final String channel;
|
||||||
|
private final int bufferSize;
|
||||||
private Object remote;
|
private int remoteCount = 0;
|
||||||
|
|
||||||
private Flux<Payload> local;
|
private Flux<Payload> local;
|
||||||
|
|
||||||
@ -30,8 +29,9 @@ public class ProducerConnection<T> {
|
|||||||
private Optional<Throwable> remoteTerminationState = null;
|
private Optional<Throwable> remoteTerminationState = null;
|
||||||
private Empty<Void> remoteTerminationSink = Sinks.empty();
|
private Empty<Void> remoteTerminationSink = Sinks.empty();
|
||||||
|
|
||||||
public ProducerConnection(String channel) {
|
public ProducerConnection(String channel, int bufferSize) {
|
||||||
this.channel = channel;
|
this.channel = channel;
|
||||||
|
this.bufferSize = bufferSize;
|
||||||
if (LOG.isDebugEnabled()) LOG.debug("{} Create new blank connection", this.printStatus());
|
if (LOG.isDebugEnabled()) LOG.debug("{} Create new blank connection", this.printStatus());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ public class ProducerConnection<T> {
|
|||||||
return "[\"%s\" (%d)%s%s%s]".formatted(channel,
|
return "[\"%s\" (%d)%s%s%s]".formatted(channel,
|
||||||
System.identityHashCode(this),
|
System.identityHashCode(this),
|
||||||
local != null ? ", local" : "",
|
local != null ? ", local" : "",
|
||||||
remote != null ? ", remote" : "",
|
remoteCount > 0 ? (remoteCount > 1 ? ", " + remoteCount + " remotes" : ", 1 remote") : "",
|
||||||
connectedState ? ((remoteTerminationState != null) ? (remoteTerminationState.isPresent() ? ", done with error" : ", done") : ", connected") : ", waiting"
|
connectedState ? ((remoteTerminationState != null) ? (remoteTerminationState.isPresent() ? ", done with error" : ", done") : ", connected") : ", waiting"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -55,13 +55,7 @@ public class ProducerConnection<T> {
|
|||||||
if (LOG.isDebugEnabled()) LOG.debug("{} Local is connected", this.printStatus());
|
if (LOG.isDebugEnabled()) LOG.debug("{} Local is connected", this.printStatus());
|
||||||
return remoteTerminationSink.asMono().publishOn(Schedulers.parallel());
|
return remoteTerminationSink.asMono().publishOn(Schedulers.parallel());
|
||||||
}
|
}
|
||||||
})).doFinally(s -> {
|
}));
|
||||||
if (s != SignalType.ON_ERROR) {
|
|
||||||
synchronized (ProducerConnection.this) {
|
|
||||||
//reset(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized Flux<Payload> connectRemote() {
|
public synchronized Flux<Payload> connectRemote() {
|
||||||
@ -77,37 +71,50 @@ public class ProducerConnection<T> {
|
|||||||
}
|
}
|
||||||
})).doOnError(ex -> {
|
})).doOnError(ex -> {
|
||||||
synchronized (ProducerConnection.this) {
|
synchronized (ProducerConnection.this) {
|
||||||
if (local != null && remoteTerminationState == null) {
|
if (remoteCount <= 1) {
|
||||||
remoteTerminationState = Optional.of(ex);
|
if (local != null && remoteTerminationState == null) {
|
||||||
if (LOG.isDebugEnabled()) LOG.debug("%s Remote connection ended with failure, emitting termination failure".formatted(this.printStatus()), ex);
|
remoteTerminationState = Optional.of(ex);
|
||||||
var sink = remoteTerminationSink;
|
if (LOG.isDebugEnabled()) LOG.debug("%s Remote connection ended with failure, emitting termination failure".formatted(this.printStatus()), ex);
|
||||||
reset(true);
|
var sink = remoteTerminationSink;
|
||||||
sink.emitError(ex, EmitFailureHandler.busyLooping(Duration.ofMillis(100)));
|
reset();
|
||||||
if (LOG.isDebugEnabled()) LOG.debug("%s Remote connection ended with failure, emitted termination failure".formatted(this.printStatus()));
|
sink.emitError(ex, EmitFailureHandler.busyLooping(Duration.ofMillis(100)));
|
||||||
|
if (LOG.isDebugEnabled()) LOG.debug("%s Remote connection ended with failure, emitted termination failure".formatted(this.printStatus()));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
remoteCount--;
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("%s Remote connection ended with failure, but at least one remote is still online".formatted(
|
||||||
|
this.printStatus()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).doFinally(s -> {
|
}).doFinally(s -> {
|
||||||
if (s != SignalType.ON_ERROR) {
|
if (s != SignalType.ON_ERROR) {
|
||||||
synchronized (ProducerConnection.this) {
|
synchronized (ProducerConnection.this) {
|
||||||
if (local != null && remoteTerminationState == null) {
|
if (LOG.isDebugEnabled()) LOG.debug("{} Remote connection ending with status {}", this.printStatus(), s);
|
||||||
assert connectedState;
|
if (remoteCount <= 1) {
|
||||||
remoteTerminationState = Optional.empty();
|
if (local != null && remoteTerminationState == null) {
|
||||||
if (LOG.isDebugEnabled()) LOG.debug("{} Remote connection ended with status {}, emitting termination complete", this.printStatus(), s);
|
assert connectedState;
|
||||||
if (s == SignalType.CANCEL) {
|
remoteTerminationState = Optional.empty();
|
||||||
remoteTerminationSink.emitError(new CancelledChannelException(), EmitFailureHandler.busyLooping(Duration.ofMillis(100)));
|
if (LOG.isDebugEnabled()) LOG.debug("{} Remote connection ended with status {}, emitting termination complete", this.printStatus(), s);
|
||||||
} else {
|
if (s == SignalType.CANCEL) {
|
||||||
remoteTerminationSink.emitEmpty(EmitFailureHandler.busyLooping(Duration.ofMillis(100)));
|
remoteTerminationSink.emitError(new CancelledChannelException(), EmitFailureHandler.busyLooping(Duration.ofMillis(100)));
|
||||||
|
} else {
|
||||||
|
remoteTerminationSink.emitEmpty(EmitFailureHandler.busyLooping(Duration.ofMillis(100)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
reset();
|
||||||
if (LOG.isDebugEnabled()) LOG.debug("{} Remote connection ended with status {}, emitted termination complete", this.printStatus(), s);
|
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);
|
||||||
}
|
}
|
||||||
reset(true);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void reset(boolean resettingFromRemote) {
|
public synchronized void reset() {
|
||||||
if (LOG.isDebugEnabled()) LOG.debug("{} Reset started", this.printStatus());
|
if (LOG.isDebugEnabled()) LOG.debug("{} Reset started", this.printStatus());
|
||||||
if (connectedState) {
|
if (connectedState) {
|
||||||
if (remoteTerminationState == null) {
|
if (remoteTerminationState == null) {
|
||||||
@ -124,7 +131,7 @@ public class ProducerConnection<T> {
|
|||||||
if (LOG.isDebugEnabled()) LOG.debug("{} The previous connection has been interrupted", this.printStatus());
|
if (LOG.isDebugEnabled()) LOG.debug("{} The previous connection has been interrupted", this.printStatus());
|
||||||
}
|
}
|
||||||
local = null;
|
local = null;
|
||||||
remote = null;
|
remoteCount = 0;
|
||||||
connectedState = false;
|
connectedState = false;
|
||||||
connectedSink = Sinks.empty();
|
connectedSink = Sinks.empty();
|
||||||
remoteTerminationState = null;
|
remoteTerminationState = null;
|
||||||
@ -134,11 +141,11 @@ public class ProducerConnection<T> {
|
|||||||
|
|
||||||
public synchronized void registerRemote() {
|
public synchronized void registerRemote() {
|
||||||
if (LOG.isDebugEnabled()) LOG.debug("{} Remote is trying to register", this.printStatus());
|
if (LOG.isDebugEnabled()) LOG.debug("{} Remote is trying to register", this.printStatus());
|
||||||
if (this.remote != null) {
|
if (this.remoteCount > 0) {
|
||||||
if (LOG.isDebugEnabled()) LOG.debug("{} Remote was already registered", this.printStatus());
|
if (LOG.isDebugEnabled()) LOG.debug("{} Remote was already registered", this.printStatus());
|
||||||
throw new IllegalStateException("Remote is already registered");
|
throw new IllegalStateException("Remote is already registered");
|
||||||
}
|
}
|
||||||
this.remote = new Object();
|
this.remoteCount++;
|
||||||
if (LOG.isDebugEnabled()) LOG.debug("{} Remote registered", this.printStatus());
|
if (LOG.isDebugEnabled()) LOG.debug("{} Remote registered", this.printStatus());
|
||||||
onChanged();
|
onChanged();
|
||||||
}
|
}
|
||||||
@ -149,14 +156,14 @@ public class ProducerConnection<T> {
|
|||||||
if (LOG.isDebugEnabled()) LOG.debug("{} Local was already registered", this.printStatus());
|
if (LOG.isDebugEnabled()) LOG.debug("{} Local was already registered", this.printStatus());
|
||||||
throw new IllegalStateException("Local is already registered");
|
throw new IllegalStateException("Local is already registered");
|
||||||
}
|
}
|
||||||
this.local = local;
|
this.local = local.publish(bufferSize).refCount(1);
|
||||||
if (LOG.isDebugEnabled()) LOG.debug("{} Local registered", this.printStatus());
|
if (LOG.isDebugEnabled()) LOG.debug("{} Local registered", this.printStatus());
|
||||||
onChanged();
|
onChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void onChanged() {
|
private synchronized void onChanged() {
|
||||||
if (LOG.isDebugEnabled()) LOG.debug("{} Checking connection changes", this.printStatus());
|
if (LOG.isDebugEnabled()) LOG.debug("{} Checking connection changes", this.printStatus());
|
||||||
if (local != null && remote != null) {
|
if (local != null && remoteCount > 0) {
|
||||||
connectedState = true;
|
connectedState = true;
|
||||||
if (LOG.isDebugEnabled()) LOG.debug("{} Connected successfully! Emitting connected event", this.printStatus());
|
if (LOG.isDebugEnabled()) LOG.debug("{} Connected successfully! Emitting connected event", this.printStatus());
|
||||||
connectedSink.emitEmpty(EmitFailureHandler.busyLooping(Duration.ofMillis(100)));
|
connectedSink.emitEmpty(EmitFailureHandler.busyLooping(Duration.ofMillis(100)));
|
||||||
|
@ -15,6 +15,7 @@ import java.time.Duration;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@ -262,12 +263,13 @@ public abstract class TestChannel {
|
|||||||
@Test
|
@Test
|
||||||
public void testConsumeMidCancel() {
|
public void testConsumeMidCancel() {
|
||||||
var dataFlux = Flux.fromIterable(data).publish().autoConnect();
|
var dataFlux = Flux.fromIterable(data).publish().autoConnect();
|
||||||
|
AtomicReference<Throwable> exRef = new AtomicReference<>();
|
||||||
var eventProducer = producer
|
var eventProducer = producer
|
||||||
.sendMessages(dataFlux.map(Integer::toUnsignedString))
|
.sendMessages(dataFlux.map(Integer::toUnsignedString))
|
||||||
.retryWhen(Retry.fixedDelay(Long.MAX_VALUE, Duration.ofSeconds(1)))
|
.retryWhen(Retry.fixedDelay(Long.MAX_VALUE, Duration.ofSeconds(1)))
|
||||||
.repeatWhen(n -> n.delayElements(Duration.ofSeconds(1)))
|
.repeatWhen(n -> n.delayElements(Duration.ofSeconds(1)))
|
||||||
.subscribeOn(Schedulers.parallel())
|
.subscribeOn(Schedulers.parallel())
|
||||||
.subscribe(n -> {}, ex -> Assertions.fail(ex));
|
.subscribe(n -> {}, exRef::set);
|
||||||
try {
|
try {
|
||||||
var receiver1 = consumer
|
var receiver1 = consumer
|
||||||
.consumeMessages()
|
.consumeMessages()
|
||||||
@ -284,10 +286,14 @@ public abstract class TestChannel {
|
|||||||
.map(Integer::parseUnsignedInt)
|
.map(Integer::parseUnsignedInt)
|
||||||
.collect(Collectors.toCollection(IntArrayList::new))
|
.collect(Collectors.toCollection(IntArrayList::new))
|
||||||
.block(Duration.ofSeconds(5));
|
.block(Duration.ofSeconds(5));
|
||||||
|
var ex = exRef.get();
|
||||||
|
if (ex != null) {
|
||||||
|
Assertions.fail(ex);
|
||||||
|
}
|
||||||
Assertions.assertNotNull(receiver1);
|
Assertions.assertNotNull(receiver1);
|
||||||
Assertions.assertNotNull(receiver2);
|
Assertions.assertNotNull(receiver2);
|
||||||
receiver1.addAll(receiver2);
|
Assertions.assertEquals(data.subList(0, 10), receiver1);
|
||||||
Assertions.assertEquals(data, receiver1);
|
Assertions.assertEquals(data.subList(50, 100), receiver2.subList(receiver2.size() - 50, receiver2.size()));
|
||||||
System.out.println(receiver1);
|
System.out.println(receiver1);
|
||||||
} finally {
|
} finally {
|
||||||
eventProducer.dispose();
|
eventProducer.dispose();
|
||||||
@ -326,8 +332,9 @@ public abstract class TestChannel {
|
|||||||
.collect(Collectors.toCollection(IntArrayList::new))
|
.collect(Collectors.toCollection(IntArrayList::new))
|
||||||
.block();
|
.block();
|
||||||
Assertions.assertNotNull(receiver2);
|
Assertions.assertNotNull(receiver2);
|
||||||
data.removeElements(0, 11);
|
Assertions.assertNotEquals(0, receiver2.getInt(0));
|
||||||
Assertions.assertEquals(data, receiver2);
|
Assertions.assertNotEquals(1, receiver2.getInt(1));
|
||||||
|
Assertions.assertNotEquals(2, receiver2.getInt(2));
|
||||||
System.out.println(receiver2);
|
System.out.println(receiver2);
|
||||||
} finally {
|
} finally {
|
||||||
eventProducer.dispose();
|
eventProducer.dispose();
|
||||||
@ -391,11 +398,10 @@ public abstract class TestChannel {
|
|||||||
producer
|
producer
|
||||||
.sendMessages(dataFlux.limitRate(1).map(Integer::toUnsignedString))
|
.sendMessages(dataFlux.limitRate(1).map(Integer::toUnsignedString))
|
||||||
.block(Duration.ofSeconds(5));
|
.block(Duration.ofSeconds(5));
|
||||||
data.removeInt(10);
|
Assertions.assertTrue(numbers.contains(0));
|
||||||
if (numbers.size() < data.size()) {
|
Assertions.assertTrue(numbers.contains(1));
|
||||||
data.removeInt(data.size() - 1);
|
Assertions.assertTrue(numbers.contains(50));
|
||||||
}
|
Assertions.assertTrue(numbers.contains(51));
|
||||||
Assertions.assertEquals(data, List.copyOf(numbers));
|
|
||||||
} finally {
|
} finally {
|
||||||
eventConsumer.dispose();
|
eventConsumer.dispose();
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ public class TestRSocket {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testServerConsumer() {
|
public void testServerConsumer() {
|
||||||
var server = new MyRSocketServer(HostAndPort.fromParts("127.0.0.1", 8085));
|
var server = new MyRSocketServer(HostAndPort.fromParts("127.0.0.1", 8085), 1);
|
||||||
try {
|
try {
|
||||||
var rawClient = RSocketConnector.create()
|
var rawClient = RSocketConnector.create()
|
||||||
.setupPayload(DefaultPayload.create("client", "setup-info"))
|
.setupPayload(DefaultPayload.create("client", "setup-info"))
|
||||||
@ -73,7 +73,7 @@ public class TestRSocket {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testServerProducer() {
|
public void testServerProducer() {
|
||||||
var server = new MyRSocketServer(HostAndPort.fromParts("127.0.0.1", 8085));
|
var server = new MyRSocketServer(HostAndPort.fromParts("127.0.0.1", 8085), 1);
|
||||||
try {
|
try {
|
||||||
var rawClient = RSocketConnector.create()
|
var rawClient = RSocketConnector.create()
|
||||||
.setupPayload(DefaultPayload.create("client", "setup-info"))
|
.setupPayload(DefaultPayload.create("client", "setup-info"))
|
||||||
@ -173,7 +173,7 @@ public class TestRSocket {
|
|||||||
@Test
|
@Test
|
||||||
public void testServerOnClose() {
|
public void testServerOnClose() {
|
||||||
Assertions.assertThrows(IllegalStateException.class, () -> {
|
Assertions.assertThrows(IllegalStateException.class, () -> {
|
||||||
var server = new MyRSocketServer(HostAndPort.fromParts("127.0.0.1", 8085));
|
var server = new MyRSocketServer(HostAndPort.fromParts("127.0.0.1", 8085), 1);
|
||||||
try {
|
try {
|
||||||
server.onClose().block(Duration.ofSeconds(1));
|
server.onClose().block(Duration.ofSeconds(1));
|
||||||
} finally {
|
} finally {
|
||||||
|
Loading…
Reference in New Issue
Block a user