Accept multiple tdlib clients

This commit is contained in:
Andrea Cavalli 2022-10-11 20:08:40 +02:00
parent a8510ed336
commit 24b4387b08
5 changed files with 163 additions and 106 deletions

View File

@ -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());

View File

@ -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();
}); });

View File

@ -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)));

View File

@ -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();
} }

View File

@ -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 {