Fix some connection errors
This commit is contained in:
parent
58770ca649
commit
677ceb70a1
@ -148,7 +148,7 @@ public class AtomixReactiveApi implements ReactiveApi {
|
||||
if (publisher != null) {
|
||||
publisher.handleRequest(req.data());
|
||||
} else {
|
||||
LOG.error("Dropped request because no session is found: {}", req);
|
||||
LOG.debug("Dropped request because no session is found: {}", req);
|
||||
}
|
||||
})
|
||||
.subscribeOn(Schedulers.parallel())
|
||||
|
@ -67,6 +67,7 @@ import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.FluxSink.OverflowStrategy;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.publisher.Sinks.EmitFailureHandler;
|
||||
import reactor.core.publisher.Sinks.EmitResult;
|
||||
import reactor.core.publisher.Sinks.Many;
|
||||
import reactor.core.scheduler.Scheduler.Worker;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
@ -503,8 +504,21 @@ public abstract class ReactiveApiPublisher {
|
||||
|
||||
public void handleRequest(OnRequest<TdApi.Object> onRequestObj) {
|
||||
handleRequestInternal(onRequestObj, response -> {
|
||||
EmitResult status;
|
||||
synchronized (this.responses) {
|
||||
this.responses.emitNext(response, EmitFailureHandler.FAIL_FAST);
|
||||
status = this.responses.tryEmitNext(response);
|
||||
}
|
||||
if (status.isFailure()) {
|
||||
switch (status) {
|
||||
case FAIL_ZERO_SUBSCRIBER ->
|
||||
LOG.warn("Failed to send response of request {}, user {}, client {}: no subscribers",
|
||||
onRequestObj.userId(), onRequestObj.userId(), onRequestObj.clientId());
|
||||
case FAIL_OVERFLOW ->
|
||||
LOG.warn("Failed to send response of request {}, user {}, client {}: too many unsent responses",
|
||||
onRequestObj.userId(), onRequestObj.userId(), onRequestObj.clientId());
|
||||
default -> LOG.error("Failed to send response of request {}, user {}, client {}: {}",
|
||||
onRequestObj.userId(), onRequestObj.userId(), onRequestObj.clientId(), status);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -185,16 +185,28 @@ public class ReactorUtils {
|
||||
}
|
||||
}
|
||||
);
|
||||
AtomicReference<Throwable> startEx = new AtomicReference<>();
|
||||
var disposable = flux
|
||||
.subscribeOn(Schedulers.parallel())
|
||||
.publishOn(Schedulers.boundedElastic())
|
||||
.subscribe(queue::add);
|
||||
.subscribe(queue::add, ex -> {
|
||||
startEx.set(ex);
|
||||
var refVal = ref.get();
|
||||
if (refVal != null) {
|
||||
refVal.error(ex);
|
||||
}
|
||||
});
|
||||
queue.startQueue();
|
||||
return Flux.create(sink -> {
|
||||
sink.onDispose(() -> {
|
||||
disposable.dispose();
|
||||
queue.close();
|
||||
});
|
||||
var startExVal = startEx.get();
|
||||
if (startExVal != null) {
|
||||
sink.error(startExVal);
|
||||
return;
|
||||
}
|
||||
ref.set(sink);
|
||||
sink.onCancel(() -> ref.set(null));
|
||||
});
|
||||
|
@ -99,7 +99,9 @@ public class Stats extends Thread {
|
||||
processedUpdatesRateSum += processedUpdatesRate;
|
||||
clientBoundEventsRateSum += clientBoundEventsRate;
|
||||
sentClientBoundEventsRateSum += sentClientBoundEventsRate;
|
||||
out.append(String.format("%d:\t", clientIds.getLong(i)));
|
||||
if (LOG.isTraceEnabled()) {
|
||||
out.append(String.format("%d:\t", clientIds.getLong(i)));
|
||||
}
|
||||
} else {
|
||||
receivedUpdatesRate = receivedUpdatesRateSum;
|
||||
diskBuffered = diskBufferedSum;
|
||||
@ -110,16 +112,18 @@ public class Stats extends Thread {
|
||||
sentClientBoundEventsRate = sentClientBoundEventsRateSum;
|
||||
out.append("Total:\t");
|
||||
}
|
||||
out.append(String.format(
|
||||
"\tUpdates:\t[received %03.2fHz\tbuffered: %03.2fHz (RAM: %d HDD: %d)\tprocessed: %03.2fHz]\tClient bound events: %03.2fHz\tProcessed events: %03.2fHz\t%n",
|
||||
receivedUpdatesRate,
|
||||
bufferedUpdatesRate,
|
||||
ramBuffered,
|
||||
diskBuffered,
|
||||
processedUpdatesRate,
|
||||
clientBoundEventsRate,
|
||||
sentClientBoundEventsRate
|
||||
));
|
||||
if (i == currentClients || LOG.isTraceEnabled()) {
|
||||
out.append(String.format(
|
||||
"\tUpdates:\t[received %03.2fHz\tbuffered: %03.2fHz (RAM: %d HDD: %d)\tprocessed: %03.2fHz]\tClient bound events: %03.2fHz\tProcessed events: %03.2fHz\t%n",
|
||||
receivedUpdatesRate,
|
||||
bufferedUpdatesRate,
|
||||
ramBuffered,
|
||||
diskBuffered,
|
||||
processedUpdatesRate,
|
||||
clientBoundEventsRate,
|
||||
sentClientBoundEventsRate
|
||||
));
|
||||
}
|
||||
}
|
||||
out.append(String.format("%n"));
|
||||
|
||||
|
@ -47,14 +47,13 @@ public class TdlibChannelsSharedHost implements Closeable {
|
||||
private final TdlibChannelsServers tdServersChannels;
|
||||
private final Disposable responsesSub;
|
||||
private final AtomicReference<Disposable> requestsSub = new AtomicReference<>();
|
||||
private final Many<OnResponse<TdApi.Object>> responses = Sinks.many().multicast().directAllOrNothing();
|
||||
private final Many<OnResponse<TdApi.Object>> responses = Sinks.many().multicast().onBackpressureBuffer(65_535);
|
||||
private final Map<String, Many<Flux<ClientBoundEvent>>> events;
|
||||
private final Flux<Timestamped<OnRequest<Object>>> requests;
|
||||
|
||||
public TdlibChannelsSharedHost(Set<String> allLanes, TdlibChannelsServers tdServersChannels) {
|
||||
this.tdServersChannels = tdServersChannels;
|
||||
this.responsesSub = Mono.defer(() -> tdServersChannels.response()
|
||||
.sendMessages(responses.asFlux()/*.log("responses", Level.FINE)*/))
|
||||
this.responsesSub = tdServersChannels.response().sendMessages(responses.asFlux()/*.log("responses", Level.FINE)*/)
|
||||
.repeatWhen(REPEAT_STRATEGY)
|
||||
.retryWhen(RETRY_STRATEGY)
|
||||
.subscribeOn(Schedulers.parallel())
|
||||
@ -64,8 +63,7 @@ public class TdlibChannelsSharedHost implements Closeable {
|
||||
Flux<ClientBoundEvent> outputEventsFlux = Flux
|
||||
.merge(sink.asFlux().map(flux -> flux.publishOn(Schedulers.parallel())), Integer.MAX_VALUE, 256)
|
||||
.doFinally(s -> LOG.debug("Output events flux of lane \"{}\" terminated with signal {}", lane, s));
|
||||
Mono.defer(() -> tdServersChannels.events(lane)
|
||||
.sendMessages(outputEventsFlux))
|
||||
tdServersChannels.events(lane).sendMessages(outputEventsFlux)
|
||||
.repeatWhen(REPEAT_STRATEGY)
|
||||
.retryWhen(RETRY_STRATEGY)
|
||||
.subscribeOn(Schedulers.parallel())
|
||||
@ -92,9 +90,9 @@ public class TdlibChannelsSharedHost implements Closeable {
|
||||
if (eventsSink == null) {
|
||||
throw new IllegalArgumentException("Lane " + lane + " does not exist");
|
||||
}
|
||||
eventsSink.emitNext(eventFlux.takeUntilOther(canceller.asMono()),
|
||||
EmitFailureHandler.busyLooping(Duration.ofMillis(100))
|
||||
);
|
||||
synchronized (events) {
|
||||
eventsSink.emitNext(eventFlux.takeUntilOther(canceller.asMono()), EmitFailureHandler.FAIL_FAST);
|
||||
}
|
||||
return () -> canceller.tryEmitEmpty();
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,6 @@ import reactor.core.publisher.Sinks.EmitFailureHandler;
|
||||
import reactor.core.publisher.Sinks.Empty;
|
||||
import reactor.core.publisher.Sinks.Many;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
import reactor.util.concurrent.Queues;
|
||||
|
||||
public class ConsumerConnection<T> {
|
||||
|
||||
@ -61,20 +60,7 @@ public class ConsumerConnection<T> {
|
||||
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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
onRemoteLastError(ex);
|
||||
} else {
|
||||
remoteCount--;
|
||||
if (LOG.isDebugEnabled()) {
|
||||
@ -88,17 +74,7 @@ public class ConsumerConnection<T> {
|
||||
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);
|
||||
onLastFinally(s);
|
||||
} else {
|
||||
remoteCount--;
|
||||
if (LOG.isDebugEnabled()) LOG.debug("{} Remote connection ended with status {}, but at least one remote is still online", this.printStatus(), s);
|
||||
@ -122,17 +98,39 @@ public class ConsumerConnection<T> {
|
||||
})
|
||||
.map(element -> new Timestamped<>(System.currentTimeMillis(), element));
|
||||
}
|
||||
})).doFinally(s -> {
|
||||
})).doOnError(this::onRemoteLastError).doFinally(this::onLastFinally);
|
||||
}
|
||||
|
||||
private synchronized void onLastFinally(SignalType s) {
|
||||
if (remoteCount > 0 && localTerminationState == null) {
|
||||
assert connectedState;
|
||||
var ex = new CancelledChannelException();
|
||||
localTerminationState = Optional.of(ex);
|
||||
if (s == SignalType.CANCEL) {
|
||||
synchronized (ConsumerConnection.this) {
|
||||
local = null;
|
||||
var ex = new InterruptedException();
|
||||
localTerminationState = Optional.of(ex);
|
||||
if (LOG.isDebugEnabled()) LOG.debug("{} Local is cancelled", this.printStatus());
|
||||
localTerminationSink.emitError(ex, EmitFailureHandler.busyLooping(Duration.ofMillis(100)));
|
||||
localTerminationSink.emitError(ex, 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);
|
||||
}
|
||||
|
||||
private synchronized void onRemoteLastError(Throwable ex) {
|
||||
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()));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized Mono<Void> connectRemote() {
|
||||
@ -154,7 +152,7 @@ public class ConsumerConnection<T> {
|
||||
if (connectedState) {
|
||||
if (localTerminationState == null) {
|
||||
if (LOG.isDebugEnabled()) LOG.debug("{} The previous connection is still marked as open but not terminated, interrupting it", this.printStatus());
|
||||
var ex = new InterruptedException();
|
||||
var ex = new InterruptedException("Interrupted this connection because a new one is being prepared");
|
||||
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());
|
||||
@ -167,7 +165,7 @@ public class ConsumerConnection<T> {
|
||||
}
|
||||
local = null;
|
||||
remoteCount = 0;
|
||||
remotes.emitComplete(EmitFailureHandler.FAIL_FAST);
|
||||
remotes.tryEmitComplete();
|
||||
remotes = Sinks.many().replay().all();
|
||||
connectedState = false;
|
||||
connectedSink = Sinks.empty();
|
||||
@ -179,7 +177,7 @@ public class ConsumerConnection<T> {
|
||||
public synchronized void registerRemote(Flux<Payload> remote) {
|
||||
if (LOG.isDebugEnabled()) LOG.debug("{} Remote is trying to register", this.printStatus());
|
||||
this.remoteCount++;
|
||||
this.remotes.emitNext(remote, EmitFailureHandler.FAIL_FAST);
|
||||
this.remotes.tryEmitNext(remote);
|
||||
if (LOG.isDebugEnabled()) LOG.debug("{} Remote registered", this.printStatus());
|
||||
onChanged();
|
||||
}
|
||||
|
@ -56,8 +56,12 @@ public class ProducerConnection<T> {
|
||||
return remoteTerminationSink.asMono().publishOn(Schedulers.parallel());
|
||||
}
|
||||
})).doFinally(s -> {
|
||||
if (s == SignalType.CANCEL) {
|
||||
if (s == SignalType.ON_ERROR || s == SignalType.CANCEL) {
|
||||
synchronized (ProducerConnection.this) {
|
||||
if (connectedState) {
|
||||
connectedState = false;
|
||||
connectedSink = Sinks.empty();
|
||||
}
|
||||
local = null;
|
||||
if (LOG.isDebugEnabled()) LOG.debug("{} Local is cancelled", this.printStatus());
|
||||
}
|
||||
@ -126,7 +130,7 @@ public class ProducerConnection<T> {
|
||||
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();
|
||||
var ex = new InterruptedException("Interrupted this connection because a new one is being prepared");
|
||||
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());
|
||||
@ -148,10 +152,6 @@ public class ProducerConnection<T> {
|
||||
|
||||
public synchronized void registerRemote() {
|
||||
if (LOG.isDebugEnabled()) LOG.debug("{} Remote is trying to register", this.printStatus());
|
||||
if (this.remoteCount > 0) {
|
||||
if (LOG.isDebugEnabled()) LOG.debug("{} Remote was already registered", this.printStatus());
|
||||
throw new IllegalStateException("Remote is already registered");
|
||||
}
|
||||
this.remoteCount++;
|
||||
if (LOG.isDebugEnabled()) LOG.debug("{} Remote registered", this.printStatus());
|
||||
onChanged();
|
||||
|
@ -22,6 +22,8 @@ public class RSocketUtils {
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
} finally {
|
||||
payload.release();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -7,12 +7,9 @@ import it.tdlight.reactiveapi.EventConsumer;
|
||||
import it.tdlight.reactiveapi.EventProducer;
|
||||
import it.tdlight.reactiveapi.RSocketParameters;
|
||||
import it.tdlight.reactiveapi.Timestamped;
|
||||
import it.tdlight.reactiveapi.rsocket.CancelledChannelException;
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
@ -25,6 +22,7 @@ import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.function.Executable;
|
||||
import reactor.core.Exceptions;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
@ -449,7 +447,7 @@ public abstract class TestChannel {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFailTwoSubscribers() {
|
||||
public void testTwoSubscribers() {
|
||||
var dataFlux = Flux.fromIterable(data).publish().autoConnect();
|
||||
var exRef1 = new AtomicReference<Throwable>();
|
||||
var exRef2 = new AtomicReference<Throwable>();
|
||||
@ -459,35 +457,43 @@ public abstract class TestChannel {
|
||||
.retryWhen(Retry.fixedDelay(5, Duration.ofSeconds(1)))
|
||||
.subscribe(n -> {}, ex -> exRef1.set(ex));
|
||||
|
||||
Assertions.assertThrows(IllegalStateException.class, () -> {
|
||||
try {
|
||||
Mono
|
||||
.when(consumer
|
||||
.consumeMessages()
|
||||
.limitRate(1)
|
||||
.map(Timestamped::data)
|
||||
.map(Integer::parseUnsignedInt)
|
||||
.log("consumer-1", Level.INFO)
|
||||
.doOnError(ex -> exRef2.set(ex)),
|
||||
consumer
|
||||
.consumeMessages()
|
||||
.limitRate(1)
|
||||
.map(Timestamped::data)
|
||||
.map(Integer::parseUnsignedInt)
|
||||
.log("consumer-2", Level.INFO)
|
||||
.onErrorResume(io.rsocket.exceptions.ApplicationErrorException.class,
|
||||
ex -> Mono.error(new IllegalStateException(ex))
|
||||
)
|
||||
)
|
||||
.block();
|
||||
Assertions.assertNull(exRef1.get());
|
||||
Assertions.assertNull(exRef2.get());
|
||||
} catch (RuntimeException ex) {
|
||||
throw Exceptions.unwrap(ex);
|
||||
} finally {
|
||||
eventProducer.dispose();
|
||||
var exe = new Executable() {
|
||||
@Override
|
||||
public void execute() throws Throwable {
|
||||
try {
|
||||
Mono
|
||||
.when(consumer
|
||||
.consumeMessages()
|
||||
.limitRate(1)
|
||||
.map(Timestamped::data)
|
||||
.map(Integer::parseUnsignedInt)
|
||||
.log("consumer-1", Level.INFO)
|
||||
.doOnError(ex -> exRef2.set(ex)),
|
||||
consumer
|
||||
.consumeMessages()
|
||||
.limitRate(1)
|
||||
.map(Timestamped::data)
|
||||
.map(Integer::parseUnsignedInt)
|
||||
.log("consumer-2", Level.INFO)
|
||||
.onErrorResume(io.rsocket.exceptions.ApplicationErrorException.class,
|
||||
ex -> Mono.error(new IllegalStateException(ex))
|
||||
)
|
||||
)
|
||||
.block();
|
||||
Assertions.assertNull(exRef1.get());
|
||||
Assertions.assertNull(exRef2.get());
|
||||
} catch (RuntimeException ex) {
|
||||
throw Exceptions.unwrap(ex);
|
||||
} finally {
|
||||
eventProducer.dispose();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
if (isConsumerClient()) {
|
||||
Assertions.assertDoesNotThrow(exe);
|
||||
} else {
|
||||
Assertions.assertThrows(IllegalStateException.class, exe);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
Loading…
Reference in New Issue
Block a user