Sockets are working
This commit is contained in:
parent
0a74e1ab1a
commit
705e5ca65e
@ -27,6 +27,7 @@ import java.util.Map;
|
|||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
import java.util.function.Consumer;
|
||||||
import org.apache.kafka.common.errors.SerializationException;
|
import org.apache.kafka.common.errors.SerializationException;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@ -46,7 +47,7 @@ abstract class BaseAtomixReactiveApiClient implements ReactiveApiMultiClient {
|
|||||||
|
|
||||||
// Temporary id used to make requests
|
// Temporary id used to make requests
|
||||||
private final long clientId;
|
private final long clientId;
|
||||||
private final Many<OnRequest<?>> requests;
|
private final Consumer<OnRequest<?>> requests;
|
||||||
private final Map<Long, CompletableFuture<Timestamped<OnResponse<TdApi.Object>>>> responses
|
private final Map<Long, CompletableFuture<Timestamped<OnResponse<TdApi.Object>>>> responses
|
||||||
= new ConcurrentHashMap<>();
|
= new ConcurrentHashMap<>();
|
||||||
private final AtomicLong requestId = new AtomicLong(0);
|
private final AtomicLong requestId = new AtomicLong(0);
|
||||||
@ -54,7 +55,7 @@ abstract class BaseAtomixReactiveApiClient implements ReactiveApiMultiClient {
|
|||||||
|
|
||||||
public BaseAtomixReactiveApiClient(TdlibChannelsSharedReceive sharedTdlibClients) {
|
public BaseAtomixReactiveApiClient(TdlibChannelsSharedReceive sharedTdlibClients) {
|
||||||
this.clientId = System.nanoTime();
|
this.clientId = System.nanoTime();
|
||||||
this.requests = sharedTdlibClients.requests();
|
this.requests = sharedTdlibClients::emitRequest;
|
||||||
|
|
||||||
this.subscription = sharedTdlibClients.responses().doOnNext(response -> {
|
this.subscription = sharedTdlibClients.responses().doOnNext(response -> {
|
||||||
var responseSink = responses.get(response.data().requestId());
|
var responseSink = responses.get(response.data().requestId());
|
||||||
@ -63,7 +64,8 @@ abstract class BaseAtomixReactiveApiClient implements ReactiveApiMultiClient {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
responseSink.complete(response);
|
responseSink.complete(response);
|
||||||
}).subscribeOn(Schedulers.parallel()).subscribe();
|
}).subscribeOn(Schedulers.parallel())
|
||||||
|
.subscribe(v -> {}, ex -> LOG.error("Reactive api client responses flux has failed unexpectedly!", ex));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -100,9 +102,7 @@ abstract class BaseAtomixReactiveApiClient implements ReactiveApiMultiClient {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.doFinally(s -> this.responses.remove(requestId));
|
.doFinally(s -> this.responses.remove(requestId));
|
||||||
synchronized (requests) {
|
requests.accept(new Request<>(userId, clientId, requestId, request, timeout));
|
||||||
requests.emitNext(new Request<>(userId, clientId, requestId, request, timeout), EmitFailureHandler.FAIL_FAST);
|
|
||||||
}
|
|
||||||
return response;
|
return response;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
package it.tdlight.reactiveapi;
|
|
||||||
|
|
||||||
import java.util.function.Function;
|
|
||||||
import reactor.core.publisher.Flux;
|
|
||||||
import reactor.core.publisher.Mono;
|
|
||||||
|
|
||||||
public class FutureEventConsumer<K> implements EventConsumer<K> {
|
|
||||||
|
|
||||||
private final Mono<EventConsumer<K>> future;
|
|
||||||
|
|
||||||
public FutureEventConsumer(Mono<EventConsumer<K>> future) {
|
|
||||||
this.future = future.cache();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Flux<Timestamped<K>> consumeMessages() {
|
|
||||||
return future.flatMapMany(EventConsumer::consumeMessages);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
package it.tdlight.reactiveapi;
|
|
||||||
|
|
||||||
import java.util.function.Function;
|
|
||||||
import reactor.core.publisher.Flux;
|
|
||||||
import reactor.core.publisher.Mono;
|
|
||||||
import reactor.core.scheduler.Schedulers;
|
|
||||||
|
|
||||||
public class FutureEventProducer<K> implements EventProducer<K> {
|
|
||||||
|
|
||||||
private final Mono<EventProducer<K>> future;
|
|
||||||
|
|
||||||
public FutureEventProducer(Mono<EventProducer<K>> future) {
|
|
||||||
this.future = future.cache();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Mono<Void> sendMessages(Flux<K> eventsFlux) {
|
|
||||||
return future.flatMap(ep -> ep.sendMessages(eventsFlux));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
future.doOnNext(EventProducer::close).subscribeOn(Schedulers.parallel()).subscribe();
|
|
||||||
}
|
|
||||||
}
|
|
@ -198,7 +198,7 @@ public abstract class ReactiveApiPublisher {
|
|||||||
.then(Mono.empty())
|
.then(Mono.empty())
|
||||||
)
|
)
|
||||||
.subscribeOn(Schedulers.parallel())
|
.subscribeOn(Schedulers.parallel())
|
||||||
.subscribe();
|
.subscribe(v -> {}, ex -> LOG.error("Resulting events flux has failed unexpectedly! (1)", ex));
|
||||||
|
|
||||||
var messagesToSend = publishedResultingEvents
|
var messagesToSend = publishedResultingEvents
|
||||||
// Obtain only client-bound events
|
// Obtain only client-bound events
|
||||||
@ -229,7 +229,7 @@ public abstract class ReactiveApiPublisher {
|
|||||||
} else {
|
} else {
|
||||||
LOG.error("Unknown cluster-bound event: {}", clusterBoundEvent);
|
LOG.error("Unknown cluster-bound event: {}", clusterBoundEvent);
|
||||||
}
|
}
|
||||||
});
|
}, ex -> LOG.error("Resulting events flux has failed unexpectedly! (2)", ex));
|
||||||
|
|
||||||
|
|
||||||
var prev = this.disposable.getAndSet(publishedResultingEvents.connect());
|
var prev = this.disposable.getAndSet(publishedResultingEvents.connect());
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
package it.tdlight.reactiveapi;
|
package it.tdlight.reactiveapi;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import org.reactivestreams.Subscription;
|
||||||
|
import reactor.core.Disposable;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import reactor.core.publisher.Sinks;
|
import reactor.core.publisher.Sinks;
|
||||||
@ -9,17 +12,20 @@ import reactor.core.publisher.Sinks.Empty;
|
|||||||
|
|
||||||
public abstract class SimpleEventProducer<K> implements EventProducer<K> {
|
public abstract class SimpleEventProducer<K> implements EventProducer<K> {
|
||||||
|
|
||||||
private final Empty<Void> closeRequest = Sinks.empty();
|
private AtomicReference<Subscription> closeRequest = new AtomicReference<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final Mono<Void> sendMessages(Flux<K> eventsFlux) {
|
public final Mono<Void> sendMessages(Flux<K> eventsFlux) {
|
||||||
return handleSendMessages(eventsFlux.takeUntilOther(closeRequest.asMono()));
|
return handleSendMessages(eventsFlux).doOnSubscribe(s -> closeRequest.set(s));
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract Mono<Void> handleSendMessages(Flux<K> eventsFlux);
|
public abstract Mono<Void> handleSendMessages(Flux<K> eventsFlux);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void close() {
|
public final void close() {
|
||||||
closeRequest.emitEmpty(EmitFailureHandler.busyLooping(Duration.ofMillis(100)));
|
var s = closeRequest.get();
|
||||||
|
if (s != null) {
|
||||||
|
s.cancel();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ import org.apache.logging.log4j.LogManager;
|
|||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import reactor.core.Disposable;
|
import reactor.core.Disposable;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
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;
|
||||||
@ -30,43 +31,48 @@ import reactor.util.retry.RetryBackoffSpec;
|
|||||||
public class TdlibChannelsSharedHost implements Closeable {
|
public class TdlibChannelsSharedHost implements Closeable {
|
||||||
|
|
||||||
private static final Logger LOG = LogManager.getLogger(TdlibChannelsSharedHost.class);
|
private static final Logger LOG = LogManager.getLogger(TdlibChannelsSharedHost.class);
|
||||||
private static final RetryBackoffSpec RETRY_STRATEGY = Retry
|
public static final RetryBackoffSpec RETRY_STRATEGY = Retry
|
||||||
.backoff(Long.MAX_VALUE, Duration.ofSeconds(1))
|
.backoff(Long.MAX_VALUE, Duration.ofSeconds(1))
|
||||||
.maxBackoff(Duration.ofSeconds(16))
|
.maxBackoff(Duration.ofSeconds(16))
|
||||||
.jitter(1.0)
|
.jitter(1.0)
|
||||||
.doBeforeRetry(signal -> LOG.warn("Retrying channel with signal {}", signal));
|
.doBeforeRetry(signal -> LogManager.getLogger("Channels").warn("Retrying channel with signal {}", signal));
|
||||||
|
|
||||||
|
public static final Function<Flux<Long>, Flux<Long>> REPEAT_STRATEGY = n -> n
|
||||||
|
.doOnNext(i -> LogManager.getLogger("Channels").debug("Resubscribing to channel"))
|
||||||
|
.delayElements(Duration.ofSeconds(5));
|
||||||
|
|
||||||
private final TdlibChannelsServers tdServersChannels;
|
private final TdlibChannelsServers tdServersChannels;
|
||||||
private final Disposable responsesSub;
|
private final Disposable responsesSub;
|
||||||
private final AtomicReference<Disposable> requestsSub = new AtomicReference<>();
|
private final AtomicReference<Disposable> requestsSub = new AtomicReference<>();
|
||||||
private final Many<OnResponse<TdApi.Object>> responses = Sinks.many().multicast().onBackpressureBuffer(65535);
|
private final Many<OnResponse<TdApi.Object>> responses = Sinks.many().multicast().directAllOrNothing();
|
||||||
private final Map<String, Many<Flux<ClientBoundEvent>>> events;
|
private final Map<String, Many<Flux<ClientBoundEvent>>> events;
|
||||||
private final Flux<Timestamped<OnRequest<Object>>> requests;
|
private final Flux<Timestamped<OnRequest<Object>>> requests;
|
||||||
|
|
||||||
public TdlibChannelsSharedHost(Set<String> allLanes, TdlibChannelsServers tdServersChannels) {
|
public TdlibChannelsSharedHost(Set<String> allLanes, TdlibChannelsServers tdServersChannels) {
|
||||||
this.tdServersChannels = tdServersChannels;
|
this.tdServersChannels = tdServersChannels;
|
||||||
this.responsesSub = tdServersChannels.response()
|
this.responsesSub = Mono.defer(() -> tdServersChannels.response()
|
||||||
.sendMessages(responses.asFlux().log("responses", Level.FINEST, SignalType.ON_NEXT))
|
.sendMessages(responses.asFlux().log("responses", Level.FINE)))
|
||||||
.repeatWhen(n -> n.delayElements(Duration.ofSeconds(5)))
|
.repeatWhen(REPEAT_STRATEGY)
|
||||||
.retryWhen(RETRY_STRATEGY)
|
.retryWhen(RETRY_STRATEGY)
|
||||||
.subscribeOn(Schedulers.parallel())
|
.subscribeOn(Schedulers.parallel())
|
||||||
.subscribe(n -> {}, ex -> LOG.error("Unexpected error when sending responses", ex));
|
.subscribe(n -> {}, ex -> LOG.error("Unexpected error when sending responses", ex));
|
||||||
events = allLanes.stream().collect(Collectors.toUnmodifiableMap(Function.identity(), lane -> {
|
events = allLanes.stream().collect(Collectors.toUnmodifiableMap(Function.identity(), lane -> {
|
||||||
Many<Flux<ClientBoundEvent>> sink = Sinks.many().multicast().onBackpressureBuffer(65535);
|
Many<Flux<ClientBoundEvent>> sink = Sinks.many().multicast().onBackpressureBuffer(65535);
|
||||||
var outputEventsFlux = Flux
|
var outputEventsFlux = Flux
|
||||||
.merge(sink.asFlux().map(flux -> flux.subscribeOn(Schedulers.parallel())), Integer.MAX_VALUE)
|
.merge(sink.asFlux().cache().map(flux -> flux.publish().autoConnect().subscribeOn(Schedulers.parallel())), Integer.MAX_VALUE)
|
||||||
.doFinally(s -> LOG.debug("Output events flux of lane \"{}\" terminated with signal {}", lane, s));
|
.doFinally(s -> LOG.debug("Output events flux of lane \"{}\" terminated with signal {}", lane, s));
|
||||||
tdServersChannels
|
Mono.defer(() -> tdServersChannels
|
||||||
.events(lane)
|
.events(lane)
|
||||||
.sendMessages(outputEventsFlux)
|
.sendMessages(outputEventsFlux))
|
||||||
.repeatWhen(n -> n.delayElements(Duration.ofSeconds(5)))
|
.repeatWhen(REPEAT_STRATEGY)
|
||||||
.retryWhen(RETRY_STRATEGY)
|
.retryWhen(RETRY_STRATEGY)
|
||||||
.subscribeOn(Schedulers.parallel())
|
.subscribeOn(Schedulers.parallel())
|
||||||
.subscribe(n -> {}, ex -> LOG.error("Unexpected error when sending events to lane {}", lane, ex));
|
.subscribe(n -> {}, ex -> LOG.error("Unexpected error when sending events to lane {}", lane, ex));
|
||||||
return sink;
|
return sink;
|
||||||
}));
|
}));
|
||||||
this.requests = tdServersChannels.request().consumeMessages()
|
this.requests = tdServersChannels.request().consumeMessages()
|
||||||
.repeatWhen(n -> n.delayElements(Duration.ofSeconds(5)))
|
.doFinally(s -> LOG.debug("Input requests consumer terminated with signal {}", s))
|
||||||
|
.repeatWhen(REPEAT_STRATEGY)
|
||||||
.retryWhen(RETRY_STRATEGY)
|
.retryWhen(RETRY_STRATEGY)
|
||||||
.doOnError(ex -> LOG.error("Unexpected error when receiving requests", ex))
|
.doOnError(ex -> LOG.error("Unexpected error when receiving requests", ex))
|
||||||
.doFinally(s -> LOG.debug("Input requests flux terminated with signal {}", s));
|
.doFinally(s -> LOG.debug("Input requests flux terminated with signal {}", s));
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
package it.tdlight.reactiveapi;
|
package it.tdlight.reactiveapi;
|
||||||
|
|
||||||
|
import static it.tdlight.reactiveapi.TdlibChannelsSharedHost.REPEAT_STRATEGY;
|
||||||
|
import static it.tdlight.reactiveapi.TdlibChannelsSharedHost.RETRY_STRATEGY;
|
||||||
|
|
||||||
import it.tdlight.jni.TdApi.Object;
|
import it.tdlight.jni.TdApi.Object;
|
||||||
import it.tdlight.reactiveapi.Event.ClientBoundEvent;
|
import it.tdlight.reactiveapi.Event.ClientBoundEvent;
|
||||||
import it.tdlight.reactiveapi.Event.OnRequest;
|
import it.tdlight.reactiveapi.Event.OnRequest;
|
||||||
@ -9,6 +12,7 @@ import java.time.Duration;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.logging.Level;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
@ -27,45 +31,46 @@ public class TdlibChannelsSharedReceive implements Closeable {
|
|||||||
|
|
||||||
private static final Logger LOG = LogManager.getLogger(TdlibChannelsSharedReceive.class);
|
private static final Logger LOG = LogManager.getLogger(TdlibChannelsSharedReceive.class);
|
||||||
|
|
||||||
private static final RetryBackoffSpec RETRY_STRATEGY = Retry
|
|
||||||
.backoff(Long.MAX_VALUE, Duration.ofSeconds(1))
|
|
||||||
.maxBackoff(Duration.ofSeconds(16))
|
|
||||||
.jitter(1.0)
|
|
||||||
.doBeforeRetry(signal -> LOG.warn("Retrying channel with signal {}", signal));
|
|
||||||
|
|
||||||
private final TdlibChannelsClients tdClientsChannels;
|
private final TdlibChannelsClients tdClientsChannels;
|
||||||
private final AtomicReference<Disposable> responsesSub = new AtomicReference<>();
|
private final AtomicReference<Disposable> responsesSub = new AtomicReference<>();
|
||||||
private final Disposable requestsSub;
|
private final Disposable requestsSub;
|
||||||
private final AtomicReference<Disposable> eventsSub = new AtomicReference<>();
|
private final AtomicReference<Disposable> eventsSub = new AtomicReference<>();
|
||||||
private final Flux<Timestamped<OnResponse<Object>>> responses;
|
private final Flux<Timestamped<OnResponse<Object>>> responses;
|
||||||
private final Map<String, Flux<Timestamped<ClientBoundEvent>>> events;
|
private final Map<String, Flux<Timestamped<ClientBoundEvent>>> events;
|
||||||
private final Many<OnRequest<?>> requests = Sinks.many().multicast().onBackpressureBuffer(65535);
|
private final Many<OnRequest<?>> requests = Sinks.many().multicast().directAllOrNothing();
|
||||||
|
|
||||||
public TdlibChannelsSharedReceive(TdlibChannelsClients tdClientsChannels) {
|
public TdlibChannelsSharedReceive(TdlibChannelsClients tdClientsChannels) {
|
||||||
this.tdClientsChannels = tdClientsChannels;
|
this.tdClientsChannels = tdClientsChannels;
|
||||||
this.responses = tdClientsChannels
|
this.responses = Flux
|
||||||
.response()
|
.defer(() -> tdClientsChannels.response().consumeMessages())
|
||||||
.consumeMessages()
|
.log("responses", Level.FINE)
|
||||||
.repeatWhen(n -> n.delayElements(Duration.ofSeconds(5)))
|
.repeatWhen(REPEAT_STRATEGY)
|
||||||
.retryWhen(RETRY_STRATEGY)
|
.retryWhen(RETRY_STRATEGY)
|
||||||
|
.publish()
|
||||||
|
.autoConnect()
|
||||||
.doFinally(s -> LOG.debug("Input responses flux terminated with signal {}", s));
|
.doFinally(s -> LOG.debug("Input responses flux terminated with signal {}", s));
|
||||||
this.events = tdClientsChannels.events().entrySet().stream()
|
this.events = tdClientsChannels.events().entrySet().stream()
|
||||||
.collect(Collectors.toUnmodifiableMap(Entry::getKey,
|
.collect(Collectors.toUnmodifiableMap(Entry::getKey,
|
||||||
e -> e
|
e -> Flux
|
||||||
.getValue()
|
.defer(() -> e.getValue().consumeMessages())
|
||||||
.consumeMessages()
|
.repeatWhen(REPEAT_STRATEGY)
|
||||||
.repeatWhen(n -> n.delayElements(Duration.ofSeconds(5)))
|
|
||||||
.retryWhen(RETRY_STRATEGY)
|
.retryWhen(RETRY_STRATEGY)
|
||||||
.doFinally(s -> LOG.debug("Input events flux of lane \"{}\" terminated with signal {}", e.getKey(), s))
|
.doFinally(s -> LOG.debug("Input events flux of lane \"{}\" terminated with signal {}", e.getKey(), s))
|
||||||
));
|
));
|
||||||
var requestsFlux = Flux.defer(() -> requests.asFlux()
|
this.requestsSub = tdClientsChannels
|
||||||
.doFinally(s -> LOG.debug("Output requests flux terminated with signal {}", s)));
|
.request()
|
||||||
this.requestsSub = tdClientsChannels.request()
|
.sendMessages(Flux
|
||||||
.sendMessages(requestsFlux)
|
.defer(() -> requests
|
||||||
.repeatWhen(n -> n.delayElements(Duration.ofSeconds(5)))
|
.asFlux()
|
||||||
|
.doFinally(s -> LOG.debug("Output requests flux terminated with signal {}", s))))
|
||||||
|
.doFinally(s -> LOG.debug("Output requests sender terminated with signal {}", s))
|
||||||
|
.repeatWhen(REPEAT_STRATEGY)
|
||||||
.retryWhen(RETRY_STRATEGY)
|
.retryWhen(RETRY_STRATEGY)
|
||||||
.subscribeOn(Schedulers.parallel())
|
.subscribeOn(Schedulers.parallel())
|
||||||
.subscribe(n -> {}, ex -> requests.emitError(ex, EmitFailureHandler.busyLooping(Duration.ofMillis(100))));
|
.subscribe(n -> {}, ex -> {
|
||||||
|
LOG.error("An error when handling requests killed the requests subscriber!", ex);
|
||||||
|
requests.emitError(ex, EmitFailureHandler.busyLooping(Duration.ofMillis(100)));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public Flux<Timestamped<OnResponse<Object>>> responses() {
|
public Flux<Timestamped<OnResponse<Object>>> responses() {
|
||||||
@ -84,8 +89,10 @@ public class TdlibChannelsSharedReceive implements Closeable {
|
|||||||
return events;
|
return events;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Many<OnRequest<?>> requests() {
|
public void emitRequest(OnRequest<?> request) {
|
||||||
return requests;
|
synchronized (requests) {
|
||||||
|
requests.emitNext(request, EmitFailureHandler.FAIL_FAST);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -3,88 +3,165 @@ package it.tdlight.reactiveapi.rsocket;
|
|||||||
import io.rsocket.Payload;
|
import io.rsocket.Payload;
|
||||||
import it.tdlight.reactiveapi.Timestamped;
|
import it.tdlight.reactiveapi.Timestamped;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
import java.util.Optional;
|
||||||
import org.apache.kafka.common.serialization.Deserializer;
|
import org.apache.kafka.common.serialization.Deserializer;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
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.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.scheduler.Schedulers;
|
import reactor.core.scheduler.Schedulers;
|
||||||
|
|
||||||
class ConsumerConnection<T> {
|
public class ConsumerConnection<T> {
|
||||||
|
|
||||||
private Flux<Timestamped<T>> remote;
|
private static final Logger LOG = LogManager.getLogger(ConsumerConnection.class);
|
||||||
|
|
||||||
|
private final String channel;
|
||||||
|
private Flux<Payload> remote;
|
||||||
|
|
||||||
private Deserializer<T> local;
|
private Deserializer<T> local;
|
||||||
|
|
||||||
private Empty<Void> connected = Sinks.empty();
|
private boolean connectedState = false;
|
||||||
|
private Empty<Void> connectedSink = Sinks.empty();
|
||||||
private Empty<Void> remoteResult = Sinks.empty();
|
private Optional<Throwable> localTerminationState = null;
|
||||||
|
private Empty<Void> localTerminationSink = Sinks.empty();
|
||||||
|
|
||||||
public ConsumerConnection(String channel) {
|
public ConsumerConnection(String channel) {
|
||||||
|
this.channel = channel;
|
||||||
|
if (LOG.isDebugEnabled()) LOG.debug("{} Create new blank connection", this.printStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String printStatus() {
|
||||||
|
return "[\"%s\" (%d)%s%s%s]".formatted(channel,
|
||||||
|
System.identityHashCode(this),
|
||||||
|
local != null ? ", local" : "",
|
||||||
|
remote != null ? ", remote" : "",
|
||||||
|
connectedState ? ((localTerminationState != null) ? (localTerminationState.isPresent() ? ", done with error" : ", done") : ", connected") : ", waiting"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized Flux<Timestamped<T>> connectLocal() {
|
public synchronized Flux<Timestamped<T>> connectLocal() {
|
||||||
return connected.asMono().publishOn(Schedulers.parallel()).thenMany(Flux.defer(() -> {
|
if (LOG.isDebugEnabled()) LOG.debug("{} Local is asking to connect", this.printStatus());
|
||||||
|
return Mono.defer(() -> {
|
||||||
synchronized (ConsumerConnection.this) {
|
synchronized (ConsumerConnection.this) {
|
||||||
return remote;
|
return connectedSink.asMono();
|
||||||
}
|
}
|
||||||
}));
|
}).publishOn(Schedulers.parallel()).thenMany(Flux.defer(() -> {
|
||||||
|
synchronized (ConsumerConnection.this) {
|
||||||
|
if (LOG.isDebugEnabled()) LOG.debug("{} Local is connected", this.printStatus());
|
||||||
|
return RSocketUtils.deserialize(remote, local)
|
||||||
|
.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() {
|
||||||
return connected.asMono().publishOn(Schedulers.parallel()).then(Mono.defer(() -> {
|
if (LOG.isDebugEnabled()) LOG.debug("{} Remote is asking to connect", this.printStatus());
|
||||||
|
return Mono.defer(() -> {
|
||||||
synchronized (ConsumerConnection.this) {
|
synchronized (ConsumerConnection.this) {
|
||||||
return remoteResult.asMono();
|
return connectedSink.asMono();
|
||||||
}
|
}
|
||||||
}));
|
}).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());
|
||||||
|
}
|
||||||
|
})).doFinally(s -> {
|
||||||
|
if (s != SignalType.ON_ERROR) {
|
||||||
|
synchronized (ConsumerConnection.this) {
|
||||||
|
//reset(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void resetRemote() {
|
public synchronized void reset(boolean resettingFromRemote) {
|
||||||
connected = Sinks.empty();
|
if (LOG.isDebugEnabled()) LOG.debug("{} Reset started", this.printStatus());
|
||||||
remoteResult = Sinks.empty();
|
if (connectedState) {
|
||||||
remote = null;
|
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());
|
||||||
|
}
|
||||||
local = null;
|
local = null;
|
||||||
|
remote = null;
|
||||||
|
connectedState = false;
|
||||||
|
connectedSink = Sinks.empty();
|
||||||
|
localTerminationState = null;
|
||||||
|
localTerminationSink = Sinks.empty();
|
||||||
|
if (LOG.isDebugEnabled()) LOG.debug("{} Reset ended", this.printStatus());
|
||||||
}
|
}
|
||||||
|
|
||||||
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 (this.remote != null) {
|
if (this.remote != null) {
|
||||||
|
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 = remote
|
this.remote = remote;
|
||||||
.transformDeferred(flux -> {
|
if (LOG.isDebugEnabled()) LOG.debug("{} Remote registered", this.printStatus());
|
||||||
synchronized (ConsumerConnection.this) {
|
|
||||||
assert local != null;
|
|
||||||
return RSocketUtils.deserialize(flux, local);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.map(element -> new Timestamped<>(System.currentTimeMillis(), element))
|
|
||||||
.doOnError(ex -> {
|
|
||||||
synchronized (ConsumerConnection.this) {
|
|
||||||
remoteResult.emitError(ex, EmitFailureHandler.busyLooping(Duration.ofMillis(100)));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.doFinally(s -> {
|
|
||||||
synchronized (ConsumerConnection.this) {
|
|
||||||
remoteResult.emitEmpty(EmitFailureHandler.busyLooping(Duration.ofMillis(100)));
|
|
||||||
resetRemote();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
onChanged();
|
onChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void registerLocal(Deserializer<T> local) {
|
public synchronized void registerLocal(Deserializer<T> local) {
|
||||||
|
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());
|
||||||
throw new IllegalStateException("Local is already registered");
|
throw new IllegalStateException("Local is already registered");
|
||||||
}
|
}
|
||||||
this.local = local;
|
this.local = local;
|
||||||
|
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 (local != null && remote != null) {
|
if (local != null && remote != null) {
|
||||||
connected.emitEmpty(EmitFailureHandler.busyLooping(Duration.ofMillis(100)));
|
connectedState = true;
|
||||||
|
if (LOG.isDebugEnabled()) LOG.debug("{} Connected successfully! Emitting connected event", this.printStatus());
|
||||||
|
connectedSink.emitEmpty(EmitFailureHandler.busyLooping(Duration.ofMillis(100)));
|
||||||
|
if (LOG.isDebugEnabled()) LOG.debug("{} Connected successfully! Emitted connected event", this.printStatus());
|
||||||
|
} else {
|
||||||
|
if (LOG.isDebugEnabled()) LOG.debug("{} Still not connected", this.printStatus());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,10 +43,10 @@ public class MyRSocketClient implements RSocketChannelManager {
|
|||||||
this.nextClient = RSocketConnector.create()
|
this.nextClient = RSocketConnector.create()
|
||||||
//.setupPayload(DefaultPayload.create("client", "setup-info"))
|
//.setupPayload(DefaultPayload.create("client", "setup-info"))
|
||||||
.payloadDecoder(PayloadDecoder.ZERO_COPY)
|
.payloadDecoder(PayloadDecoder.ZERO_COPY)
|
||||||
.reconnect(retryStrategy)
|
//.reconnect(retryStrategy)
|
||||||
.connect(transport)
|
.connect(transport)
|
||||||
.doOnNext(lastClient::set)
|
.doOnNext(lastClient::set)
|
||||||
.cache();
|
.cacheInvalidateIf(RSocket::isDisposed);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -70,9 +70,11 @@ public class MyRSocketClient implements RSocketChannelManager {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<Void> handleSendMessages(Flux<K> eventsFlux) {
|
public Mono<Void> handleSendMessages(Flux<K> eventsFlux) {
|
||||||
|
return Mono.defer(() -> {
|
||||||
Flux<Payload> rawFlux = eventsFlux.transform(flux -> RSocketUtils.serialize(flux, serializer));
|
Flux<Payload> rawFlux = eventsFlux.transform(flux -> RSocketUtils.serialize(flux, serializer));
|
||||||
Flux<Payload> combinedRawFlux = Flux.just(DefaultPayload.create(channelName, "channel")).concatWith(rawFlux);
|
Flux<Payload> combinedRawFlux = Flux.just(DefaultPayload.create(channelName, "channel")).concatWith(rawFlux);
|
||||||
return nextClient.flatMapMany(client -> client.requestChannel(combinedRawFlux)).take(1, true).then();
|
return nextClient.flatMapMany(client -> client.requestChannel(combinedRawFlux).take(1, true)).then();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -43,7 +43,7 @@ public class MyRSocketServer implements RSocketChannelManager, RSocket {
|
|||||||
.payloadDecoder(PayloadDecoder.ZERO_COPY)
|
.payloadDecoder(PayloadDecoder.ZERO_COPY)
|
||||||
.bind(TcpServerTransport.create(baseHost.getHost(), baseHost.getPort()))
|
.bind(TcpServerTransport.create(baseHost.getHost(), baseHost.getPort()))
|
||||||
.doOnNext(d -> logger.debug("Server up"))
|
.doOnNext(d -> logger.debug("Server up"))
|
||||||
.cache();
|
.cacheInvalidateIf(CloseableChannel::isDisposed);
|
||||||
|
|
||||||
serverMono.subscribeOn(Schedulers.parallel()).subscribe(v -> {}, ex -> logger.warn("Failed to bind server"));
|
serverMono.subscribeOn(Schedulers.parallel()).subscribe(v -> {}, ex -> logger.warn("Failed to bind server"));
|
||||||
|
|
||||||
@ -93,7 +93,7 @@ public class MyRSocketServer implements RSocketChannelManager, RSocket {
|
|||||||
return new EventConsumer<K>() {
|
return new EventConsumer<K>() {
|
||||||
@Override
|
@Override
|
||||||
public Flux<Timestamped<K>> consumeMessages() {
|
public Flux<Timestamped<K>> consumeMessages() {
|
||||||
return Flux.defer(() -> {
|
return serverCloseable.flatMapMany(x -> {
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
var conn = (ConsumerConnection<K>) consumerRegistry.computeIfAbsent(channelName, ConsumerConnection::new);
|
var conn = (ConsumerConnection<K>) consumerRegistry.computeIfAbsent(channelName, ConsumerConnection::new);
|
||||||
conn.registerLocal(deserializer);
|
conn.registerLocal(deserializer);
|
||||||
@ -116,7 +116,7 @@ public class MyRSocketServer implements RSocketChannelManager, RSocket {
|
|||||||
return new EventProducer<K>() {
|
return new EventProducer<K>() {
|
||||||
@Override
|
@Override
|
||||||
public Mono<Void> sendMessages(Flux<K> eventsFlux) {
|
public Mono<Void> sendMessages(Flux<K> eventsFlux) {
|
||||||
return Mono.defer(() -> {
|
return serverCloseable.flatMap(x -> {
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
var conn = (ProducerConnection<K>) producerRegistry.computeIfAbsent(channelName, ProducerConnection::new);
|
var conn = (ProducerConnection<K>) producerRegistry.computeIfAbsent(channelName, ProducerConnection::new);
|
||||||
conn.registerLocal(eventsFlux.transform(flux -> RSocketUtils.serialize(flux, serializer)));
|
conn.registerLocal(eventsFlux.transform(flux -> RSocketUtils.serialize(flux, serializer)));
|
||||||
|
@ -2,80 +2,167 @@ package it.tdlight.reactiveapi.rsocket;
|
|||||||
|
|
||||||
import io.rsocket.Payload;
|
import io.rsocket.Payload;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
import java.util.Optional;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
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.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.scheduler.Schedulers;
|
import reactor.core.scheduler.Schedulers;
|
||||||
|
|
||||||
class ProducerConnection<T> {
|
public class ProducerConnection<T> {
|
||||||
|
|
||||||
|
private static final Logger LOG = LogManager.getLogger(ProducerConnection.class);
|
||||||
|
|
||||||
|
private final String channel;
|
||||||
|
|
||||||
private Object remote;
|
private Object remote;
|
||||||
|
|
||||||
private Flux<Payload> local;
|
private Flux<Payload> local;
|
||||||
|
|
||||||
private Empty<Void> connected = Sinks.empty();
|
private boolean connectedState = false;
|
||||||
|
private Empty<Void> connectedSink = Sinks.empty();
|
||||||
private Empty<Void> remoteResult = Sinks.empty();
|
private Optional<Throwable> remoteTerminationState = null;
|
||||||
|
private Empty<Void> remoteTerminationSink = Sinks.empty();
|
||||||
|
|
||||||
public ProducerConnection(String channel) {
|
public ProducerConnection(String channel) {
|
||||||
|
this.channel = channel;
|
||||||
|
if (LOG.isDebugEnabled()) LOG.debug("{} Create new blank connection", this.printStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized String printStatus() {
|
||||||
|
return "[\"%s\" (%d)%s%s%s]".formatted(channel,
|
||||||
|
System.identityHashCode(this),
|
||||||
|
local != null ? ", local" : "",
|
||||||
|
remote != null ? ", remote" : "",
|
||||||
|
connectedState ? ((remoteTerminationState != null) ? (remoteTerminationState.isPresent() ? ", done with error" : ", done") : ", connected") : ", waiting"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized Mono<Void> connectLocal() {
|
public synchronized Mono<Void> connectLocal() {
|
||||||
return connected.asMono().publishOn(Schedulers.parallel()).then(Mono.defer(() -> {
|
if (LOG.isDebugEnabled()) LOG.debug("{} Local is asking to connect", this.printStatus());
|
||||||
|
return Mono.defer(() -> {
|
||||||
synchronized (ProducerConnection.this) {
|
synchronized (ProducerConnection.this) {
|
||||||
return remoteResult.asMono().doFinally(r -> reset());
|
return connectedSink.asMono();
|
||||||
}
|
}
|
||||||
})).doOnError(ex -> {
|
}).publishOn(Schedulers.parallel()).then(Mono.defer(() -> {
|
||||||
synchronized (ProducerConnection.this) {
|
synchronized (ProducerConnection.this) {
|
||||||
remoteResult.emitError(ex, EmitFailureHandler.busyLooping(Duration.ofMillis(100)));
|
if (LOG.isDebugEnabled()) LOG.debug("{} Local is connected", this.printStatus());
|
||||||
|
return remoteTerminationSink.asMono().publishOn(Schedulers.parallel());
|
||||||
}
|
}
|
||||||
}).doFinally(ended -> reset());
|
})).doFinally(s -> {
|
||||||
|
if (s != SignalType.ON_ERROR) {
|
||||||
|
synchronized (ProducerConnection.this) {
|
||||||
|
//reset(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized Flux<Payload> connectRemote() {
|
public synchronized Flux<Payload> connectRemote() {
|
||||||
return connected.asMono().publishOn(Schedulers.parallel()).thenMany(Flux.defer(() -> {
|
if (LOG.isDebugEnabled()) LOG.debug("{} Remote is asking to connect", this.printStatus());
|
||||||
|
return Mono.defer(() -> {
|
||||||
synchronized (ProducerConnection.this) {
|
synchronized (ProducerConnection.this) {
|
||||||
|
return connectedSink.asMono();
|
||||||
|
}
|
||||||
|
}).publishOn(Schedulers.parallel()).thenMany(Flux.defer(() -> {
|
||||||
|
synchronized (ProducerConnection.this) {
|
||||||
|
if (LOG.isDebugEnabled()) LOG.debug("{} Remote is connected", this.printStatus());
|
||||||
return local;
|
return local;
|
||||||
}
|
}
|
||||||
}).doOnError(ex -> {
|
})).doOnError(ex -> {
|
||||||
synchronized (ProducerConnection.this) {
|
synchronized (ProducerConnection.this) {
|
||||||
remoteResult.emitError(ex, EmitFailureHandler.busyLooping(Duration.ofMillis(100)));
|
if (local != null && remoteTerminationState == null) {
|
||||||
|
remoteTerminationState = Optional.of(ex);
|
||||||
|
if (LOG.isDebugEnabled()) LOG.debug("%s Remote connection ended with failure, emitting termination failure".formatted(this.printStatus()), ex);
|
||||||
|
var sink = remoteTerminationSink;
|
||||||
|
reset(true);
|
||||||
|
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()));
|
||||||
}
|
}
|
||||||
})).doFinally(ended -> reset());
|
}
|
||||||
|
}).doFinally(s -> {
|
||||||
|
if (s != SignalType.ON_ERROR) {
|
||||||
|
synchronized (ProducerConnection.this) {
|
||||||
|
if (local != null && remoteTerminationState == null) {
|
||||||
|
assert connectedState;
|
||||||
|
remoteTerminationState = Optional.empty();
|
||||||
|
if (LOG.isDebugEnabled()) LOG.debug("{} Remote connection ended with status {}, emitting termination complete", this.printStatus(), s);
|
||||||
|
if (s == SignalType.CANCEL) {
|
||||||
|
remoteTerminationSink.emitError(new CancelledChannelException(), EmitFailureHandler.busyLooping(Duration.ofMillis(100)));
|
||||||
|
} else {
|
||||||
|
remoteTerminationSink.emitEmpty(EmitFailureHandler.busyLooping(Duration.ofMillis(100)));
|
||||||
|
}
|
||||||
|
if (LOG.isDebugEnabled()) LOG.debug("{} Remote connection ended with status {}, emitted termination complete", this.printStatus(), s);
|
||||||
|
}
|
||||||
|
reset(true);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void reset() {
|
public synchronized void reset(boolean resettingFromRemote) {
|
||||||
if (local != null && remote != null) {
|
if (LOG.isDebugEnabled()) LOG.debug("{} Reset started", this.printStatus());
|
||||||
remoteResult.emitEmpty(EmitFailureHandler.busyLooping(Duration.ofMillis(100)));
|
if (connectedState) {
|
||||||
|
if (remoteTerminationState == 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();
|
||||||
|
remoteTerminationState = Optional.of(ex);
|
||||||
|
remoteTerminationSink.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());
|
||||||
|
remoteTerminationState = Optional.empty();
|
||||||
|
remoteTerminationSink.emitEmpty(EmitFailureHandler.busyLooping(Duration.ofMillis(100)));
|
||||||
|
if (LOG.isDebugEnabled()) LOG.debug("{} The previous connection has been interrupted", this.printStatus());
|
||||||
|
}
|
||||||
local = null;
|
local = null;
|
||||||
remote = null;
|
remote = null;
|
||||||
connected = Sinks.empty();
|
connectedState = false;
|
||||||
remoteResult = Sinks.empty();
|
connectedSink = Sinks.empty();
|
||||||
}
|
remoteTerminationState = null;
|
||||||
|
remoteTerminationSink = Sinks.empty();
|
||||||
|
if (LOG.isDebugEnabled()) LOG.debug("{} Reset ended", this.printStatus());
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void registerRemote() {
|
public synchronized void registerRemote() {
|
||||||
|
if (LOG.isDebugEnabled()) LOG.debug("{} Remote is trying to register", this.printStatus());
|
||||||
if (this.remote != null) {
|
if (this.remote != null) {
|
||||||
|
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.remote = new Object();
|
||||||
|
if (LOG.isDebugEnabled()) LOG.debug("{} Remote registered", this.printStatus());
|
||||||
onChanged();
|
onChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void registerLocal(Flux<Payload> local) {
|
public synchronized void registerLocal(Flux<Payload> local) {
|
||||||
|
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());
|
||||||
throw new IllegalStateException("Local is already registered");
|
throw new IllegalStateException("Local is already registered");
|
||||||
}
|
}
|
||||||
this.local = local;
|
this.local = local;
|
||||||
|
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 (local != null && remote != null) {
|
if (local != null && remote != null) {
|
||||||
connected.emitEmpty(EmitFailureHandler.busyLooping(Duration.ofMillis(100)));
|
connectedState = true;
|
||||||
|
if (LOG.isDebugEnabled()) LOG.debug("{} Connected successfully! Emitting connected event", this.printStatus());
|
||||||
|
connectedSink.emitEmpty(EmitFailureHandler.busyLooping(Duration.ofMillis(100)));
|
||||||
|
if (LOG.isDebugEnabled()) LOG.debug("{} Connected successfully! Emitted connected event", this.printStatus());
|
||||||
|
} else {
|
||||||
|
if (LOG.isDebugEnabled()) LOG.debug("{} Still not connected", this.printStatus());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
<!-- log only INFO, WARN, ERROR and FATAL logging by classes in this package -->
|
<!-- log only INFO, WARN, ERROR and FATAL logging by classes in this package -->
|
||||||
<AsyncLogger name="io.netty" level="INFO" additivity="false"/>
|
<AsyncLogger name="io.netty" level="INFO" additivity="false"/>
|
||||||
<AsyncLogger name="io.rsocket" level="INFO" additivity="false"/>
|
<AsyncLogger name="io.rsocket" level="INFO" additivity="false"/>
|
||||||
|
<AsyncLogger name="it.tdlight.reactiveapi.rsocket" level="DEBUG"/>
|
||||||
|
|
||||||
<AsyncRoot level="DEBUG">
|
<AsyncRoot level="DEBUG">
|
||||||
<filters>
|
<filters>
|
||||||
|
@ -212,12 +212,14 @@ public abstract class TestChannel {
|
|||||||
.map(Integer::parseUnsignedInt)
|
.map(Integer::parseUnsignedInt)
|
||||||
.take(50, true)
|
.take(50, true)
|
||||||
.collect(Collectors.toCollection(IntArrayList::new));
|
.collect(Collectors.toCollection(IntArrayList::new));
|
||||||
|
Assertions.assertThrows(Throwable.class, () -> {
|
||||||
var response = Flux
|
var response = Flux
|
||||||
.merge(isConsumerClient() ? (List.of(eventProducer, eventConsumer)) : List.of(eventConsumer, eventProducer))
|
.merge(isConsumerClient() ? (List.of(eventProducer, eventConsumer)) : List.of(eventConsumer, eventProducer))
|
||||||
.blockLast(Duration.ofSeconds(5));
|
.blockLast(Duration.ofSeconds(5));
|
||||||
data.removeElements(50, 100);
|
data.removeElements(50, 100);
|
||||||
Assertions.assertEquals(response, data);
|
Assertions.assertEquals(response, data);
|
||||||
System.out.println(response);
|
System.out.println(response);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -274,14 +276,14 @@ public abstract class TestChannel {
|
|||||||
.map(Integer::parseUnsignedInt)
|
.map(Integer::parseUnsignedInt)
|
||||||
.take(10, true)
|
.take(10, true)
|
||||||
.collect(Collectors.toCollection(IntArrayList::new))
|
.collect(Collectors.toCollection(IntArrayList::new))
|
||||||
.block();
|
.block(Duration.ofSeconds(5));
|
||||||
var receiver2 = consumer
|
var receiver2 = consumer
|
||||||
.consumeMessages()
|
.consumeMessages()
|
||||||
.limitRate(1)
|
.limitRate(1)
|
||||||
.map(Timestamped::data)
|
.map(Timestamped::data)
|
||||||
.map(Integer::parseUnsignedInt)
|
.map(Integer::parseUnsignedInt)
|
||||||
.collect(Collectors.toCollection(IntArrayList::new))
|
.collect(Collectors.toCollection(IntArrayList::new))
|
||||||
.block();
|
.block(Duration.ofSeconds(5));
|
||||||
Assertions.assertNotNull(receiver1);
|
Assertions.assertNotNull(receiver1);
|
||||||
Assertions.assertNotNull(receiver2);
|
Assertions.assertNotNull(receiver2);
|
||||||
receiver1.addAll(receiver2);
|
receiver1.addAll(receiver2);
|
||||||
|
Loading…
Reference in New Issue
Block a user