Use RxJava2
This commit is contained in:
parent
26eb359238
commit
b51fcbbf90
10
pom.xml
10
pom.xml
@ -72,6 +72,16 @@
|
||||
<version>${vertx.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.vertx</groupId>
|
||||
<artifactId>vertx-reactive-streams</artifactId>
|
||||
<version>${vertx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.vertx</groupId>
|
||||
<artifactId>vertx-rx-java2</artifactId>
|
||||
<version>${vertx.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
|
@ -1,11 +1,11 @@
|
||||
package it.tdlight.tdlibsession;
|
||||
|
||||
import io.vertx.core.eventbus.DeliveryOptions;
|
||||
import io.vertx.core.eventbus.EventBus;
|
||||
import io.vertx.core.eventbus.Message;
|
||||
import io.vertx.core.eventbus.MessageCodec;
|
||||
import io.vertx.core.eventbus.MessageConsumer;
|
||||
import io.vertx.core.eventbus.ReplyException;
|
||||
import io.vertx.reactivex.core.eventbus.EventBus;
|
||||
import io.vertx.reactivex.core.eventbus.Message;
|
||||
import io.vertx.core.eventbus.MessageCodec;
|
||||
import io.vertx.reactivex.core.eventbus.MessageConsumer;
|
||||
import it.tdlight.utils.MonoUtils;
|
||||
import java.net.ConnectException;
|
||||
import java.time.Duration;
|
||||
|
@ -3,10 +3,10 @@ package it.tdlight.tdlibsession.remoteclient;
|
||||
import io.vertx.core.AsyncResult;
|
||||
import io.vertx.core.Future;
|
||||
import io.vertx.core.Handler;
|
||||
import io.vertx.core.Promise;
|
||||
import io.vertx.core.eventbus.Message;
|
||||
import io.vertx.reactivex.core.Promise;
|
||||
import io.vertx.reactivex.core.eventbus.Message;
|
||||
import io.vertx.core.net.JksOptions;
|
||||
import io.vertx.core.shareddata.AsyncMap;
|
||||
import io.vertx.reactivex.core.shareddata.AsyncMap;
|
||||
import it.tdlight.common.Init;
|
||||
import it.tdlight.common.utils.CantLoadLibrary;
|
||||
import it.tdlight.tdlibsession.td.middle.TdClusterManager;
|
||||
|
@ -9,18 +9,20 @@ import com.hazelcast.config.MergePolicyConfig;
|
||||
import com.hazelcast.config.cp.SemaphoreConfig;
|
||||
import io.vertx.core.DeploymentOptions;
|
||||
import io.vertx.core.Handler;
|
||||
import io.vertx.core.Vertx;
|
||||
import io.vertx.core.VertxOptions;
|
||||
import io.vertx.core.eventbus.DeliveryOptions;
|
||||
import io.vertx.core.eventbus.EventBus;
|
||||
import io.vertx.core.eventbus.Message;
|
||||
import io.vertx.core.eventbus.MessageCodec;
|
||||
import io.vertx.core.eventbus.MessageConsumer;
|
||||
import io.vertx.core.http.ClientAuth;
|
||||
import io.vertx.core.net.JksOptions;
|
||||
import io.vertx.core.shareddata.SharedData;
|
||||
import io.vertx.core.spi.cluster.ClusterManager;
|
||||
import io.vertx.reactivex.core.Vertx;
|
||||
import io.vertx.reactivex.core.eventbus.EventBus;
|
||||
import io.vertx.reactivex.core.eventbus.Message;
|
||||
import io.vertx.reactivex.core.eventbus.MessageConsumer;
|
||||
import io.vertx.reactivex.core.shareddata.SharedData;
|
||||
import io.vertx.spi.cluster.hazelcast.HazelcastClusterManager;
|
||||
import it.tdlight.common.ConstructorDetector;
|
||||
import it.tdlight.tdlibsession.td.TdResultMessage;
|
||||
import it.tdlight.utils.MonoUtils;
|
||||
import java.nio.channels.AlreadyBoundException;
|
||||
import java.util.ArrayList;
|
||||
@ -45,6 +47,18 @@ public class TdClusterManager {
|
||||
this.mgr = mgr;
|
||||
this.vertxOptions = vertxOptions;
|
||||
this.vertx = vertx;
|
||||
|
||||
if (vertx != null && vertx.eventBus() != null) {
|
||||
vertx
|
||||
.eventBus()
|
||||
.getDelegate()
|
||||
.registerDefaultCodec(TdResultList.class, new TdResultListMessageCodec())
|
||||
.registerDefaultCodec(ExecuteObject.class, new TdExecuteObjectMessageCodec())
|
||||
.registerDefaultCodec(TdResultMessage.class, new TdResultMessageCodec());
|
||||
for (Class<?> value : ConstructorDetector.getTDConstructorsUnsafe().values()) {
|
||||
vertx.eventBus().getDelegate().registerDefaultCodec(value, new TdMessageCodec(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Mono<TdClusterManager> ofMaster(JksOptions keyStoreOptions, JksOptions trustStoreOptions, boolean onlyLocal, String masterHostname, String netInterface, int port, Set<String> nodesAddresses) {
|
||||
@ -169,14 +183,13 @@ public class TdClusterManager {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param objectClass
|
||||
* @param messageCodec
|
||||
* @param <T>
|
||||
* @return true if registered, false if already registered
|
||||
*/
|
||||
public <T> boolean registerDefaultCodec(Class<T> objectClass, MessageCodec<T, T> messageCodec) {
|
||||
public <T> boolean registerCodec(MessageCodec<T, T> messageCodec) {
|
||||
try {
|
||||
vertx.eventBus().registerDefaultCodec(objectClass, messageCodec);
|
||||
vertx.eventBus().registerCodec(messageCodec);
|
||||
return true;
|
||||
} catch (IllegalStateException ex) {
|
||||
if (ex.getMessage().startsWith("Already a default codec registered for class")) {
|
||||
|
@ -1,14 +1,12 @@
|
||||
package it.tdlight.tdlibsession.td.middle.client;
|
||||
|
||||
import io.vertx.circuitbreaker.CircuitBreaker;
|
||||
import io.reactivex.Completable;
|
||||
import io.vertx.circuitbreaker.CircuitBreakerOptions;
|
||||
import io.vertx.core.AbstractVerticle;
|
||||
import io.vertx.core.AsyncResult;
|
||||
import io.vertx.core.Promise;
|
||||
import io.vertx.core.eventbus.DeliveryOptions;
|
||||
import io.vertx.core.eventbus.Message;
|
||||
import io.vertx.core.json.JsonObject;
|
||||
import it.tdlight.common.ConstructorDetector;
|
||||
import io.vertx.reactivex.circuitbreaker.CircuitBreaker;
|
||||
import io.vertx.reactivex.core.AbstractVerticle;
|
||||
import io.vertx.reactivex.core.eventbus.Message;
|
||||
import it.tdlight.jni.TdApi;
|
||||
import it.tdlight.jni.TdApi.AuthorizationStateClosed;
|
||||
import it.tdlight.jni.TdApi.Error;
|
||||
@ -21,16 +19,12 @@ import it.tdlight.tdlibsession.td.TdResultMessage;
|
||||
import it.tdlight.tdlibsession.td.middle.AsyncTdMiddle;
|
||||
import it.tdlight.tdlibsession.td.middle.ExecuteObject;
|
||||
import it.tdlight.tdlibsession.td.middle.TdClusterManager;
|
||||
import it.tdlight.tdlibsession.td.middle.TdExecuteObjectMessageCodec;
|
||||
import it.tdlight.tdlibsession.td.middle.TdMessageCodec;
|
||||
import it.tdlight.tdlibsession.td.middle.TdResultList;
|
||||
import it.tdlight.tdlibsession.td.middle.TdResultListMessageCodec;
|
||||
import it.tdlight.tdlibsession.td.middle.TdResultMessageCodec;
|
||||
import it.tdlight.utils.MonoUtils;
|
||||
import java.net.ConnectException;
|
||||
import java.time.Duration;
|
||||
import java.util.Objects;
|
||||
import java.util.StringJoiner;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.warp.commonutils.error.InitializationException;
|
||||
@ -38,7 +32,7 @@ import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.publisher.SignalType;
|
||||
import reactor.core.publisher.Sinks;
|
||||
import reactor.core.publisher.Sinks.Many;
|
||||
import reactor.core.publisher.Sinks.Empty;
|
||||
import reactor.core.publisher.Sinks.One;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
@ -49,52 +43,56 @@ public class AsyncTdMiddleEventBusClient extends AbstractVerticle implements Asy
|
||||
public static final boolean OUTPUT_REQUESTS = false;
|
||||
public static final byte[] EMPTY = new byte[0];
|
||||
|
||||
private final Many<Boolean> tdCloseRequested = Sinks.many().replay().latestOrDefault(false);
|
||||
private final Many<Boolean> tdClosed = Sinks.many().replay().latestOrDefault(false);
|
||||
private final One<Error> tdCrash = Sinks.one();
|
||||
private final TdClusterManager cluster;
|
||||
private final DeliveryOptions deliveryOptions;
|
||||
private final DeliveryOptions deliveryOptionsWithTimeout;
|
||||
|
||||
private TdClusterManager cluster;
|
||||
private final Empty<Void> tdCloseRequested = Sinks.empty();
|
||||
private final Empty<Void> tdClosed = Sinks.empty();
|
||||
private final One<Error> tdCrashed = Sinks.one();
|
||||
|
||||
private String botAddress;
|
||||
private String botAlias;
|
||||
private boolean local;
|
||||
private long initTime;
|
||||
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public AsyncTdMiddleEventBusClient(TdClusterManager clusterManager) {
|
||||
cluster = clusterManager;
|
||||
if (cluster.registerDefaultCodec(TdResultList.class, new TdResultListMessageCodec())) {
|
||||
cluster.registerDefaultCodec(ExecuteObject.class, new TdExecuteObjectMessageCodec());
|
||||
cluster.registerDefaultCodec(TdResultMessage.class, new TdResultMessageCodec());
|
||||
for (Class<?> value : ConstructorDetector.getTDConstructorsUnsafe().values()) {
|
||||
cluster.registerDefaultCodec(value, new TdMessageCodec(value));
|
||||
}
|
||||
}
|
||||
this.deliveryOptions = cluster.newDeliveryOpts().setLocalOnly(local);
|
||||
this.deliveryOptionsWithTimeout = cluster.newDeliveryOpts().setLocalOnly(local).setSendTimeout(30000);
|
||||
}
|
||||
|
||||
public static Mono<AsyncTdMiddleEventBusClient> getAndDeployInstance(TdClusterManager clusterManager, String botAlias, String botAddress, boolean local) throws InitializationException {
|
||||
try {
|
||||
public static Mono<AsyncTdMiddle> getAndDeployInstance(TdClusterManager clusterManager,
|
||||
String botAlias,
|
||||
String botAddress,
|
||||
boolean local) {
|
||||
var instance = new AsyncTdMiddleEventBusClient(clusterManager);
|
||||
var options = clusterManager.newDeploymentOpts().setConfig(new JsonObject()
|
||||
.put("botAddress", botAddress)
|
||||
.put("botAlias", botAlias)
|
||||
.put("local", local));
|
||||
return MonoUtils.<String>executeAsFuture(promise -> {
|
||||
clusterManager.getVertx().deployVerticle(instance, options, promise);
|
||||
}).doOnNext(_v -> {
|
||||
logger.trace("Deployed verticle for bot address: " + botAddress);
|
||||
}).thenReturn(instance);
|
||||
} catch (RuntimeException e) {
|
||||
throw new InitializationException(e);
|
||||
}
|
||||
var options = clusterManager
|
||||
.newDeploymentOpts()
|
||||
.setConfig(new JsonObject().put("botAddress", botAddress).put("botAlias", botAlias).put("local", local));
|
||||
return clusterManager
|
||||
.getVertx()
|
||||
.rxDeployVerticle(instance, options)
|
||||
.as(MonoUtils::toMono)
|
||||
.doOnSuccess(s -> logger.trace("Deployed verticle for bot address: " + botAddress))
|
||||
.thenReturn(instance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Promise<Void> startPromise) {
|
||||
public Completable rxStart() {
|
||||
CircuitBreaker startBreaker = CircuitBreaker.create("bot-" + botAddress + "-server-online-check-circuit-breaker", vertx,
|
||||
new CircuitBreakerOptions().setMaxFailures(1).setMaxRetries(4).setTimeout(30000)
|
||||
)
|
||||
.retryPolicy(policy -> 4000L)
|
||||
.openHandler(closed -> {
|
||||
logger.error("Circuit opened! " + botAddress);
|
||||
})
|
||||
.closeHandler(closed -> {
|
||||
logger.error("Circuit closed! " + botAddress);
|
||||
});
|
||||
|
||||
return Mono
|
||||
.fromCallable(() -> {
|
||||
var botAddress = config().getString("botAddress");
|
||||
if (botAddress == null || botAddress.isEmpty()) {
|
||||
throw new IllegalArgumentException("botAddress is not set!");
|
||||
@ -111,123 +109,88 @@ public class AsyncTdMiddleEventBusClient extends AbstractVerticle implements Asy
|
||||
}
|
||||
this.local = local;
|
||||
this.initTime = System.currentTimeMillis();
|
||||
|
||||
CircuitBreaker startBreaker = CircuitBreaker.create("bot-" + botAddress + "-server-online-check-circuit-breaker", vertx,
|
||||
new CircuitBreakerOptions().setMaxFailures(1).setMaxRetries(4).setTimeout(30000)
|
||||
)
|
||||
.retryPolicy(policy -> 4000L)
|
||||
.openHandler(closed -> {
|
||||
logger.error("Circuit opened! " + botAddress);
|
||||
return null;
|
||||
})
|
||||
.closeHandler(closed -> {
|
||||
logger.error("Circuit closed! " + botAddress);
|
||||
});
|
||||
|
||||
startBreaker.execute(future -> {
|
||||
try {
|
||||
logger.debug("Requesting tdlib.remoteclient.clients.deploy.existing");
|
||||
cluster.getEventBus().publish("tdlib.remoteclient.clients.deploy", botAddress, deliveryOptions);
|
||||
|
||||
|
||||
.then(startBreaker.rxExecute(future -> {
|
||||
logger.debug("Waiting for " + botAddress + ".readyToStart");
|
||||
var readyToStartConsumer = cluster.getEventBus().<byte[]>consumer(botAddress + ".readyToStart");
|
||||
readyToStartConsumer.handler((Message<byte[]> pingMsg) -> {
|
||||
logger.debug("Received ping reply (succeeded)");
|
||||
readyToStartConsumer.unregister(unregistered -> {
|
||||
// Reply instantly
|
||||
readyToStartConsumer
|
||||
.rxUnregister()
|
||||
.as(MonoUtils::toMono)
|
||||
.doOnError(ex -> {
|
||||
logger.error("Failed to unregister readyToStartConsumer", ex);
|
||||
})
|
||||
.then(Mono.fromCallable(() -> {
|
||||
pingMsg.reply(new byte[0]);
|
||||
if (unregistered.succeeded()) {
|
||||
logger.debug("Requesting " + botAddress + ".start");
|
||||
cluster
|
||||
return null;
|
||||
}))
|
||||
.then(cluster
|
||||
.getEventBus()
|
||||
.request(botAddress + ".start", EMPTY, deliveryOptionsWithTimeout, startMsg -> {
|
||||
if (startMsg.succeeded()) {
|
||||
logger.debug("Requesting " + botAddress + ".isWorking");
|
||||
cluster
|
||||
.getEventBus()
|
||||
.request(botAddress + ".isWorking", EMPTY, deliveryOptionsWithTimeout, msg -> {
|
||||
if (msg.succeeded()) {
|
||||
this.listen()
|
||||
.timeout(Duration.ofSeconds(30))
|
||||
.subscribeOn(Schedulers.single())
|
||||
.rxRequest(botAddress + ".start", EMPTY, deliveryOptionsWithTimeout)
|
||||
.as(MonoUtils::toMono)
|
||||
.doOnError(ex -> logger.error("Failed to request bot start", ex)))
|
||||
.doOnNext(msg -> logger.debug("Requesting " + botAddress + ".isWorking"))
|
||||
.then(cluster.getEventBus()
|
||||
.rxRequest(botAddress + ".isWorking", EMPTY, deliveryOptionsWithTimeout)
|
||||
.as(MonoUtils::toMono)
|
||||
.doOnError(ex -> logger.error("Failed to request isWorking", ex)))
|
||||
.subscribe(v -> {}, future::fail, future::complete);
|
||||
} else {
|
||||
future.fail(msg.cause());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
future.fail(startMsg.cause());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
logger.error("Failed to unregister readyToStartConsumer", unregistered.cause());
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (Exception ex) {
|
||||
future.fail(ex);
|
||||
}
|
||||
})
|
||||
.onFailure(ex -> {
|
||||
|
||||
readyToStartConsumer
|
||||
.rxCompletionHandler()
|
||||
.as(MonoUtils::toMono)
|
||||
.doOnSuccess(s -> logger.debug("Requesting tdlib.remoteclient.clients.deploy.existing"))
|
||||
.then(Mono.fromCallable(() -> cluster.getEventBus().publish("tdlib.remoteclient.clients.deploy", botAddress, deliveryOptions)))
|
||||
.subscribeOn(Schedulers.single())
|
||||
.subscribe(v -> {}, future::fail);
|
||||
}).as(MonoUtils::toMono)
|
||||
)
|
||||
.onErrorMap(ex -> {
|
||||
logger.error("Failure when starting bot " + botAddress, ex);
|
||||
startPromise.fail(new InitializationException("Can't connect tdlib middle client to tdlib middle server!"));
|
||||
return new InitializationException("Can't connect tdlib middle client to tdlib middle server!");
|
||||
})
|
||||
.onSuccess(v -> startPromise.complete());
|
||||
.as(MonoUtils::toCompletable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop(Promise<Void> stopPromise) {
|
||||
logger.debug("Stopping AsyncTdMiddle client verticle...");
|
||||
tdCloseRequested.asFlux().take(1).single().flatMap(closeRequested -> {
|
||||
if (!closeRequested) {
|
||||
return tdCrash.asMono().switchIfEmpty(Mono
|
||||
public Completable rxStop() {
|
||||
return Mono
|
||||
.fromRunnable(() -> logger.debug("Stopping AsyncTdMiddle client verticle..."))
|
||||
.then(Mono
|
||||
.firstWithSignal(
|
||||
tdCloseRequested.asMono(),
|
||||
tdClosed.asMono(),
|
||||
Mono.firstWithSignal(
|
||||
tdCrashed.asMono(),
|
||||
Mono
|
||||
.fromRunnable(() -> logger.warn("Verticle is being stopped before closing TDLib with Close()! Sending Close() before stopping..."))
|
||||
.then(this.execute(new TdApi.Close(), false)
|
||||
).then()
|
||||
.cast(TdApi.Error.class)
|
||||
).doOnTerminate(() -> {
|
||||
.then(this.execute(new TdApi.Close(), false))
|
||||
.then()
|
||||
)
|
||||
.doOnTerminate(() -> {
|
||||
logger.debug("Close() sent to td");
|
||||
markCloseRequested();
|
||||
}).then();
|
||||
} else {
|
||||
return Mono.empty();
|
||||
}
|
||||
}).thenMany(tdClosed.asFlux()).filter(closed -> closed).take(1).subscribe(v -> {}, cause -> {
|
||||
logger.debug("Failed to stop AsyncTdMiddle client verticle");
|
||||
stopPromise.fail(cause);
|
||||
}, () -> {
|
||||
logger.debug("Stopped AsyncTdMiddle client verticle");
|
||||
stopPromise.complete();
|
||||
});
|
||||
}
|
||||
|
||||
private Mono<Void> listen() {
|
||||
// Nothing to listen for now
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
private static class UpdatesBatchResult {
|
||||
public final Flux<TdApi.Object> updatesFlux;
|
||||
public final boolean completed;
|
||||
|
||||
private UpdatesBatchResult(Flux<TdApi.Object> updatesFlux, boolean completed) {
|
||||
this.updatesFlux = updatesFlux;
|
||||
this.completed = completed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new StringJoiner(", ", UpdatesBatchResult.class.getSimpleName() + "[", "]")
|
||||
.add("updatesFlux=" + updatesFlux)
|
||||
.add("completed=" + completed)
|
||||
.toString();
|
||||
}
|
||||
})
|
||||
.then(tdClosed.asMono())
|
||||
)
|
||||
)
|
||||
.doOnError(ex -> logger.debug("Failed to stop AsyncTdMiddle client verticle"))
|
||||
.doOnSuccess(s -> logger.debug("Stopped AsyncTdMiddle client verticle"))
|
||||
.as(MonoUtils::toCompletable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<TdApi.Object> receive() {
|
||||
var fluxCodec = new TdResultListMessageCodec();
|
||||
return tdCloseRequested.asFlux().take(1).single().filter(close -> !close).flatMapMany(_closed -> EventBusFlux
|
||||
return Flux
|
||||
.firstWithSignal(
|
||||
tdCloseRequested.asMono().flux().cast(TdApi.Object.class),
|
||||
tdClosed.asMono().flux().cast(TdApi.Object.class),
|
||||
EventBusFlux
|
||||
.<TdResultList>connect(cluster.getEventBus(),
|
||||
botAddress + ".updates",
|
||||
deliveryOptions,
|
||||
@ -247,7 +210,7 @@ public class AsyncTdMiddleEventBusClient extends AbstractVerticle implements Asy
|
||||
theError = new Error(406, "INVALID_UPDATE");
|
||||
logger.error("Bot updates request failed! Marking as closed.", error);
|
||||
}
|
||||
tdCrash.tryEmitValue(theError);
|
||||
tdCrashed.tryEmitValue(theError);
|
||||
return Flux.just(TdResult.failed(theError));
|
||||
}).flatMap(item -> Mono.fromCallable(item::orElseThrow))
|
||||
.filter(Objects::nonNull)
|
||||
@ -269,41 +232,37 @@ public class AsyncTdMiddleEventBusClient extends AbstractVerticle implements Asy
|
||||
markClosed();
|
||||
}
|
||||
}
|
||||
})).doFinally(s -> {
|
||||
}).doFinally(s -> {
|
||||
if (s == SignalType.ON_ERROR) {
|
||||
// Send tdClosed early to avoid errors
|
||||
logger.debug("Updates flux terminated with an error signal. Marking td as closed");
|
||||
markCloseRequested();
|
||||
markClosed();
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private void markCloseRequested() {
|
||||
if (tdCloseRequested.tryEmitNext(true).isFailure()) {
|
||||
if (tdCloseRequested.tryEmitEmpty().isFailure()) {
|
||||
logger.error("Failed to set tdCloseRequested");
|
||||
if (tdCloseRequested.tryEmitComplete().isFailure()) {
|
||||
logger.error("Failed to complete tdCloseRequested");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void markClosed() {
|
||||
if (tdCrash.tryEmitEmpty().isFailure()) {
|
||||
logger.debug("TDLib already crashed");
|
||||
}
|
||||
if (tdClosed.tryEmitNext(true).isFailure()) {
|
||||
if (tdClosed.tryEmitEmpty().isFailure()) {
|
||||
logger.error("Failed to set tdClosed");
|
||||
if (tdClosed.tryEmitComplete().isFailure()) {
|
||||
logger.error("Failed to complete tdClosed");
|
||||
}
|
||||
if (tdCrashed.tryEmitEmpty().isFailure()) {
|
||||
logger.debug("TDLib already crashed");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends TdApi.Object> Mono<TdResult<T>> execute(Function request, boolean executeDirectly) {
|
||||
|
||||
var req = new ExecuteObject(executeDirectly, request);
|
||||
return Mono
|
||||
.fromRunnable(() -> {
|
||||
if (OUTPUT_REQUESTS) {
|
||||
System.out.println(" -> " + request.toString()
|
||||
.replace("\n", " ")
|
||||
@ -311,50 +270,25 @@ public class AsyncTdMiddleEventBusClient extends AbstractVerticle implements Asy
|
||||
.replace(" ", "")
|
||||
.replace(" = ", "="));
|
||||
}
|
||||
|
||||
var crashMono = tdCrash.asMono().<TdResult<T>>map(TdResult::failed);
|
||||
var executeMono = tdCloseRequested.asFlux().take(1).single().filter(close -> !close).<TdResult<T>>flatMap((_x) -> Mono.create(sink -> {
|
||||
try {
|
||||
cluster
|
||||
.getEventBus()
|
||||
.request(botAddress + ".execute",
|
||||
req,
|
||||
deliveryOptions,
|
||||
(AsyncResult<Message<TdResultMessage>> event) -> {
|
||||
try {
|
||||
if (event.succeeded()) {
|
||||
if (event.result().body() == null) {
|
||||
sink.error(new NullPointerException("Response is empty"));
|
||||
} else {
|
||||
sink.success(Objects.requireNonNull(event.result().body()).toTdResult());
|
||||
}
|
||||
} else {
|
||||
sink.error(ResponseError.newResponseError(request, botAlias, event.cause()));
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
sink.error(t);
|
||||
}
|
||||
}
|
||||
);
|
||||
} catch (Throwable t) {
|
||||
sink.error(t);
|
||||
}
|
||||
})).<TdResult<T>>switchIfEmpty(Mono.defer(() -> Mono.just(TdResult.<T>failed(new TdApi.Error(500,
|
||||
"Client is closed or response is empty"
|
||||
))))).<TdResult<T>>handle((response, sink) -> {
|
||||
try {
|
||||
Objects.requireNonNull(response);
|
||||
})
|
||||
.then(Mono.firstWithSignal(
|
||||
tdCloseRequested.asMono().flatMap(t -> Mono.empty()),
|
||||
tdClosed.asMono().flatMap(t -> Mono.empty()),
|
||||
cluster.getEventBus()
|
||||
.<TdResultMessage>rxRequest(botAddress + ".execute", req, deliveryOptions)
|
||||
.as(MonoUtils::toMono)
|
||||
.onErrorMap(ex -> ResponseError.newResponseError(request, botAlias, ex))
|
||||
.<TdResult<T>>flatMap(resp -> resp.body() == null ? Mono.<TdResult<T>>error(new NullPointerException("Response is empty")) : Mono.just(resp.body().toTdResult()))
|
||||
.switchIfEmpty(Mono.defer(() -> Mono.just(TdResult.<T>failed(new TdApi.Error(500, "Client is closed or response is empty")))))
|
||||
.doOnNext(response -> {
|
||||
if (OUTPUT_REQUESTS) {
|
||||
System.out.println(
|
||||
" <- " + response.toString().replace("\n", " ").replace("\t", "").replace(" ", "").replace(" = ", "="));
|
||||
System.out.println(" <- " + response.toString()
|
||||
.replace("\n", " ")
|
||||
.replace("\t", "")
|
||||
.replace(" ", "")
|
||||
.replace(" = ", "="));
|
||||
}
|
||||
sink.next(response);
|
||||
} catch (Exception e) {
|
||||
sink.error(e);
|
||||
}
|
||||
}).switchIfEmpty(Mono.fromSupplier(() -> {
|
||||
return TdResult.failed(new TdApi.Error(500, "Client is closed or response is empty"));
|
||||
}));
|
||||
return Mono.firstWithValue(crashMono, executeMono);
|
||||
})
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,6 @@ import it.tdlight.tdlibsession.td.middle.TdClusterManager;
|
||||
import it.tdlight.utils.MonoUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.warp.commonutils.error.InitializationException;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.publisher.Sinks;
|
||||
@ -30,27 +29,23 @@ public class AsyncTdMiddleDirect extends AbstractVerticle implements AsyncTdMidd
|
||||
protected AsyncTdDirectImpl td;
|
||||
private String botAddress;
|
||||
private String botAlias;
|
||||
private Empty<Object> closeRequest = Sinks.empty();
|
||||
private final Empty<Object> closeRequest = Sinks.empty();
|
||||
|
||||
public AsyncTdMiddleDirect() {
|
||||
}
|
||||
|
||||
public static Mono<AsyncTdMiddleDirect> getAndDeployInstance(TdClusterManager clusterManager,
|
||||
public static Mono<AsyncTdMiddle> getAndDeployInstance(TdClusterManager clusterManager,
|
||||
String botAlias,
|
||||
String botAddress) throws InitializationException {
|
||||
try {
|
||||
String botAddress) {
|
||||
var instance = new AsyncTdMiddleDirect();
|
||||
var options = clusterManager.newDeploymentOpts().setConfig(new JsonObject()
|
||||
.put("botAlias", botAlias)
|
||||
.put("botAddress", botAddress));
|
||||
return MonoUtils.<String>executeAsFuture(promise -> {
|
||||
clusterManager.getVertx().deployVerticle(instance, options, promise);
|
||||
}).doOnNext(_v -> {
|
||||
logger.trace("Deployed verticle for bot " + botAlias + ", address: " + botAddress);
|
||||
}).thenReturn(instance);
|
||||
} catch (RuntimeException e) {
|
||||
throw new InitializationException(e);
|
||||
}
|
||||
return clusterManager.getVertx()
|
||||
.rxDeployVerticle(instance, options)
|
||||
.as(MonoUtils::toMono)
|
||||
.doOnNext(_v -> logger.trace("Deployed verticle for bot " + botAlias + ", address: " + botAddress))
|
||||
.thenReturn(instance);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -82,30 +77,21 @@ public class AsyncTdMiddleDirect extends AbstractVerticle implements AsyncTdMidd
|
||||
return td
|
||||
.receive(new AsyncTdDirectOptions(WAIT_DURATION, 1000))
|
||||
.takeUntilOther(closeRequest.asMono())
|
||||
.doOnError(ex -> {
|
||||
logger.info("TdMiddle verticle error", ex);
|
||||
})
|
||||
.doOnTerminate(() -> {
|
||||
logger.debug("TdMiddle verticle stopped");
|
||||
}).flatMap(result -> {
|
||||
if (result.succeeded()) {
|
||||
return Mono.just(result.result());
|
||||
} else {
|
||||
logger.error("Received an errored update",
|
||||
ResponseError.newResponseError("incoming update", botAlias, result.cause()));
|
||||
return Mono.<TdApi.Object>empty();
|
||||
.doOnError(ex -> logger.info("TdMiddle verticle error", ex))
|
||||
.doOnTerminate(() -> logger.debug("TdMiddle verticle stopped"))
|
||||
.doOnNext(result -> {
|
||||
if (result.failed()) {
|
||||
logger.error("Received an errored update: {}", result.cause());
|
||||
}
|
||||
});
|
||||
})
|
||||
.filter(TdResult::succeeded)
|
||||
.map(TdResult::result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Object> Mono<TdResult<T>> execute(Function requestFunction, boolean executeDirectly) {
|
||||
return td.<T>execute(requestFunction, executeDirectly).onErrorMap(error -> {
|
||||
return ResponseError.newResponseError(
|
||||
requestFunction,
|
||||
botAlias,
|
||||
error
|
||||
);
|
||||
});
|
||||
return td
|
||||
.<T>execute(requestFunction, executeDirectly)
|
||||
.onErrorMap(error -> ResponseError.newResponseError(requestFunction, botAlias, error));
|
||||
}
|
||||
}
|
||||
|
@ -4,28 +4,25 @@ import it.tdlight.jni.TdApi;
|
||||
import it.tdlight.jni.TdApi.Function;
|
||||
import it.tdlight.jni.TdApi.Object;
|
||||
import it.tdlight.tdlibsession.td.TdResult;
|
||||
import it.tdlight.tdlibsession.td.direct.AsyncTdDirectImpl;
|
||||
import it.tdlight.tdlibsession.td.middle.AsyncTdMiddle;
|
||||
import it.tdlight.tdlibsession.td.middle.TdClusterManager;
|
||||
import it.tdlight.tdlibsession.td.middle.client.AsyncTdMiddleEventBusClient;
|
||||
import it.tdlight.tdlibsession.td.middle.server.AsyncTdMiddleEventBusServer;
|
||||
import java.util.Objects;
|
||||
import org.warp.commonutils.error.InitializationException;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.publisher.ReplayProcessor;
|
||||
import reactor.core.publisher.Sinks;
|
||||
import reactor.core.publisher.Sinks.One;
|
||||
|
||||
public class AsyncTdMiddleLocal implements AsyncTdMiddle {
|
||||
|
||||
private final AsyncTdDirectImpl td;
|
||||
private final AsyncTdMiddleEventBusServer srv;
|
||||
private final TdClusterManager masterClusterManager;
|
||||
private ReplayProcessor<AsyncTdMiddleEventBusClient> cli = ReplayProcessor.cacheLast();
|
||||
private final One<AsyncTdMiddle> cli = Sinks.one();
|
||||
private final String botAlias;
|
||||
private final String botAddress;
|
||||
|
||||
public AsyncTdMiddleLocal(TdClusterManager masterClusterManager, String botAlias, String botAddress) throws InitializationException {
|
||||
this.td = new AsyncTdDirectImpl(botAlias);
|
||||
public AsyncTdMiddleLocal(TdClusterManager masterClusterManager, String botAlias, String botAddress) {
|
||||
this.srv = new AsyncTdMiddleEventBusServer(masterClusterManager);
|
||||
this.masterClusterManager = masterClusterManager;
|
||||
this.botAlias = botAlias;
|
||||
@ -33,28 +30,24 @@ public class AsyncTdMiddleLocal implements AsyncTdMiddle {
|
||||
}
|
||||
|
||||
public Mono<AsyncTdMiddleLocal> start() {
|
||||
return srv.start(botAddress, botAlias, true).onErrorMap(InitializationException::new).flatMap(_x -> {
|
||||
try {
|
||||
return AsyncTdMiddleEventBusClient.getAndDeployInstance(masterClusterManager, botAlias, botAddress, true).doOnNext(cli -> {
|
||||
this.cli.onNext(cli);
|
||||
}).doOnError(error -> this.cli.onError(error)).doOnSuccess(_v -> this.cli.onComplete());
|
||||
} catch (InitializationException e) {
|
||||
this.cli.onError(e);
|
||||
return Mono.error(e);
|
||||
}
|
||||
}).map(v -> this);
|
||||
return srv
|
||||
.start(botAddress, botAlias, true)
|
||||
.onErrorMap(InitializationException::new)
|
||||
.single()
|
||||
.then(AsyncTdMiddleEventBusClient.getAndDeployInstance(masterClusterManager, botAlias, botAddress, true))
|
||||
.single()
|
||||
.doOnNext(this.cli::tryEmitValue)
|
||||
.doOnError(this.cli::tryEmitError)
|
||||
.thenReturn(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<TdApi.Object> receive() {
|
||||
return cli.filter(Objects::nonNull).single().flatMapMany(AsyncTdMiddleEventBusClient::receive);
|
||||
return cli.asMono().single().flatMapMany(AsyncTdMiddle::receive);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Object> Mono<TdResult<T>> execute(Function request, boolean executeDirectly) {
|
||||
return cli
|
||||
.filter(obj -> Objects.nonNull(obj))
|
||||
.single()
|
||||
.flatMap(c -> c.<T>execute(request, executeDirectly));
|
||||
return cli.asMono().single().flatMap(c -> c.execute(request, executeDirectly));
|
||||
}
|
||||
}
|
||||
|
@ -6,9 +6,8 @@ import io.vertx.core.AsyncResult;
|
||||
import io.vertx.core.Future;
|
||||
import io.vertx.core.Handler;
|
||||
import io.vertx.core.Promise;
|
||||
import io.vertx.core.eventbus.Message;
|
||||
import io.vertx.core.eventbus.MessageConsumer;
|
||||
import it.tdlight.common.ConstructorDetector;
|
||||
import io.vertx.reactivex.core.eventbus.Message;
|
||||
import io.vertx.reactivex.core.eventbus.MessageConsumer;
|
||||
import it.tdlight.jni.TdApi;
|
||||
import it.tdlight.jni.TdApi.AuthorizationStateClosed;
|
||||
import it.tdlight.jni.TdApi.Error;
|
||||
@ -21,11 +20,8 @@ import it.tdlight.tdlibsession.td.direct.AsyncTdDirectImpl;
|
||||
import it.tdlight.tdlibsession.td.direct.AsyncTdDirectOptions;
|
||||
import it.tdlight.tdlibsession.td.middle.ExecuteObject;
|
||||
import it.tdlight.tdlibsession.td.middle.TdClusterManager;
|
||||
import it.tdlight.tdlibsession.td.middle.TdExecuteObjectMessageCodec;
|
||||
import it.tdlight.tdlibsession.td.middle.TdMessageCodec;
|
||||
import it.tdlight.tdlibsession.td.middle.TdResultList;
|
||||
import it.tdlight.tdlibsession.td.middle.TdResultListMessageCodec;
|
||||
import it.tdlight.tdlibsession.td.middle.TdResultMessageCodec;
|
||||
import it.tdlight.utils.MonoUtils;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
@ -71,13 +67,6 @@ public class AsyncTdMiddleEventBusServer {
|
||||
public AsyncTdMiddleEventBusServer(TdClusterManager clusterManager) {
|
||||
this.cluster = clusterManager;
|
||||
this.tdOptions = new AsyncTdDirectOptions(WAIT_DURATION, 1000);
|
||||
if (cluster.registerDefaultCodec(TdResultList.class, new TdResultListMessageCodec())) {
|
||||
cluster.registerDefaultCodec(ExecuteObject.class, new TdExecuteObjectMessageCodec());
|
||||
cluster.registerDefaultCodec(TdResultMessage.class, new TdResultMessageCodec());
|
||||
for (Class<?> value : ConstructorDetector.getTDConstructorsUnsafe().values()) {
|
||||
cluster.registerDefaultCodec(value, new TdMessageCodec(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Mono<AsyncTdMiddleEventBusServer> start(String botAddress, String botAlias, boolean local) {
|
||||
|
@ -1,16 +1,18 @@
|
||||
package it.tdlight.utils;
|
||||
|
||||
import io.reactivex.Completable;
|
||||
import io.reactivex.Maybe;
|
||||
import io.reactivex.Single;
|
||||
import io.vertx.core.AsyncResult;
|
||||
import io.vertx.core.Future;
|
||||
import io.vertx.core.Handler;
|
||||
import io.vertx.core.Promise;
|
||||
import io.vertx.core.Vertx;
|
||||
import it.tdlight.jni.TdApi;
|
||||
import it.tdlight.jni.TdApi.Object;
|
||||
import it.tdlight.tdlibsession.td.TdError;
|
||||
import it.tdlight.tdlibsession.td.TdResult;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
import org.reactivestreams.Subscription;
|
||||
@ -59,22 +61,6 @@ public class MonoUtils {
|
||||
return PromiseSink.of(context, promise);
|
||||
}
|
||||
|
||||
public static <T, R> BiConsumer<? super T, SynchronousSink<R>> executeBlockingSink(Vertx vertx, BiConsumer<? super T, SynchronousSink<R>> handler) {
|
||||
return (value, sink) -> {
|
||||
vertx.executeBlocking((Promise<R> finished) -> {
|
||||
handler.accept(value, PromiseSink.of(sink.currentContext(), finished));
|
||||
}, toHandler(sink));
|
||||
};
|
||||
}
|
||||
|
||||
public static <T> Mono<T> executeBlocking(Vertx vertx, Consumer<SynchronousSink<T>> action) {
|
||||
return Mono.create((MonoSink<T> sink) -> {
|
||||
vertx.executeBlocking((Promise<T> finished) -> {
|
||||
action.accept(toSink(sink.currentContext(), finished));
|
||||
}, toHandler(sink));
|
||||
});
|
||||
}
|
||||
|
||||
public static <T> Mono<T> executeAsFuture(Consumer<Handler<AsyncResult<T>>> action) {
|
||||
return Mono.<T>fromFuture(() -> {
|
||||
return CompletableFutureUtils.getCompletableFuture(() -> {
|
||||
@ -206,4 +192,30 @@ public class MonoUtils {
|
||||
}, () -> cf.complete(null));
|
||||
return cf;
|
||||
}
|
||||
|
||||
public static <T> Mono<T> toMono(Future<T> future) {
|
||||
return Mono.<T>create(sink -> future.onComplete(result -> {
|
||||
if (result.succeeded()) {
|
||||
sink.success(result.result());
|
||||
} else {
|
||||
sink.error(result.cause());
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public static <T> Mono<T> toMono(Single<T> single) {
|
||||
return Mono.fromDirect(single.toFlowable());
|
||||
}
|
||||
|
||||
public static <T> Mono<T> toMono(Maybe<T> single) {
|
||||
return Mono.fromDirect(single.toFlowable());
|
||||
}
|
||||
|
||||
public static <T> Mono<T> toMono(Completable completable) {
|
||||
return Mono.fromDirect(completable.toFlowable());
|
||||
}
|
||||
|
||||
public static <T> Completable toCompletable(Mono<T> s) {
|
||||
return Completable.fromPublisher(s);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user