Fix performance problems

This commit is contained in:
Andrea Cavalli 2022-09-13 22:15:18 +02:00
parent 5e40530a20
commit 8651ce3e97
14 changed files with 175 additions and 142 deletions

View File

@ -20,18 +20,21 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.Disposable;
import reactor.core.publisher.Mono;
import reactor.core.publisher.Sinks.EmitFailureHandler;
import reactor.core.scheduler.Schedulers;
public class AtomixReactiveApi implements ReactiveApi {
private static final Logger LOG = LoggerFactory.getLogger(AtomixReactiveApi.class);
private final boolean clientOnly;
private final AtomixReactiveApiMode mode;
private final KafkaSharedTdlibClients kafkaSharedTdlibClients;
@Nullable
private final KafkaSharedTdlibServers kafkaSharedTdlibServers;
private final ReactiveApiMultiClient client;
private final Set<ResultingEventTransformer> resultingEventTransformerSet;
/**
@ -44,12 +47,19 @@ public class AtomixReactiveApi implements ReactiveApi {
@Nullable
private final DiskSessionsManager diskSessions;
private volatile boolean closeRequested;
private volatile Disposable requestsSub;
public AtomixReactiveApi(boolean clientOnly,
public enum AtomixReactiveApiMode {
CLIENT,
SERVER,
FULL
}
public AtomixReactiveApi(AtomixReactiveApiMode mode,
KafkaParameters kafkaParameters,
@Nullable DiskSessionsManager diskSessions,
@NotNull Set<ResultingEventTransformer> resultingEventTransformerSet) {
this.clientOnly = clientOnly;
this.mode = mode;
var kafkaTDLibRequestProducer = new KafkaTdlibRequestProducer(kafkaParameters);
var kafkaTDLibResponseConsumer = new KafkaTdlibResponseConsumer(kafkaParameters);
var kafkaClientBoundConsumer = new KafkaClientBoundConsumer(kafkaParameters);
@ -57,10 +67,14 @@ public class AtomixReactiveApi implements ReactiveApi {
kafkaTDLibResponseConsumer,
kafkaClientBoundConsumer
);
this.kafkaSharedTdlibClients = new KafkaSharedTdlibClients(kafkaTdlibClientsChannels);
if (clientOnly) {
this.kafkaSharedTdlibServers = null;
if (mode != AtomixReactiveApiMode.SERVER) {
this.kafkaSharedTdlibClients = new KafkaSharedTdlibClients(kafkaTdlibClientsChannels);
this.client = new LiveAtomixReactiveApiClient(kafkaSharedTdlibClients);
} else {
this.kafkaSharedTdlibClients = null;
this.client = null;
}
if (mode != AtomixReactiveApiMode.CLIENT) {
var kafkaTDLibRequestConsumer = new KafkaTdlibRequestConsumer(kafkaParameters);
var kafkaTDLibResponseProducer = new KafkaTdlibResponseProducer(kafkaParameters);
var kafkaClientBoundProducer = new KafkaClientBoundProducer(kafkaParameters);
@ -69,6 +83,8 @@ public class AtomixReactiveApi implements ReactiveApi {
kafkaClientBoundProducer
);
this.kafkaSharedTdlibServers = new KafkaSharedTdlibServers(kafkaTDLibServer);
} else {
this.kafkaSharedTdlibServers = null;
}
this.resultingEventTransformerSet = resultingEventTransformerSet;
@ -90,7 +106,7 @@ public class AtomixReactiveApi implements ReactiveApi {
.flatMapIterable(a -> a)
.map(a -> new DiskSessionAndId(a.getValue(), a.getKey()));
return idsSavedIntoLocalConfiguration
var loadSessions = idsSavedIntoLocalConfiguration
.filter(diskSessionAndId -> {
try {
diskSessionAndId.diskSession().validate();
@ -111,13 +127,22 @@ public class AtomixReactiveApi implements ReactiveApi {
})
.then()
.doOnTerminate(() -> LOG.info("Loaded all saved sessions from disk"));
return loadSessions.then(Mono.fromRunnable(() -> {
if (kafkaSharedTdlibServers != null) {
requestsSub = kafkaSharedTdlibServers.requests()
.doOnNext(req -> localSessions.get(req.data().userId()).handleRequest(req.data()))
.subscribeOn(Schedulers.parallel())
.subscribe();
}
}));
}
@Override
public Mono<CreateSessionResponse> createSession(CreateSessionRequest req) {
LOG.debug("Received create session request: {}", req);
if (clientOnly) {
if (mode == AtomixReactiveApiMode.CLIENT) {
return Mono.error(new UnsupportedOperationException("This is a client, it can't have own sessions"));
}
@ -225,8 +250,8 @@ public class AtomixReactiveApi implements ReactiveApi {
}
@Override
public ReactiveApiClient client(long userId) {
return new LiveAtomixReactiveApiClient(kafkaSharedTdlibClients, userId);
public ReactiveApiMultiClient client() {
return client;
}
@Override
@ -238,9 +263,17 @@ public class AtomixReactiveApi implements ReactiveApi {
} else {
kafkaServerProducersStopper = Mono.empty();
}
Mono<?> kafkaClientProducersStopper = Mono
.fromRunnable(kafkaSharedTdlibClients::close)
.subscribeOn(Schedulers.boundedElastic());
Mono<?> kafkaClientProducersStopper;
if (kafkaSharedTdlibClients != null) {
kafkaClientProducersStopper = Mono
.fromRunnable(kafkaSharedTdlibClients::close)
.subscribeOn(Schedulers.boundedElastic());
} else {
kafkaClientProducersStopper = Mono.empty();
}
if (requestsSub != null) {
requestsSub.dispose();
}
return Mono.when(kafkaServerProducersStopper, kafkaClientProducersStopper);
}

View File

@ -4,6 +4,8 @@ import static it.tdlight.reactiveapi.Event.SERIAL_VERSION;
import it.tdlight.jni.TdApi;
import it.tdlight.jni.TdApi.Error;
import it.tdlight.jni.TdApi.Function;
import it.tdlight.jni.TdApi.Object;
import it.tdlight.reactiveapi.Event.ClientBoundEvent;
import it.tdlight.reactiveapi.Event.Ignored;
import it.tdlight.reactiveapi.Event.OnBotLoginCodeRequested;
@ -31,19 +33,17 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.Disposable;
import reactor.core.publisher.Mono;
import reactor.core.publisher.Sinks;
import reactor.core.publisher.Sinks.EmitFailureHandler;
import reactor.core.publisher.Sinks.Many;
import reactor.core.scheduler.Schedulers;
import reactor.util.concurrent.Queues;
abstract class BaseAtomixReactiveApiClient implements ReactiveApiClient, AutoCloseable {
abstract class BaseAtomixReactiveApiClient implements ReactiveApiMultiClient {
private static final Logger LOG = LoggerFactory.getLogger(BaseAtomixReactiveApiClient.class);
private static final Duration TEN_MS = Duration.ofMillis(10);
private static final Duration HUNDRED_MS = Duration.ofMillis(100);
private static final long EMPTY_USER_ID = 0;
protected final long userId;
// Temporary id used to make requests
private final long clientId;
private final Many<OnRequest<?>> requests;
@ -51,33 +51,23 @@ abstract class BaseAtomixReactiveApiClient implements ReactiveApiClient, AutoClo
= new ConcurrentHashMap<>();
private final AtomicLong requestId = new AtomicLong(0);
private final Disposable subscription;
private final boolean pullMode;
public BaseAtomixReactiveApiClient(KafkaSharedTdlibClients kafkaSharedTdlibClients, long userId) {
this.userId = userId;
public BaseAtomixReactiveApiClient(KafkaSharedTdlibClients kafkaSharedTdlibClients) {
this.clientId = System.nanoTime();
this.requests = kafkaSharedTdlibClients.requests();
this.pullMode = kafkaSharedTdlibClients.canRequestsWait();
var disposable2 = kafkaSharedTdlibClients.responses(clientId)
.doOnNext(response -> {
var responseSink = responses.get(response.data().requestId());
if (responseSink == null) {
LOG.debug("Bot #IDU{} received a response for an unknown request id: {}",
userId, response.data().requestId());
return;
}
responseSink.complete(response);
})
.subscribeOn(Schedulers.parallel())
.subscribe();
this.subscription = () -> {
disposable2.dispose();
};
this.subscription = kafkaSharedTdlibClients.responses().doOnNext(response -> {
var responseSink = responses.get(response.data().requestId());
if (responseSink == null) {
LOG.debug("Bot received a response for an unknown request id: {}", response.data().requestId());
return;
}
responseSink.complete(response);
}).subscribeOn(Schedulers.parallel()).subscribe();
}
@Override
public final <T extends TdApi.Object> Mono<T> request(TdApi.Function<T> request, Instant timeout) {
public <T extends Object> Mono<T> request(long userId, Function<T> request, Instant timeout) {
return Mono.defer(() -> {
var requestId = this.requestId.getAndIncrement();
var timeoutError = new TdError(408, "Request Timeout");
@ -110,22 +100,12 @@ abstract class BaseAtomixReactiveApiClient implements ReactiveApiClient, AutoClo
}
})
.doFinally(s -> this.responses.remove(requestId));
requests.emitNext(new Request<>(userId, clientId, requestId, request, timeout), EmitFailureHandler.busyLooping(TEN_MS));
requests.emitNext(new Request<>(userId, clientId, requestId, request, timeout), EmitFailureHandler.busyLooping(
HUNDRED_MS));
return response;
});
}
@Override
public final long getUserId() {
return userId;
}
@Override
public final boolean isPullMode() {
return pullMode;
}
static ClientBoundEvent deserializeEvent(byte[] bytes) {
try (var byteArrayInputStream = new ByteArrayInputStream(bytes)) {
try (var is = new DataInputStream(byteArrayInputStream)) {
@ -154,12 +134,14 @@ abstract class BaseAtomixReactiveApiClient implements ReactiveApiClient, AutoClo
}
@Override
public void close() {
subscription.dispose();
long now = System.currentTimeMillis();
responses.forEach((requestId, cf) -> cf.complete(new Timestamped<>(now,
new Response<>(clientId, requestId, userId, new Error(408, "Request Timeout"))
)));
responses.clear();
public Mono<Void> close() {
return Mono.fromRunnable(() -> {
subscription.dispose();
long now = System.currentTimeMillis();
responses.forEach((requestId, cf) -> cf.complete(new Timestamped<>(now,
new Response<>(clientId, requestId, EMPTY_USER_ID, new Error(408, "Request Timeout"))
)));
responses.clear();
});
}
}

View File

@ -4,6 +4,7 @@ import static java.util.Collections.unmodifiableSet;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import it.tdlight.reactiveapi.AtomixReactiveApi.AtomixReactiveApiMode;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
@ -66,7 +67,7 @@ public class Entrypoint {
@Nullable DiskSessionsManager diskSessions) {
Set<ResultingEventTransformer> resultingEventTransformerSet;
boolean clientOnly = false;
AtomixReactiveApiMode mode = AtomixReactiveApiMode.SERVER;
if (instanceSettings.client) {
if (diskSessions != null) {
throw new IllegalArgumentException("A client instance can't have a session manager!");
@ -74,7 +75,7 @@ public class Entrypoint {
if (instanceSettings.clientAddress == null) {
throw new IllegalArgumentException("A client instance must have an address (host:port)");
}
clientOnly = true;
mode = AtomixReactiveApiMode.CLIENT;
resultingEventTransformerSet = Set.of();
} else {
if (diskSessions == null) {
@ -103,7 +104,7 @@ public class Entrypoint {
var kafkaParameters = new KafkaParameters(clusterSettings, instanceSettings.id);
var api = new AtomixReactiveApi(clientOnly, kafkaParameters, diskSessions, resultingEventTransformerSet);
var api = new AtomixReactiveApi(mode, kafkaParameters, diskSessions, resultingEventTransformerSet);
LOG.info("Starting ReactiveApi...");

View File

@ -13,9 +13,7 @@ public class InstanceSettings {
public String id;
/**
* True if this is just a client, false if this is a complete node
* <p>
* A client is a lightweight node
* True if this is just a client, false if this is a server
*/
public boolean client;

View File

@ -7,6 +7,7 @@ import it.tdlight.common.utils.CantLoadLibrary;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.regex.Pattern;
import org.apache.kafka.clients.consumer.CommitFailedException;
@ -49,16 +50,16 @@ public abstract class KafkaConsumer<K> {
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, getChannelName().getDeserializerClass());
props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "latest");
props.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, toIntExact(Duration.ofMinutes(5).toMillis()));
if (isQuickResponse()) {
props.put(ConsumerConfig.HEARTBEAT_INTERVAL_MS_CONFIG, "5000");
props.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, "10000");
} else {
if (!isQuickResponse()) {
props.put(ConsumerConfig.HEARTBEAT_INTERVAL_MS_CONFIG, "10000");
props.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, "30000");
props.put(ConsumerConfig.FETCH_MIN_BYTES_CONFIG, "1048576");
props.put(ConsumerConfig.FETCH_MAX_WAIT_MS_CONFIG, "100");
}
ReceiverOptions<Integer, K> receiverOptions = ReceiverOptions.create(props);
ReceiverOptions<Integer, K> receiverOptions = ReceiverOptions.<Integer, K>create(props)
.commitInterval(Duration.ofSeconds(10))
.commitBatchSize(65535)
.maxCommitAttempts(100);
Pattern pattern;
if (userId == null) {
pattern = Pattern.compile("tdlib\\." + getChannelName() + "\\.\\d+");
@ -109,7 +110,9 @@ public abstract class KafkaConsumer<K> {
private Flux<Timestamped<K>> consumeMessagesInternal(@NotNull String subGroupId, @Nullable Long userId) {
return createReceiver(kafkaParameters.groupId() + "-" + subGroupId, userId)
.receive()
.receiveAutoAck(isQuickResponse() ? null : 32)
.concatMap(Flux::collectList)
.flatMapIterable(Function.identity())
.log("consume-messages" + (userId != null ? "-" + userId : ""),
Level.FINEST,
SignalType.REQUEST,
@ -117,7 +120,6 @@ public abstract class KafkaConsumer<K> {
SignalType.ON_ERROR,
SignalType.ON_COMPLETE
)
//.doOnNext(result -> result.receiverOffset().acknowledge())
.map(record -> {
if (record.timestampType() == TimestampType.CREATE_TIME) {
return new Timestamped<>(record.timestamp(), record.value());

View File

@ -26,9 +26,9 @@ public abstract class KafkaProducer<K> {
Map<String, Object> props = new HashMap<>();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaParameters.bootstrapServers());
props.put(ProducerConfig.CLIENT_ID_CONFIG, kafkaParameters.clientId());
//props.put(ProducerConfig.ACKS_CONFIG, "1");
props.put(ProducerConfig.ACKS_CONFIG, "0");
props.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);
props.put(ProducerConfig.ACKS_CONFIG, "1");
props.put(ProducerConfig.BATCH_SIZE_CONFIG, Integer.toString(32 * 1024));
props.put(ProducerConfig.LINGER_MS_CONFIG, "20");
props.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "snappy");
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, IntegerSerializer.class);
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, getChannelName().getSerializerClass());

View File

@ -1,7 +1,6 @@
package it.tdlight.reactiveapi;
import static java.util.Objects.requireNonNullElse;
import static reactor.core.publisher.Sinks.EmitFailureHandler.busyLooping;
import it.tdlight.jni.TdApi.Object;
import it.tdlight.reactiveapi.Event.ClientBoundEvent;
@ -39,31 +38,20 @@ public class KafkaSharedTdlibClients implements Closeable {
public KafkaSharedTdlibClients(KafkaTdlibClientsChannels kafkaTdlibClientsChannels) {
this.kafkaTdlibClientsChannels = kafkaTdlibClientsChannels;
this.responses = kafkaTdlibClientsChannels.response().consumeMessages("td-responses")
.publish(65535)
.autoConnect(1, this.responsesSub::set);
this.events = kafkaTdlibClientsChannels.events().consumeMessages("td-handler")
.publish(65535)
.autoConnect(1, this.eventsSub::set);
this.responses = kafkaTdlibClientsChannels.response().consumeMessages("td-responses");
this.events = kafkaTdlibClientsChannels.events().consumeMessages("td-handler");
this.requestsSub = kafkaTdlibClientsChannels.request()
.sendMessages(0L, requests.asFlux())
.subscribeOn(Schedulers.parallel())
.subscribe();
}
public Flux<Timestamped<OnResponse<Object>>> responses(long clientId) {
return responses
.filter(group -> group.data().clientId() == clientId)
//.onBackpressureBuffer(8192, BufferOverflowStrategy.DROP_OLDEST)
.log("req-" + clientId, Level.FINEST, SignalType.REQUEST);
public Flux<Timestamped<OnResponse<Object>>> responses() {
return responses;
}
public Flux<Timestamped<ClientBoundEvent>> events(long userId) {
return events
.filter(group -> group.data().userId() == userId)
//.onBackpressureBuffer(8192, BufferOverflowStrategy.DROP_OLDEST)
.doOnSubscribe(s -> LOG.info("Reading updates of client: {}", userId))
.log("event-" + userId, Level.FINEST, SignalType.REQUEST);
public Flux<Timestamped<ClientBoundEvent>> events() {
return events;
}
public Many<OnRequest<?>> requests() {
@ -83,8 +71,4 @@ public class KafkaSharedTdlibClients implements Closeable {
}
kafkaTdlibClientsChannels.close();
}
public boolean canRequestsWait() {
return false;
}
}

View File

@ -1,7 +1,6 @@
package it.tdlight.reactiveapi;
import static java.util.Objects.requireNonNullElse;
import static reactor.core.publisher.Sinks.EmitFailureHandler.busyLooping;
import it.tdlight.jni.TdApi;
import it.tdlight.jni.TdApi.Object;
@ -39,16 +38,13 @@ public class KafkaSharedTdlibServers implements Closeable {
.subscribeOn(Schedulers.parallel())
.subscribe();
this.requests = kafkaTdlibServersChannels.request()
.consumeMessages("td-requests")
.publish(65535)
.autoConnect(1, this.requestsSub::set);
.consumeMessages("td-requests");
}
public Flux<Timestamped<OnRequest<Object>>> requests(long userId) {
public Flux<Timestamped<OnRequest<Object>>> requests() {
return requests
.filter(group -> group.data().userId() == userId)
//.onBackpressureBuffer(8192, BufferOverflowStrategy.DROP_OLDEST)
.log("requests-" + userId, Level.FINEST, SignalType.REQUEST, SignalType.ON_NEXT);
.log("requests", Level.FINEST, SignalType.REQUEST, SignalType.ON_NEXT);
}
public Disposable events(Flux<ClientBoundEvent> eventFlux) {

View File

@ -7,9 +7,9 @@ public class LiveAtomixReactiveApiClient extends BaseAtomixReactiveApiClient {
private final Flux<ClientBoundEvent> clientBoundEvents;
LiveAtomixReactiveApiClient(KafkaSharedTdlibClients kafkaSharedTdlibClients, long userId) {
super(kafkaSharedTdlibClients, userId);
this.clientBoundEvents = kafkaSharedTdlibClients.events(userId).map(Timestamped::data);
LiveAtomixReactiveApiClient(KafkaSharedTdlibClients kafkaSharedTdlibClients) {
super(kafkaSharedTdlibClients);
this.clientBoundEvents = kafkaSharedTdlibClients.events().map(Timestamped::data);
}
@Override

View File

@ -11,7 +11,7 @@ public interface ReactiveApi {
Mono<CreateSessionResponse> createSession(CreateSessionRequest req);
ReactiveApiClient client(long userId);
ReactiveApiMultiClient client();
Mono<Void> close();

View File

@ -6,13 +6,9 @@ import java.time.Instant;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public interface ReactiveApiClient {
public interface ReactiveApiClient extends ReactiveApiThinClient {
Flux<ClientBoundEvent> clientBoundEvents();
<T extends TdApi.Object> Mono<T> request(TdApi.Function<T> request, Instant timeout);
long getUserId();
boolean isPullMode();
}

View File

@ -1,6 +1,8 @@
package it.tdlight.reactiveapi;
import it.tdlight.jni.TdApi;
import it.tdlight.jni.TdApi.Function;
import it.tdlight.jni.TdApi.Object;
import it.tdlight.reactiveapi.Event.ClientBoundEvent;
import java.time.Instant;
import reactor.core.publisher.Flux;
@ -8,9 +10,23 @@ import reactor.core.publisher.Mono;
public interface ReactiveApiMultiClient {
Flux<ClientBoundEvent> clientBoundEvents(boolean ack);
Flux<ClientBoundEvent> clientBoundEvents();
<T extends TdApi.Object> Mono<T> request(long userId, long liveId, TdApi.Function<T> request, Instant timeout);
<T extends TdApi.Object> Mono<T> request(long userId, TdApi.Function<T> request, Instant timeout);
void close();
Mono<Void> close();
default ReactiveApiThinClient view(long userId) {
return new ReactiveApiThinClient() {
@Override
public <T extends Object> Mono<T> request(Function<T> request, Instant timeout) {
return ReactiveApiMultiClient.this.request(userId, request, timeout);
}
@Override
public long getUserId() {
return userId;
}
};
}
}

View File

@ -49,26 +49,27 @@ import java.util.List;
import java.util.Set;
import java.util.StringJoiner;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import org.apache.kafka.common.errors.SerializationException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.Disposable;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.publisher.Sinks;
import reactor.core.publisher.Sinks.EmitFailureHandler;
import reactor.core.publisher.Sinks.Many;
import reactor.core.scheduler.Schedulers;
import reactor.util.concurrent.Queues;
public abstract class ReactiveApiPublisher {
private static final Logger LOG = LoggerFactory.getLogger(ReactiveApiPublisher.class);
private static final Duration SPECIAL_RAW_TIMEOUT_DURATION = Duration.ofMinutes(5);
private static final Duration TEN_MS = Duration.ofMillis(10);
private static final Duration HUNDRED_MS = Duration.ofMillis(100);
private final KafkaSharedTdlibServers kafkaSharedTdlibServers;
private final Set<ResultingEventTransformer> resultingEventTransformerSet;
private final ReactiveTelegramClient rawTelegramClient;
@ -96,7 +97,6 @@ public abstract class ReactiveApiPublisher {
throw new RuntimeException("Can't load TDLight", e);
}
this.telegramClient = Flux.<Signal>create(sink -> {
var subscription = this.registerTopics();
try {
rawTelegramClient.createAndRegisterClient();
} catch (Throwable ex) {
@ -106,7 +106,6 @@ public abstract class ReactiveApiPublisher {
rawTelegramClient.setListener(sink::next);
sink.onCancel(rawTelegramClient::cancel);
sink.onDispose(() -> {
subscription.dispose();
rawTelegramClient.dispose();
});
});
@ -455,21 +454,6 @@ public abstract class ReactiveApiPublisher {
}
}
@SuppressWarnings("unchecked")
private Disposable registerTopics() {
var subscription1 = kafkaSharedTdlibServers.requests(userId)
.flatMapSequential(req -> this
.handleRequest(req.data())
.doOnNext(response -> this.responses.emitNext(response, EmitFailureHandler.busyLooping(TEN_MS)))
.then()
)
.subscribeOn(Schedulers.parallel())
.subscribe();
return () -> {
subscription1.dispose();
};
}
private static byte[] serializeResponse(Response response) {
if (response == null) return null;
var id = response.getId();
@ -486,13 +470,19 @@ public abstract class ReactiveApiPublisher {
}
}
private Mono<Event.OnResponse.Response<TdApi.Object>> handleRequest(OnRequest<TdApi.Object> onRequestObj) {
public void handleRequest(OnRequest<TdApi.Object> onRequestObj) {
handleRequestInternal(onRequestObj,
response -> this.responses.emitNext(response, EmitFailureHandler.busyLooping(HUNDRED_MS)));
}
private void handleRequestInternal(OnRequest<TdApi.Object> onRequestObj, Consumer<Event.OnResponse.Response<TdApi.Object>> r) {
if (onRequestObj instanceof OnRequest.InvalidRequest invalidRequest) {
return Mono.just(new Event.OnResponse.Response<>(invalidRequest.clientId(),
r.accept(new Event.OnResponse.Response<>(invalidRequest.clientId(),
invalidRequest.requestId(),
userId,
new TdApi.Error(400, "Conflicting protocol version")
));
return;
}
var requestObj = (Request<Object>) onRequestObj;
var requestWithTimeoutInstant = new RequestWithTimeoutInstant<>(requestObj.request(), requestObj.timeout());
@ -504,15 +494,36 @@ public abstract class ReactiveApiPublisher {
LOG.warn("Received an expired request. Expiration: {}", requestWithTimeoutInstant.timeout());
}
return Mono
.from(rawTelegramClient.send(request, timeoutDuration))
.map(responseObj -> new Event.OnResponse.Response<>(onRequestObj.clientId(),
rawTelegramClient.send(request, timeoutDuration).subscribe(new Subscriber<Object>() {
@Override
public void onSubscribe(Subscription subscription) {
subscription.request(1);
}
@Override
public void onNext(Object responseObj) {
r.accept(new Event.OnResponse.Response<>(onRequestObj.clientId(),
onRequestObj.requestId(),
userId, responseObj))
.publishOn(Schedulers.parallel());
userId, responseObj));
}
@Override
public void onError(Throwable throwable) {
LOG.error("Unexpected error while processing response for update {}, user {}, client {}",
onRequestObj.requestId(),
onRequestObj.userId(),
onRequestObj.clientId()
);
}
@Override
public void onComplete() {
}
});
} else {
LOG.error("Ignored a request to {} because the current state is {}. Request: {}", userId, state, requestObj);
return Mono.just(new Event.OnResponse.Response<>(onRequestObj.clientId(),
r.accept(new Event.OnResponse.Response<>(onRequestObj.clientId(),
onRequestObj.requestId(),
userId, new TdApi.Error(503, "Service Unavailable: " + state)));
}

View File

@ -0,0 +1,14 @@
package it.tdlight.reactiveapi;
import it.tdlight.jni.TdApi;
import it.tdlight.reactiveapi.Event.ClientBoundEvent;
import java.time.Instant;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public interface ReactiveApiThinClient {
<T extends TdApi.Object> Mono<T> request(TdApi.Function<T> request, Instant timeout);
long getUserId();
}