Fix performance problems
This commit is contained in:
parent
5e40530a20
commit
8651ce3e97
|
@ -20,18 +20,21 @@ import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import reactor.core.Disposable;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
import reactor.core.publisher.Sinks.EmitFailureHandler;
|
||||||
import reactor.core.scheduler.Schedulers;
|
import reactor.core.scheduler.Schedulers;
|
||||||
|
|
||||||
public class AtomixReactiveApi implements ReactiveApi {
|
public class AtomixReactiveApi implements ReactiveApi {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(AtomixReactiveApi.class);
|
private static final Logger LOG = LoggerFactory.getLogger(AtomixReactiveApi.class);
|
||||||
|
|
||||||
private final boolean clientOnly;
|
private final AtomixReactiveApiMode mode;
|
||||||
|
|
||||||
private final KafkaSharedTdlibClients kafkaSharedTdlibClients;
|
private final KafkaSharedTdlibClients kafkaSharedTdlibClients;
|
||||||
@Nullable
|
@Nullable
|
||||||
private final KafkaSharedTdlibServers kafkaSharedTdlibServers;
|
private final KafkaSharedTdlibServers kafkaSharedTdlibServers;
|
||||||
|
private final ReactiveApiMultiClient client;
|
||||||
|
|
||||||
private final Set<ResultingEventTransformer> resultingEventTransformerSet;
|
private final Set<ResultingEventTransformer> resultingEventTransformerSet;
|
||||||
/**
|
/**
|
||||||
|
@ -44,12 +47,19 @@ public class AtomixReactiveApi implements ReactiveApi {
|
||||||
@Nullable
|
@Nullable
|
||||||
private final DiskSessionsManager diskSessions;
|
private final DiskSessionsManager diskSessions;
|
||||||
private volatile boolean closeRequested;
|
private volatile boolean closeRequested;
|
||||||
|
private volatile Disposable requestsSub;
|
||||||
|
|
||||||
public AtomixReactiveApi(boolean clientOnly,
|
public enum AtomixReactiveApiMode {
|
||||||
|
CLIENT,
|
||||||
|
SERVER,
|
||||||
|
FULL
|
||||||
|
}
|
||||||
|
|
||||||
|
public AtomixReactiveApi(AtomixReactiveApiMode mode,
|
||||||
KafkaParameters kafkaParameters,
|
KafkaParameters kafkaParameters,
|
||||||
@Nullable DiskSessionsManager diskSessions,
|
@Nullable DiskSessionsManager diskSessions,
|
||||||
@NotNull Set<ResultingEventTransformer> resultingEventTransformerSet) {
|
@NotNull Set<ResultingEventTransformer> resultingEventTransformerSet) {
|
||||||
this.clientOnly = clientOnly;
|
this.mode = mode;
|
||||||
var kafkaTDLibRequestProducer = new KafkaTdlibRequestProducer(kafkaParameters);
|
var kafkaTDLibRequestProducer = new KafkaTdlibRequestProducer(kafkaParameters);
|
||||||
var kafkaTDLibResponseConsumer = new KafkaTdlibResponseConsumer(kafkaParameters);
|
var kafkaTDLibResponseConsumer = new KafkaTdlibResponseConsumer(kafkaParameters);
|
||||||
var kafkaClientBoundConsumer = new KafkaClientBoundConsumer(kafkaParameters);
|
var kafkaClientBoundConsumer = new KafkaClientBoundConsumer(kafkaParameters);
|
||||||
|
@ -57,10 +67,14 @@ public class AtomixReactiveApi implements ReactiveApi {
|
||||||
kafkaTDLibResponseConsumer,
|
kafkaTDLibResponseConsumer,
|
||||||
kafkaClientBoundConsumer
|
kafkaClientBoundConsumer
|
||||||
);
|
);
|
||||||
this.kafkaSharedTdlibClients = new KafkaSharedTdlibClients(kafkaTdlibClientsChannels);
|
if (mode != AtomixReactiveApiMode.SERVER) {
|
||||||
if (clientOnly) {
|
this.kafkaSharedTdlibClients = new KafkaSharedTdlibClients(kafkaTdlibClientsChannels);
|
||||||
this.kafkaSharedTdlibServers = null;
|
this.client = new LiveAtomixReactiveApiClient(kafkaSharedTdlibClients);
|
||||||
} else {
|
} else {
|
||||||
|
this.kafkaSharedTdlibClients = null;
|
||||||
|
this.client = null;
|
||||||
|
}
|
||||||
|
if (mode != AtomixReactiveApiMode.CLIENT) {
|
||||||
var kafkaTDLibRequestConsumer = new KafkaTdlibRequestConsumer(kafkaParameters);
|
var kafkaTDLibRequestConsumer = new KafkaTdlibRequestConsumer(kafkaParameters);
|
||||||
var kafkaTDLibResponseProducer = new KafkaTdlibResponseProducer(kafkaParameters);
|
var kafkaTDLibResponseProducer = new KafkaTdlibResponseProducer(kafkaParameters);
|
||||||
var kafkaClientBoundProducer = new KafkaClientBoundProducer(kafkaParameters);
|
var kafkaClientBoundProducer = new KafkaClientBoundProducer(kafkaParameters);
|
||||||
|
@ -69,6 +83,8 @@ public class AtomixReactiveApi implements ReactiveApi {
|
||||||
kafkaClientBoundProducer
|
kafkaClientBoundProducer
|
||||||
);
|
);
|
||||||
this.kafkaSharedTdlibServers = new KafkaSharedTdlibServers(kafkaTDLibServer);
|
this.kafkaSharedTdlibServers = new KafkaSharedTdlibServers(kafkaTDLibServer);
|
||||||
|
} else {
|
||||||
|
this.kafkaSharedTdlibServers = null;
|
||||||
}
|
}
|
||||||
this.resultingEventTransformerSet = resultingEventTransformerSet;
|
this.resultingEventTransformerSet = resultingEventTransformerSet;
|
||||||
|
|
||||||
|
@ -90,7 +106,7 @@ public class AtomixReactiveApi implements ReactiveApi {
|
||||||
.flatMapIterable(a -> a)
|
.flatMapIterable(a -> a)
|
||||||
.map(a -> new DiskSessionAndId(a.getValue(), a.getKey()));
|
.map(a -> new DiskSessionAndId(a.getValue(), a.getKey()));
|
||||||
|
|
||||||
return idsSavedIntoLocalConfiguration
|
var loadSessions = idsSavedIntoLocalConfiguration
|
||||||
.filter(diskSessionAndId -> {
|
.filter(diskSessionAndId -> {
|
||||||
try {
|
try {
|
||||||
diskSessionAndId.diskSession().validate();
|
diskSessionAndId.diskSession().validate();
|
||||||
|
@ -111,13 +127,22 @@ public class AtomixReactiveApi implements ReactiveApi {
|
||||||
})
|
})
|
||||||
.then()
|
.then()
|
||||||
.doOnTerminate(() -> LOG.info("Loaded all saved sessions from disk"));
|
.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
|
@Override
|
||||||
public Mono<CreateSessionResponse> createSession(CreateSessionRequest req) {
|
public Mono<CreateSessionResponse> createSession(CreateSessionRequest req) {
|
||||||
LOG.debug("Received create session request: {}", 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"));
|
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
|
@Override
|
||||||
public ReactiveApiClient client(long userId) {
|
public ReactiveApiMultiClient client() {
|
||||||
return new LiveAtomixReactiveApiClient(kafkaSharedTdlibClients, userId);
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -238,9 +263,17 @@ public class AtomixReactiveApi implements ReactiveApi {
|
||||||
} else {
|
} else {
|
||||||
kafkaServerProducersStopper = Mono.empty();
|
kafkaServerProducersStopper = Mono.empty();
|
||||||
}
|
}
|
||||||
Mono<?> kafkaClientProducersStopper = Mono
|
Mono<?> kafkaClientProducersStopper;
|
||||||
.fromRunnable(kafkaSharedTdlibClients::close)
|
if (kafkaSharedTdlibClients != null) {
|
||||||
.subscribeOn(Schedulers.boundedElastic());
|
kafkaClientProducersStopper = Mono
|
||||||
|
.fromRunnable(kafkaSharedTdlibClients::close)
|
||||||
|
.subscribeOn(Schedulers.boundedElastic());
|
||||||
|
} else {
|
||||||
|
kafkaClientProducersStopper = Mono.empty();
|
||||||
|
}
|
||||||
|
if (requestsSub != null) {
|
||||||
|
requestsSub.dispose();
|
||||||
|
}
|
||||||
return Mono.when(kafkaServerProducersStopper, kafkaClientProducersStopper);
|
return Mono.when(kafkaServerProducersStopper, kafkaClientProducersStopper);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,8 @@ import static it.tdlight.reactiveapi.Event.SERIAL_VERSION;
|
||||||
|
|
||||||
import it.tdlight.jni.TdApi;
|
import it.tdlight.jni.TdApi;
|
||||||
import it.tdlight.jni.TdApi.Error;
|
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.ClientBoundEvent;
|
||||||
import it.tdlight.reactiveapi.Event.Ignored;
|
import it.tdlight.reactiveapi.Event.Ignored;
|
||||||
import it.tdlight.reactiveapi.Event.OnBotLoginCodeRequested;
|
import it.tdlight.reactiveapi.Event.OnBotLoginCodeRequested;
|
||||||
|
@ -31,19 +33,17 @@ import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import reactor.core.Disposable;
|
import reactor.core.Disposable;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import reactor.core.publisher.Sinks;
|
|
||||||
import reactor.core.publisher.Sinks.EmitFailureHandler;
|
import reactor.core.publisher.Sinks.EmitFailureHandler;
|
||||||
import reactor.core.publisher.Sinks.Many;
|
import reactor.core.publisher.Sinks.Many;
|
||||||
import reactor.core.scheduler.Schedulers;
|
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 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
|
// Temporary id used to make requests
|
||||||
private final long clientId;
|
private final long clientId;
|
||||||
private final Many<OnRequest<?>> requests;
|
private final Many<OnRequest<?>> requests;
|
||||||
|
@ -51,33 +51,23 @@ abstract class BaseAtomixReactiveApiClient implements ReactiveApiClient, AutoClo
|
||||||
= new ConcurrentHashMap<>();
|
= new ConcurrentHashMap<>();
|
||||||
private final AtomicLong requestId = new AtomicLong(0);
|
private final AtomicLong requestId = new AtomicLong(0);
|
||||||
private final Disposable subscription;
|
private final Disposable subscription;
|
||||||
private final boolean pullMode;
|
|
||||||
|
|
||||||
public BaseAtomixReactiveApiClient(KafkaSharedTdlibClients kafkaSharedTdlibClients, long userId) {
|
public BaseAtomixReactiveApiClient(KafkaSharedTdlibClients kafkaSharedTdlibClients) {
|
||||||
this.userId = userId;
|
|
||||||
this.clientId = System.nanoTime();
|
this.clientId = System.nanoTime();
|
||||||
this.requests = kafkaSharedTdlibClients.requests();
|
this.requests = kafkaSharedTdlibClients.requests();
|
||||||
this.pullMode = kafkaSharedTdlibClients.canRequestsWait();
|
|
||||||
|
|
||||||
var disposable2 = kafkaSharedTdlibClients.responses(clientId)
|
this.subscription = kafkaSharedTdlibClients.responses().doOnNext(response -> {
|
||||||
.doOnNext(response -> {
|
var responseSink = responses.get(response.data().requestId());
|
||||||
var responseSink = responses.get(response.data().requestId());
|
if (responseSink == null) {
|
||||||
if (responseSink == null) {
|
LOG.debug("Bot received a response for an unknown request id: {}", response.data().requestId());
|
||||||
LOG.debug("Bot #IDU{} received a response for an unknown request id: {}",
|
return;
|
||||||
userId, response.data().requestId());
|
}
|
||||||
return;
|
responseSink.complete(response);
|
||||||
}
|
}).subscribeOn(Schedulers.parallel()).subscribe();
|
||||||
responseSink.complete(response);
|
|
||||||
})
|
|
||||||
.subscribeOn(Schedulers.parallel())
|
|
||||||
.subscribe();
|
|
||||||
this.subscription = () -> {
|
|
||||||
disposable2.dispose();
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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(() -> {
|
return Mono.defer(() -> {
|
||||||
var requestId = this.requestId.getAndIncrement();
|
var requestId = this.requestId.getAndIncrement();
|
||||||
var timeoutError = new TdError(408, "Request Timeout");
|
var timeoutError = new TdError(408, "Request Timeout");
|
||||||
|
@ -110,22 +100,12 @@ abstract class BaseAtomixReactiveApiClient implements ReactiveApiClient, AutoClo
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.doFinally(s -> this.responses.remove(requestId));
|
.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;
|
return response;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public final long getUserId() {
|
|
||||||
return userId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final boolean isPullMode() {
|
|
||||||
return pullMode;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static ClientBoundEvent deserializeEvent(byte[] bytes) {
|
static ClientBoundEvent deserializeEvent(byte[] bytes) {
|
||||||
try (var byteArrayInputStream = new ByteArrayInputStream(bytes)) {
|
try (var byteArrayInputStream = new ByteArrayInputStream(bytes)) {
|
||||||
try (var is = new DataInputStream(byteArrayInputStream)) {
|
try (var is = new DataInputStream(byteArrayInputStream)) {
|
||||||
|
@ -154,12 +134,14 @@ abstract class BaseAtomixReactiveApiClient implements ReactiveApiClient, AutoClo
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public Mono<Void> close() {
|
||||||
subscription.dispose();
|
return Mono.fromRunnable(() -> {
|
||||||
long now = System.currentTimeMillis();
|
subscription.dispose();
|
||||||
responses.forEach((requestId, cf) -> cf.complete(new Timestamped<>(now,
|
long now = System.currentTimeMillis();
|
||||||
new Response<>(clientId, requestId, userId, new Error(408, "Request Timeout"))
|
responses.forEach((requestId, cf) -> cf.complete(new Timestamped<>(now,
|
||||||
)));
|
new Response<>(clientId, requestId, EMPTY_USER_ID, new Error(408, "Request Timeout"))
|
||||||
responses.clear();
|
)));
|
||||||
|
responses.clear();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import static java.util.Collections.unmodifiableSet;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
|
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
|
||||||
|
import it.tdlight.reactiveapi.AtomixReactiveApi.AtomixReactiveApiMode;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
@ -66,7 +67,7 @@ public class Entrypoint {
|
||||||
@Nullable DiskSessionsManager diskSessions) {
|
@Nullable DiskSessionsManager diskSessions) {
|
||||||
|
|
||||||
Set<ResultingEventTransformer> resultingEventTransformerSet;
|
Set<ResultingEventTransformer> resultingEventTransformerSet;
|
||||||
boolean clientOnly = false;
|
AtomixReactiveApiMode mode = AtomixReactiveApiMode.SERVER;
|
||||||
if (instanceSettings.client) {
|
if (instanceSettings.client) {
|
||||||
if (diskSessions != null) {
|
if (diskSessions != null) {
|
||||||
throw new IllegalArgumentException("A client instance can't have a session manager!");
|
throw new IllegalArgumentException("A client instance can't have a session manager!");
|
||||||
|
@ -74,7 +75,7 @@ public class Entrypoint {
|
||||||
if (instanceSettings.clientAddress == null) {
|
if (instanceSettings.clientAddress == null) {
|
||||||
throw new IllegalArgumentException("A client instance must have an address (host:port)");
|
throw new IllegalArgumentException("A client instance must have an address (host:port)");
|
||||||
}
|
}
|
||||||
clientOnly = true;
|
mode = AtomixReactiveApiMode.CLIENT;
|
||||||
resultingEventTransformerSet = Set.of();
|
resultingEventTransformerSet = Set.of();
|
||||||
} else {
|
} else {
|
||||||
if (diskSessions == null) {
|
if (diskSessions == null) {
|
||||||
|
@ -103,7 +104,7 @@ public class Entrypoint {
|
||||||
|
|
||||||
var kafkaParameters = new KafkaParameters(clusterSettings, instanceSettings.id);
|
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...");
|
LOG.info("Starting ReactiveApi...");
|
||||||
|
|
||||||
|
|
|
@ -13,9 +13,7 @@ public class InstanceSettings {
|
||||||
public String id;
|
public String id;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True if this is just a client, false if this is a complete node
|
* True if this is just a client, false if this is a server
|
||||||
* <p>
|
|
||||||
* A client is a lightweight node
|
|
||||||
*/
|
*/
|
||||||
public boolean client;
|
public boolean client;
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ import it.tdlight.common.utils.CantLoadLibrary;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import org.apache.kafka.clients.consumer.CommitFailedException;
|
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.VALUE_DESERIALIZER_CLASS_CONFIG, getChannelName().getDeserializerClass());
|
||||||
props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "latest");
|
props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "latest");
|
||||||
props.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, toIntExact(Duration.ofMinutes(5).toMillis()));
|
props.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, toIntExact(Duration.ofMinutes(5).toMillis()));
|
||||||
if (isQuickResponse()) {
|
if (!isQuickResponse()) {
|
||||||
props.put(ConsumerConfig.HEARTBEAT_INTERVAL_MS_CONFIG, "5000");
|
|
||||||
props.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, "10000");
|
|
||||||
} else {
|
|
||||||
props.put(ConsumerConfig.HEARTBEAT_INTERVAL_MS_CONFIG, "10000");
|
props.put(ConsumerConfig.HEARTBEAT_INTERVAL_MS_CONFIG, "10000");
|
||||||
props.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, "30000");
|
props.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, "30000");
|
||||||
props.put(ConsumerConfig.FETCH_MIN_BYTES_CONFIG, "1048576");
|
props.put(ConsumerConfig.FETCH_MIN_BYTES_CONFIG, "1048576");
|
||||||
props.put(ConsumerConfig.FETCH_MAX_WAIT_MS_CONFIG, "100");
|
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;
|
Pattern pattern;
|
||||||
if (userId == null) {
|
if (userId == null) {
|
||||||
pattern = Pattern.compile("tdlib\\." + getChannelName() + "\\.\\d+");
|
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) {
|
private Flux<Timestamped<K>> consumeMessagesInternal(@NotNull String subGroupId, @Nullable Long userId) {
|
||||||
return createReceiver(kafkaParameters.groupId() + "-" + subGroupId, userId)
|
return createReceiver(kafkaParameters.groupId() + "-" + subGroupId, userId)
|
||||||
.receive()
|
.receiveAutoAck(isQuickResponse() ? null : 32)
|
||||||
|
.concatMap(Flux::collectList)
|
||||||
|
.flatMapIterable(Function.identity())
|
||||||
.log("consume-messages" + (userId != null ? "-" + userId : ""),
|
.log("consume-messages" + (userId != null ? "-" + userId : ""),
|
||||||
Level.FINEST,
|
Level.FINEST,
|
||||||
SignalType.REQUEST,
|
SignalType.REQUEST,
|
||||||
|
@ -117,7 +120,6 @@ public abstract class KafkaConsumer<K> {
|
||||||
SignalType.ON_ERROR,
|
SignalType.ON_ERROR,
|
||||||
SignalType.ON_COMPLETE
|
SignalType.ON_COMPLETE
|
||||||
)
|
)
|
||||||
//.doOnNext(result -> result.receiverOffset().acknowledge())
|
|
||||||
.map(record -> {
|
.map(record -> {
|
||||||
if (record.timestampType() == TimestampType.CREATE_TIME) {
|
if (record.timestampType() == TimestampType.CREATE_TIME) {
|
||||||
return new Timestamped<>(record.timestamp(), record.value());
|
return new Timestamped<>(record.timestamp(), record.value());
|
||||||
|
|
|
@ -26,9 +26,9 @@ public abstract class KafkaProducer<K> {
|
||||||
Map<String, Object> props = new HashMap<>();
|
Map<String, Object> props = new HashMap<>();
|
||||||
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaParameters.bootstrapServers());
|
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, kafkaParameters.bootstrapServers());
|
||||||
props.put(ProducerConfig.CLIENT_ID_CONFIG, kafkaParameters.clientId());
|
props.put(ProducerConfig.CLIENT_ID_CONFIG, kafkaParameters.clientId());
|
||||||
//props.put(ProducerConfig.ACKS_CONFIG, "1");
|
props.put(ProducerConfig.ACKS_CONFIG, "1");
|
||||||
props.put(ProducerConfig.ACKS_CONFIG, "0");
|
props.put(ProducerConfig.BATCH_SIZE_CONFIG, Integer.toString(32 * 1024));
|
||||||
props.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);
|
props.put(ProducerConfig.LINGER_MS_CONFIG, "20");
|
||||||
props.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "snappy");
|
props.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "snappy");
|
||||||
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, IntegerSerializer.class);
|
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, IntegerSerializer.class);
|
||||||
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, getChannelName().getSerializerClass());
|
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, getChannelName().getSerializerClass());
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package it.tdlight.reactiveapi;
|
package it.tdlight.reactiveapi;
|
||||||
|
|
||||||
import static java.util.Objects.requireNonNullElse;
|
import static java.util.Objects.requireNonNullElse;
|
||||||
import static reactor.core.publisher.Sinks.EmitFailureHandler.busyLooping;
|
|
||||||
|
|
||||||
import it.tdlight.jni.TdApi.Object;
|
import it.tdlight.jni.TdApi.Object;
|
||||||
import it.tdlight.reactiveapi.Event.ClientBoundEvent;
|
import it.tdlight.reactiveapi.Event.ClientBoundEvent;
|
||||||
|
@ -39,31 +38,20 @@ public class KafkaSharedTdlibClients implements Closeable {
|
||||||
|
|
||||||
public KafkaSharedTdlibClients(KafkaTdlibClientsChannels kafkaTdlibClientsChannels) {
|
public KafkaSharedTdlibClients(KafkaTdlibClientsChannels kafkaTdlibClientsChannels) {
|
||||||
this.kafkaTdlibClientsChannels = kafkaTdlibClientsChannels;
|
this.kafkaTdlibClientsChannels = kafkaTdlibClientsChannels;
|
||||||
this.responses = kafkaTdlibClientsChannels.response().consumeMessages("td-responses")
|
this.responses = kafkaTdlibClientsChannels.response().consumeMessages("td-responses");
|
||||||
.publish(65535)
|
this.events = kafkaTdlibClientsChannels.events().consumeMessages("td-handler");
|
||||||
.autoConnect(1, this.responsesSub::set);
|
|
||||||
this.events = kafkaTdlibClientsChannels.events().consumeMessages("td-handler")
|
|
||||||
.publish(65535)
|
|
||||||
.autoConnect(1, this.eventsSub::set);
|
|
||||||
this.requestsSub = kafkaTdlibClientsChannels.request()
|
this.requestsSub = kafkaTdlibClientsChannels.request()
|
||||||
.sendMessages(0L, requests.asFlux())
|
.sendMessages(0L, requests.asFlux())
|
||||||
.subscribeOn(Schedulers.parallel())
|
.subscribeOn(Schedulers.parallel())
|
||||||
.subscribe();
|
.subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Flux<Timestamped<OnResponse<Object>>> responses(long clientId) {
|
public Flux<Timestamped<OnResponse<Object>>> responses() {
|
||||||
return responses
|
return responses;
|
||||||
.filter(group -> group.data().clientId() == clientId)
|
|
||||||
//.onBackpressureBuffer(8192, BufferOverflowStrategy.DROP_OLDEST)
|
|
||||||
.log("req-" + clientId, Level.FINEST, SignalType.REQUEST);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Flux<Timestamped<ClientBoundEvent>> events(long userId) {
|
public Flux<Timestamped<ClientBoundEvent>> events() {
|
||||||
return events
|
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 Many<OnRequest<?>> requests() {
|
public Many<OnRequest<?>> requests() {
|
||||||
|
@ -83,8 +71,4 @@ public class KafkaSharedTdlibClients implements Closeable {
|
||||||
}
|
}
|
||||||
kafkaTdlibClientsChannels.close();
|
kafkaTdlibClientsChannels.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean canRequestsWait() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package it.tdlight.reactiveapi;
|
package it.tdlight.reactiveapi;
|
||||||
|
|
||||||
import static java.util.Objects.requireNonNullElse;
|
import static java.util.Objects.requireNonNullElse;
|
||||||
import static reactor.core.publisher.Sinks.EmitFailureHandler.busyLooping;
|
|
||||||
|
|
||||||
import it.tdlight.jni.TdApi;
|
import it.tdlight.jni.TdApi;
|
||||||
import it.tdlight.jni.TdApi.Object;
|
import it.tdlight.jni.TdApi.Object;
|
||||||
|
@ -39,16 +38,13 @@ public class KafkaSharedTdlibServers implements Closeable {
|
||||||
.subscribeOn(Schedulers.parallel())
|
.subscribeOn(Schedulers.parallel())
|
||||||
.subscribe();
|
.subscribe();
|
||||||
this.requests = kafkaTdlibServersChannels.request()
|
this.requests = kafkaTdlibServersChannels.request()
|
||||||
.consumeMessages("td-requests")
|
.consumeMessages("td-requests");
|
||||||
.publish(65535)
|
|
||||||
.autoConnect(1, this.requestsSub::set);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Flux<Timestamped<OnRequest<Object>>> requests(long userId) {
|
public Flux<Timestamped<OnRequest<Object>>> requests() {
|
||||||
return requests
|
return requests
|
||||||
.filter(group -> group.data().userId() == userId)
|
|
||||||
//.onBackpressureBuffer(8192, BufferOverflowStrategy.DROP_OLDEST)
|
//.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) {
|
public Disposable events(Flux<ClientBoundEvent> eventFlux) {
|
||||||
|
|
|
@ -7,9 +7,9 @@ public class LiveAtomixReactiveApiClient extends BaseAtomixReactiveApiClient {
|
||||||
|
|
||||||
private final Flux<ClientBoundEvent> clientBoundEvents;
|
private final Flux<ClientBoundEvent> clientBoundEvents;
|
||||||
|
|
||||||
LiveAtomixReactiveApiClient(KafkaSharedTdlibClients kafkaSharedTdlibClients, long userId) {
|
LiveAtomixReactiveApiClient(KafkaSharedTdlibClients kafkaSharedTdlibClients) {
|
||||||
super(kafkaSharedTdlibClients, userId);
|
super(kafkaSharedTdlibClients);
|
||||||
this.clientBoundEvents = kafkaSharedTdlibClients.events(userId).map(Timestamped::data);
|
this.clientBoundEvents = kafkaSharedTdlibClients.events().map(Timestamped::data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -11,7 +11,7 @@ public interface ReactiveApi {
|
||||||
|
|
||||||
Mono<CreateSessionResponse> createSession(CreateSessionRequest req);
|
Mono<CreateSessionResponse> createSession(CreateSessionRequest req);
|
||||||
|
|
||||||
ReactiveApiClient client(long userId);
|
ReactiveApiMultiClient client();
|
||||||
|
|
||||||
Mono<Void> close();
|
Mono<Void> close();
|
||||||
|
|
||||||
|
|
|
@ -6,13 +6,9 @@ import java.time.Instant;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
public interface ReactiveApiClient {
|
public interface ReactiveApiClient extends ReactiveApiThinClient {
|
||||||
|
|
||||||
Flux<ClientBoundEvent> clientBoundEvents();
|
Flux<ClientBoundEvent> clientBoundEvents();
|
||||||
|
|
||||||
<T extends TdApi.Object> Mono<T> request(TdApi.Function<T> request, Instant timeout);
|
|
||||||
|
|
||||||
long getUserId();
|
|
||||||
|
|
||||||
boolean isPullMode();
|
boolean isPullMode();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package it.tdlight.reactiveapi;
|
package it.tdlight.reactiveapi;
|
||||||
|
|
||||||
import it.tdlight.jni.TdApi;
|
import it.tdlight.jni.TdApi;
|
||||||
|
import it.tdlight.jni.TdApi.Function;
|
||||||
|
import it.tdlight.jni.TdApi.Object;
|
||||||
import it.tdlight.reactiveapi.Event.ClientBoundEvent;
|
import it.tdlight.reactiveapi.Event.ClientBoundEvent;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
|
@ -8,9 +10,23 @@ import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
public interface ReactiveApiMultiClient {
|
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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,26 +49,27 @@ import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.StringJoiner;
|
import java.util.StringJoiner;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
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.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.reactivestreams.Subscriber;
|
||||||
|
import org.reactivestreams.Subscription;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
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.Mono;
|
||||||
import reactor.core.publisher.Sinks;
|
|
||||||
import reactor.core.publisher.Sinks.EmitFailureHandler;
|
import reactor.core.publisher.Sinks.EmitFailureHandler;
|
||||||
import reactor.core.publisher.Sinks.Many;
|
import reactor.core.publisher.Sinks.Many;
|
||||||
import reactor.core.scheduler.Schedulers;
|
import reactor.core.scheduler.Schedulers;
|
||||||
import reactor.util.concurrent.Queues;
|
|
||||||
|
|
||||||
public abstract class ReactiveApiPublisher {
|
public abstract class ReactiveApiPublisher {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(ReactiveApiPublisher.class);
|
private static final Logger LOG = LoggerFactory.getLogger(ReactiveApiPublisher.class);
|
||||||
private static final Duration SPECIAL_RAW_TIMEOUT_DURATION = Duration.ofMinutes(5);
|
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 KafkaSharedTdlibServers kafkaSharedTdlibServers;
|
||||||
private final Set<ResultingEventTransformer> resultingEventTransformerSet;
|
private final Set<ResultingEventTransformer> resultingEventTransformerSet;
|
||||||
private final ReactiveTelegramClient rawTelegramClient;
|
private final ReactiveTelegramClient rawTelegramClient;
|
||||||
|
@ -96,7 +97,6 @@ public abstract class ReactiveApiPublisher {
|
||||||
throw new RuntimeException("Can't load TDLight", e);
|
throw new RuntimeException("Can't load TDLight", e);
|
||||||
}
|
}
|
||||||
this.telegramClient = Flux.<Signal>create(sink -> {
|
this.telegramClient = Flux.<Signal>create(sink -> {
|
||||||
var subscription = this.registerTopics();
|
|
||||||
try {
|
try {
|
||||||
rawTelegramClient.createAndRegisterClient();
|
rawTelegramClient.createAndRegisterClient();
|
||||||
} catch (Throwable ex) {
|
} catch (Throwable ex) {
|
||||||
|
@ -106,7 +106,6 @@ public abstract class ReactiveApiPublisher {
|
||||||
rawTelegramClient.setListener(sink::next);
|
rawTelegramClient.setListener(sink::next);
|
||||||
sink.onCancel(rawTelegramClient::cancel);
|
sink.onCancel(rawTelegramClient::cancel);
|
||||||
sink.onDispose(() -> {
|
sink.onDispose(() -> {
|
||||||
subscription.dispose();
|
|
||||||
rawTelegramClient.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) {
|
private static byte[] serializeResponse(Response response) {
|
||||||
if (response == null) return null;
|
if (response == null) return null;
|
||||||
var id = response.getId();
|
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) {
|
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(),
|
invalidRequest.requestId(),
|
||||||
userId,
|
userId,
|
||||||
new TdApi.Error(400, "Conflicting protocol version")
|
new TdApi.Error(400, "Conflicting protocol version")
|
||||||
));
|
));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
var requestObj = (Request<Object>) onRequestObj;
|
var requestObj = (Request<Object>) onRequestObj;
|
||||||
var requestWithTimeoutInstant = new RequestWithTimeoutInstant<>(requestObj.request(), requestObj.timeout());
|
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());
|
LOG.warn("Received an expired request. Expiration: {}", requestWithTimeoutInstant.timeout());
|
||||||
}
|
}
|
||||||
|
|
||||||
return Mono
|
rawTelegramClient.send(request, timeoutDuration).subscribe(new Subscriber<Object>() {
|
||||||
.from(rawTelegramClient.send(request, timeoutDuration))
|
@Override
|
||||||
.map(responseObj -> new Event.OnResponse.Response<>(onRequestObj.clientId(),
|
public void onSubscribe(Subscription subscription) {
|
||||||
|
subscription.request(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNext(Object responseObj) {
|
||||||
|
r.accept(new Event.OnResponse.Response<>(onRequestObj.clientId(),
|
||||||
onRequestObj.requestId(),
|
onRequestObj.requestId(),
|
||||||
userId, responseObj))
|
userId, responseObj));
|
||||||
.publishOn(Schedulers.parallel());
|
}
|
||||||
|
|
||||||
|
@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 {
|
} else {
|
||||||
LOG.error("Ignored a request to {} because the current state is {}. Request: {}", userId, state, requestObj);
|
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(),
|
onRequestObj.requestId(),
|
||||||
userId, new TdApi.Error(503, "Service Unavailable: " + state)));
|
userId, new TdApi.Error(503, "Service Unavailable: " + state)));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
Loading…
Reference in New Issue