This commit is contained in:
Andrea Cavalli 2021-01-24 19:13:46 +01:00
parent e1e18d61cf
commit 8b71b9e2cb
15 changed files with 354 additions and 146 deletions

View File

@ -0,0 +1,13 @@
TDLib session container
=======================
This software is a wrapper for [TDLight Java](https://github.com/tdlight-team/tdlight-java)
Unlike TDLight java, this wrapper abstracts TDLib to make it possible to run remotely,
decoupling it into a client and a server.
TDLib session container can be used in various ways:
- Clustered
- Remote
- Local
- Direct (The client process, as TDLight java)

View File

@ -139,7 +139,7 @@
<dependency> <dependency>
<groupId>it.tdlight</groupId> <groupId>it.tdlight</groupId>
<artifactId>tdlight-java</artifactId> <artifactId>tdlight-java</artifactId>
<version>[3.171.36,)</version> <version>[4.171.54,)</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>it.cavallium</groupId> <groupId>it.cavallium</groupId>

View File

@ -25,6 +25,7 @@ import reactor.core.publisher.Mono;
import reactor.core.publisher.Sinks; import reactor.core.publisher.Sinks;
import reactor.core.publisher.Sinks.One; import reactor.core.publisher.Sinks.One;
import reactor.core.scheduler.Schedulers; import reactor.core.scheduler.Schedulers;
import reactor.tools.agent.ReactorDebugAgent;
public class TDLibRemoteClient implements AutoCloseable { public class TDLibRemoteClient implements AutoCloseable {
@ -41,13 +42,27 @@ public class TDLibRemoteClient implements AutoCloseable {
*/ */
private final AtomicInteger statsActiveDeployments = new AtomicInteger(); private final AtomicInteger statsActiveDeployments = new AtomicInteger();
public TDLibRemoteClient(SecurityInfo securityInfo, String masterHostname, String netInterface, int port, Set<String> membersAddresses) { public static boolean runningFromIntelliJ() {
return System.getProperty("java.class.path").contains("idea_rt.jar")
|| System.getProperty("idea.test.cyclic.buffer.size") != null;
}
public TDLibRemoteClient(SecurityInfo securityInfo,
String masterHostname,
String netInterface,
int port,
Set<String> membersAddresses,
boolean enableStacktraces) {
this.securityInfo = securityInfo; this.securityInfo = securityInfo;
this.masterHostname = masterHostname; this.masterHostname = masterHostname;
this.netInterface = netInterface; this.netInterface = netInterface;
this.port = port; this.port = port;
this.membersAddresses = membersAddresses; this.membersAddresses = membersAddresses;
if (enableStacktraces && !runningFromIntelliJ()) {
ReactorDebugAgent.init();
}
try { try {
Init.start(); Init.start();
} catch (CantLoadLibrary ex) { } catch (CantLoadLibrary ex) {
@ -74,13 +89,14 @@ public class TDLibRemoteClient implements AutoCloseable {
Path keyStorePasswordPath = Paths.get(args[4]); Path keyStorePasswordPath = Paths.get(args[4]);
Path trustStorePath = Paths.get(args[5]); Path trustStorePath = Paths.get(args[5]);
Path trustStorePasswordPath = Paths.get(args[6]); Path trustStorePasswordPath = Paths.get(args[6]);
boolean enableStacktraces = Boolean.parseBoolean(args[7]);
var loggerContext = (org.apache.logging.log4j.core.LoggerContext) LogManager.getContext(false); var loggerContext = (org.apache.logging.log4j.core.LoggerContext) LogManager.getContext(false);
loggerContext.setConfigLocation(TDLibRemoteClient.class.getResource("/tdlib-session-container-log4j2.xml").toURI()); loggerContext.setConfigLocation(TDLibRemoteClient.class.getResource("/tdlib-session-container-log4j2.xml").toURI());
var securityInfo = new SecurityInfo(keyStorePath, keyStorePasswordPath, trustStorePath, trustStorePasswordPath); var securityInfo = new SecurityInfo(keyStorePath, keyStorePasswordPath, trustStorePath, trustStorePasswordPath);
var client = new TDLibRemoteClient(securityInfo, masterHostname, netInterface, port, membersAddresses); var client = new TDLibRemoteClient(securityInfo, masterHostname, netInterface, port, membersAddresses, enableStacktraces);
client client
.start() .start()
@ -140,16 +156,18 @@ public class TDLibRemoteClient implements AutoCloseable {
.setConfig(new JsonObject() .setConfig(new JsonObject()
.put("botId", req.id()) .put("botId", req.id())
.put("botAlias", req.alias()) .put("botAlias", req.alias())
.put("local", false)); .put("local", false)
.put("implementationDetails", req.implementationDetails()));
var verticle = new AsyncTdMiddleEventBusServer(); var verticle = new AsyncTdMiddleEventBusServer();
// Binlog path // Binlog path
var sessPath = getSessionDirectory(req.id()); var sessPath = getSessionDirectory(req.id());
var mediaPath = getMediaDirectory(req.id());
var blPath = getSessionBinlogDirectory(req.id()); var blPath = getSessionBinlogDirectory(req.id());
BinlogUtils BinlogUtils
.chooseBinlog(clusterManager.getVertx().fileSystem(), blPath, req.binlog(), req.binlogDate()) .chooseBinlog(clusterManager.getVertx().fileSystem(), blPath, req.binlog(), req.binlogDate())
.then(BinlogUtils.cleanSessionPath(clusterManager.getVertx().fileSystem(), blPath, sessPath)) .then(BinlogUtils.cleanSessionPath(clusterManager.getVertx().fileSystem(), blPath, sessPath, mediaPath))
.then(clusterManager.getVertx().rxDeployVerticle(verticle, deploymentOptions).as(MonoUtils::toMono)) .then(clusterManager.getVertx().rxDeployVerticle(verticle, deploymentOptions).as(MonoUtils::toMono))
.subscribeOn(Schedulers.single()) .subscribeOn(Schedulers.single())
.subscribe( .subscribe(

View File

@ -1,15 +1,15 @@
package it.tdlight.tdlibsession.td.direct; package it.tdlight.tdlibsession.td.direct;
import io.vertx.core.json.JsonObject;
import it.tdlight.common.TelegramClient; import it.tdlight.common.TelegramClient;
import it.tdlight.jni.TdApi; import it.tdlight.jni.TdApi;
import it.tdlight.jni.TdApi.AuthorizationStateClosed; import it.tdlight.jni.TdApi.AuthorizationStateClosed;
import it.tdlight.jni.TdApi.Close; import it.tdlight.jni.TdApi.Close;
import it.tdlight.jni.TdApi.Function; import it.tdlight.jni.TdApi.Function;
import it.tdlight.jni.TdApi.Object;
import it.tdlight.jni.TdApi.Ok; import it.tdlight.jni.TdApi.Ok;
import it.tdlight.jni.TdApi.UpdateAuthorizationState; import it.tdlight.jni.TdApi.UpdateAuthorizationState;
import it.tdlight.tdlibsession.td.TdError;
import it.tdlight.tdlibsession.td.TdResult; import it.tdlight.tdlibsession.td.TdResult;
import it.tdlight.tdlight.ClientManager;
import it.tdlight.utils.MonoUtils; import it.tdlight.utils.MonoUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -23,11 +23,17 @@ public class AsyncTdDirectImpl implements AsyncTdDirect {
private static final Logger logger = LoggerFactory.getLogger(AsyncTdDirect.class); private static final Logger logger = LoggerFactory.getLogger(AsyncTdDirect.class);
private final One<TelegramClient> td = Sinks.one(); private final TelegramClientFactory telegramClientFactory;
private final JsonObject implementationDetails;
private final String botAlias; private final String botAlias;
public AsyncTdDirectImpl(String botAlias) { private final One<TelegramClient> td = Sinks.one();
public AsyncTdDirectImpl(TelegramClientFactory telegramClientFactory,
JsonObject implementationDetails,
String botAlias) {
this.telegramClientFactory = telegramClientFactory;
this.implementationDetails = implementationDetails;
this.botAlias = botAlias; this.botAlias = botAlias;
} }
@ -65,41 +71,40 @@ public class AsyncTdDirectImpl implements AsyncTdDirect {
public Flux<TdApi.Object> receive(AsyncTdDirectOptions options) { public Flux<TdApi.Object> receive(AsyncTdDirectOptions options) {
// If closed it will be either true or false // If closed it will be either true or false
final One<Boolean> closedFromTd = Sinks.one(); final One<Boolean> closedFromTd = Sinks.one();
return Flux.<TdApi.Object>create(emitter -> { return telegramClientFactory.create(implementationDetails)
var client = ClientManager.create((Object object) -> { .flatMapMany(client -> Flux
emitter.next(object); .<TdApi.Object>create(updatesSink -> {
// Close the emitter if receive closed state client.initialize((TdApi.Object object) -> {
if (object.getConstructor() == UpdateAuthorizationState.CONSTRUCTOR updatesSink.next(object);
&& ((UpdateAuthorizationState) object).authorizationState.getConstructor() // Close the emitter if receive closed state
== AuthorizationStateClosed.CONSTRUCTOR) { if (object.getConstructor() == UpdateAuthorizationState.CONSTRUCTOR
logger.debug("Received closed status from tdlib"); && ((UpdateAuthorizationState) object).authorizationState.getConstructor()
closedFromTd.tryEmitValue(true); == AuthorizationStateClosed.CONSTRUCTOR) {
emitter.complete(); logger.debug("Received closed status from tdlib");
} closedFromTd.tryEmitValue(true);
}, emitter::error, emitter::error); updatesSink.complete();
try { }
this.td.tryEmitValue(client).orThrow(); }, updatesSink::error, updatesSink::error);
} catch (Exception ex) {
emitter.error(ex);
}
// Send close if the stream is disposed before tdlib is closed if (td.tryEmitValue(client).isFailure()) {
emitter.onDispose(() -> { updatesSink.error(new TdError(500, "Failed to emit td client"));
// Try to emit false, so that if it has not been closed from tdlib, now it is explicitly false. }
closedFromTd.tryEmitValue(false);
closedFromTd.asMono() // Send close if the stream is disposed before tdlib is closed
.filter(isClosedFromTd -> !isClosedFromTd) updatesSink.onDispose(() -> {
.doOnNext(x -> { // Try to emit false, so that if it has not been closed from tdlib, now it is explicitly false.
logger.warn("The stream has been disposed without closing tdlib. Sending TdApi.Close()..."); closedFromTd.tryEmitValue(false);
client.send(new Close(),
result -> logger.warn("Close result: {}", result), closedFromTd.asMono().filter(isClosedFromTd -> !isClosedFromTd).doOnNext(x -> {
ex -> logger.error("Error when disposing td client", ex) logger.warn("The stream has been disposed without closing tdlib. Sending TdApi.Close()...");
); client.send(new Close(),
result -> logger.warn("Close result: {}", result),
ex -> logger.error("Error when disposing td client", ex)
);
}).subscribeOn(Schedulers.single()).subscribe();
});
}) })
.subscribeOn(Schedulers.single()) .subscribeOn(Schedulers.boundedElastic())
.subscribe(); );
});
});
} }
} }

View File

@ -0,0 +1,28 @@
package it.tdlight.tdlibsession.td.direct;
import io.vertx.core.json.JsonObject;
import it.tdlight.common.TelegramClient;
import it.tdlight.tdlight.ClientManager;
import it.tdlight.utils.MonoUtils;
import reactor.core.publisher.Mono;
public class TelegramClientFactory {
public TelegramClientFactory() {
}
public Mono<TelegramClient> create(JsonObject implementationDetails) {
return MonoUtils.fromBlockingSingle(() -> {
var implementationName = implementationDetails.getString("name", "native-client");
switch (implementationName) {
case "native-client":
return ClientManager.create();
case "test-client":
//todo: create a noop test client with optional behaviours
default:
return null;
}
});
}
}

View File

@ -57,7 +57,7 @@ import reactor.core.scheduler.Schedulers;
public class AsyncTdEasy { public class AsyncTdEasy {
private static final Logger logger = LoggerFactory.getLogger(AsyncTdEasy.class); private final Logger logger;
private static final Scheduler scheduler = Schedulers.newSingle("AsyncTdEasy", false); private static final Scheduler scheduler = Schedulers.newSingle("AsyncTdEasy", false);
private final ReplayProcessor<AuthorizationState> authState = ReplayProcessor.create(1); private final ReplayProcessor<AuthorizationState> authState = ReplayProcessor.create(1);
@ -72,6 +72,7 @@ public class AsyncTdEasy {
public AsyncTdEasy(AsyncTdMiddle td, String logName) { public AsyncTdEasy(AsyncTdMiddle td, String logName) {
this.td = td; this.td = td;
this.logName = logName; this.logName = logName;
this.logger = LoggerFactory.getLogger("AsyncTdEasy " + logName);
// todo: use Duration.ZERO instead of 10ms interval // todo: use Duration.ZERO instead of 10ms interval
this.incomingUpdates = td.receive() this.incomingUpdates = td.receive()
@ -93,8 +94,9 @@ public class AsyncTdEasy {
} else { } else {
logger.error(ex.getLocalizedMessage(), ex); logger.error(ex.getLocalizedMessage(), ex);
} }
}).doOnComplete(() -> { })
authState.asFlux().take(1).single().subscribe(authState -> { .doOnComplete(() -> {
authState.asFlux().take(1).single().subscribeOn(Schedulers.single()).subscribe(authState -> {
onUpdatesTerminated(); onUpdatesTerminated();
if (authState.getConstructor() != AuthorizationStateClosed.CONSTRUCTOR) { if (authState.getConstructor() != AuthorizationStateClosed.CONSTRUCTOR) {
logger.warn("Updates stream has closed while" logger.warn("Updates stream has closed while"
@ -104,7 +106,7 @@ public class AsyncTdEasy {
} }
}); });
}).doOnError(ex -> { }).doOnError(ex -> {
authState.asFlux().take(1).single().subscribe(authState -> { authState.asFlux().take(1).single().subscribeOn(Schedulers.single()).subscribe(authState -> {
onUpdatesTerminated(); onUpdatesTerminated();
if (authState.getConstructor() != AuthorizationStateClosed.CONSTRUCTOR) { if (authState.getConstructor() != AuthorizationStateClosed.CONSTRUCTOR) {
logger.warn("Updates stream has terminated with an error while" logger.warn("Updates stream has terminated with an error while"

View File

@ -1,5 +1,6 @@
package it.tdlight.tdlibsession.td.middle; package it.tdlight.tdlibsession.td.middle;
import io.vertx.core.json.JsonObject;
import java.util.Arrays; import java.util.Arrays;
import java.util.Objects; import java.util.Objects;
import java.util.StringJoiner; import java.util.StringJoiner;
@ -10,12 +11,14 @@ public final class StartSessionMessage {
private final String alias; private final String alias;
private final byte[] binlog; private final byte[] binlog;
private final long binlogDate; private final long binlogDate;
private final JsonObject implementationDetails;
public StartSessionMessage(int id, String alias, byte[] binlog, long binlogDate) { public StartSessionMessage(int id, String alias, byte[] binlog, long binlogDate, JsonObject implementationDetails) {
this.id = id; this.id = id;
this.alias = alias; this.alias = alias;
this.binlog = binlog; this.binlog = binlog;
this.binlogDate = binlogDate; this.binlogDate = binlogDate;
this.implementationDetails = implementationDetails;
} }
public int id() { public int id() {
@ -34,6 +37,10 @@ public final class StartSessionMessage {
return binlogDate; return binlogDate;
} }
public JsonObject implementationDetails() {
return implementationDetails;
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) { if (this == o) {
@ -54,7 +61,10 @@ public final class StartSessionMessage {
if (!Objects.equals(alias, that.alias)) { if (!Objects.equals(alias, that.alias)) {
return false; return false;
} }
return Arrays.equals(binlog, that.binlog); if (!Arrays.equals(binlog, that.binlog)) {
return false;
}
return Objects.equals(implementationDetails, that.implementationDetails);
} }
@Override @Override
@ -63,6 +73,7 @@ public final class StartSessionMessage {
result = 31 * result + (alias != null ? alias.hashCode() : 0); result = 31 * result + (alias != null ? alias.hashCode() : 0);
result = 31 * result + Arrays.hashCode(binlog); result = 31 * result + Arrays.hashCode(binlog);
result = 31 * result + (int) (binlogDate ^ (binlogDate >>> 32)); result = 31 * result + (int) (binlogDate ^ (binlogDate >>> 32));
result = 31 * result + (implementationDetails != null ? implementationDetails.hashCode() : 0);
return result; return result;
} }
@ -73,6 +84,7 @@ public final class StartSessionMessage {
.add("alias='" + alias + "'") .add("alias='" + alias + "'")
.add("binlog=" + Arrays.toString(binlog)) .add("binlog=" + Arrays.toString(binlog))
.add("binlogDate=" + binlogDate) .add("binlogDate=" + binlogDate)
.add("implementationDetails=" + implementationDetails)
.toString(); .toString();
} }
} }

View File

@ -2,6 +2,7 @@ package it.tdlight.tdlibsession.td.middle;
import io.vertx.core.buffer.Buffer; import io.vertx.core.buffer.Buffer;
import io.vertx.core.eventbus.MessageCodec; import io.vertx.core.eventbus.MessageCodec;
import io.vertx.core.json.JsonObject;
import it.tdlight.utils.VertxBufferInputStream; import it.tdlight.utils.VertxBufferInputStream;
import it.tdlight.utils.VertxBufferOutputStream; import it.tdlight.utils.VertxBufferOutputStream;
import org.warp.commonutils.stream.SafeDataInputStream; import org.warp.commonutils.stream.SafeDataInputStream;
@ -25,6 +26,7 @@ public class StartSessionMessageCodec implements MessageCodec<StartSessionMessag
dos.writeInt(t.binlog().length); dos.writeInt(t.binlog().length);
dos.write(t.binlog()); dos.write(t.binlog());
dos.writeLong(t.binlogDate()); dos.writeLong(t.binlogDate());
dos.writeUTF(t.implementationDetails().toString());
} }
} }
} }
@ -33,7 +35,12 @@ public class StartSessionMessageCodec implements MessageCodec<StartSessionMessag
public StartSessionMessage decodeFromWire(int pos, Buffer buffer) { public StartSessionMessage decodeFromWire(int pos, Buffer buffer) {
try (var fis = new VertxBufferInputStream(buffer, pos)) { try (var fis = new VertxBufferInputStream(buffer, pos)) {
try (var dis = new SafeDataInputStream(fis)) { try (var dis = new SafeDataInputStream(fis)) {
return new StartSessionMessage(dis.readInt(), dis.readUTF(), dis.readNBytes(dis.readInt()), dis.readLong()); return new StartSessionMessage(dis.readInt(),
dis.readUTF(),
dis.readNBytes(dis.readInt()),
dis.readLong(),
new JsonObject(dis.readUTF())
);
} }
} }
} }

View File

@ -160,6 +160,18 @@ public class TdClusterManager {
} }
vertxOptions.setPreferNativeTransport(true); vertxOptions.setPreferNativeTransport(true);
// check for blocked threads every 5s
vertxOptions.setBlockedThreadCheckInterval(5);
vertxOptions.setBlockedThreadCheckIntervalUnit(TimeUnit.SECONDS);
// warn if an event loop thread handler took more than 100ms to execute
vertxOptions.setMaxEventLoopExecuteTime(50);
vertxOptions.setMaxEventLoopExecuteTimeUnit(TimeUnit.MILLISECONDS);
// warn if an worker thread handler took more than 10s to execute
vertxOptions.setMaxWorkerExecuteTime(10);
vertxOptions.setMaxWorkerExecuteTimeUnit(TimeUnit.SECONDS);
// log the stack trace if an event loop or worker handler took more than 20s to execute
vertxOptions.setWarningExceptionTime(100);
vertxOptions.setWarningExceptionTimeUnit(TimeUnit.MILLISECONDS);
return Mono return Mono
.<Vertx>create(sink -> { .<Vertx>create(sink -> {

View File

@ -1,6 +1,7 @@
package it.tdlight.tdlibsession.td.middle.client; package it.tdlight.tdlibsession.td.middle.client;
import io.vertx.core.eventbus.DeliveryOptions; import io.vertx.core.eventbus.DeliveryOptions;
import io.vertx.core.json.JsonObject;
import io.vertx.reactivex.core.Vertx; import io.vertx.reactivex.core.Vertx;
import io.vertx.reactivex.core.eventbus.Message; import io.vertx.reactivex.core.eventbus.Message;
import io.vertx.reactivex.core.eventbus.MessageConsumer; import io.vertx.reactivex.core.eventbus.MessageConsumer;
@ -22,7 +23,6 @@ import it.tdlight.utils.MonoUtils;
import it.tdlight.utils.MonoUtils.SinkRWStream; import it.tdlight.utils.MonoUtils.SinkRWStream;
import java.nio.file.Path; import java.nio.file.Path;
import java.time.Duration; import java.time.Duration;
import org.reactivestreams.Publisher;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
@ -66,6 +66,7 @@ public class AsyncTdMiddleEventBusClient implements AsyncTdMiddle {
int botId, int botId,
String botAlias, String botAlias,
boolean local, boolean local,
JsonObject implementationDetails,
Path binlogsArchiveDirectory) { Path binlogsArchiveDirectory) {
var instance = new AsyncTdMiddleEventBusClient(clusterManager); var instance = new AsyncTdMiddleEventBusClient(clusterManager);
return retrieveBinlog(clusterManager.getVertx(), binlogsArchiveDirectory, botId) return retrieveBinlog(clusterManager.getVertx(), binlogsArchiveDirectory, botId)
@ -78,7 +79,7 @@ public class AsyncTdMiddleEventBusClient implements AsyncTdMiddle {
.thenReturn(binlog) .thenReturn(binlog)
) )
.<AsyncTdMiddle>flatMap(binlog -> instance .<AsyncTdMiddle>flatMap(binlog -> instance
.start(botId, botAlias, local, binlog) .start(botId, botAlias, local, implementationDetails, binlog)
.thenReturn(instance) .thenReturn(instance)
) )
.single(); .single();
@ -96,7 +97,11 @@ public class AsyncTdMiddleEventBusClient implements AsyncTdMiddle {
return this.binlog.asMono().flatMap(binlog -> BinlogUtils.saveBinlog(binlog, data)); return this.binlog.asMono().flatMap(binlog -> BinlogUtils.saveBinlog(binlog, data));
} }
public Mono<Void> start(int botId, String botAlias, boolean local, BinlogAsyncFile binlog) { public Mono<Void> start(int botId,
String botAlias,
boolean local,
JsonObject implementationDetails,
BinlogAsyncFile binlog) {
this.botId = botId; this.botId = botId;
this.botAlias = botAlias; this.botAlias = botAlias;
this.botAddress = "bots.bot." + this.botId; this.botAddress = "bots.bot." + this.botId;
@ -111,16 +116,22 @@ public class AsyncTdMiddleEventBusClient implements AsyncTdMiddle {
var binlogLastModifiedTime = tuple.getT1(); var binlogLastModifiedTime = tuple.getT1();
var binlogData = tuple.getT2(); var binlogData = tuple.getT2();
var msg = new StartSessionMessage(this.botId, this.botAlias, binlogData, binlogLastModifiedTime); var msg = new StartSessionMessage(this.botId,
this.botAlias,
binlogData,
binlogLastModifiedTime,
implementationDetails
);
return setupUpdatesListener() return setupUpdatesListener()
.then(cluster.getEventBus().<byte[]>rxRequest("bots.start-bot", msg).as(MonoUtils::toMono)) .then(Mono.defer(() -> local ? Mono.empty()
: cluster.getEventBus().<byte[]>rxRequest("bots.start-bot", msg).as(MonoUtils::toMono)))
.then(); .then();
}); });
} }
@SuppressWarnings("CallingSubscribeInNonBlockingScope") @SuppressWarnings("CallingSubscribeInNonBlockingScope")
private Mono<Void> setupUpdatesListener() { private Mono<Void> setupUpdatesListener() {
MessageConsumer<TdResultList> updateConsumer = MessageConsumer.newInstance(cluster.getEventBus().<TdResultList>consumer(botAddress + ".updates").getDelegate()); MessageConsumer<TdResultList> updateConsumer = MessageConsumer.newInstance(cluster.getEventBus().<TdResultList>consumer(botAddress + ".updates").setMaxBufferedMessages(5000).getDelegate());
updateConsumer.endHandler(h -> { updateConsumer.endHandler(h -> {
logger.error("<<<<<<<<<<<<<<<<EndHandler?>>>>>>>>>>>>>"); logger.error("<<<<<<<<<<<<<<<<EndHandler?>>>>>>>>>>>>>");
}); });
@ -138,7 +149,12 @@ public class AsyncTdMiddleEventBusClient implements AsyncTdMiddle {
@Override @Override
public Flux<TdApi.Object> receive() { public Flux<TdApi.Object> receive() {
// Here the updates will be received // Here the updates will be received
return cluster.getEventBus().<byte[]>rxRequest(botAddress + ".ready-to-receive", EMPTY).as(MonoUtils::toMono) return Mono
.fromRunnable(() -> logger.trace("Called receive() from parent"))
.doOnSuccess(s -> logger.trace("Sending ready-to-receive"))
.then(cluster.getEventBus().<byte[]>rxRequest(botAddress + ".ready-to-receive", EMPTY, deliveryOptionsWithTimeout).as(MonoUtils::toMono))
.doOnSuccess(s -> logger.trace("Sent ready-to-receive, received reply"))
.doOnSuccess(s -> logger.trace("About to read updates flux"))
.thenMany(updates.readAsFlux()) .thenMany(updates.readAsFlux())
.cast(io.vertx.core.eventbus.Message.class) .cast(io.vertx.core.eventbus.Message.class)
.timeout(Duration.ofSeconds(20), Mono.fromCallable(() -> { .timeout(Duration.ofSeconds(20), Mono.fromCallable(() -> {
@ -157,15 +173,14 @@ public class AsyncTdMiddleEventBusClient implements AsyncTdMiddle {
.doOnTerminate(updatesStreamEnd::tryEmitEmpty); .doOnTerminate(updatesStreamEnd::tryEmitEmpty);
} }
private Publisher<TdApi.Object> interceptUpdate(TdApi.Object update) { private Mono<TdApi.Object> interceptUpdate(TdApi.Object update) {
switch (update.getConstructor()) { switch (update.getConstructor()) {
case TdApi.UpdateAuthorizationState.CONSTRUCTOR: case TdApi.UpdateAuthorizationState.CONSTRUCTOR:
var updateAuthorizationState = (TdApi.UpdateAuthorizationState) update; var updateAuthorizationState = (TdApi.UpdateAuthorizationState) update;
switch (updateAuthorizationState.authorizationState.getConstructor()) { switch (updateAuthorizationState.authorizationState.getConstructor()) {
case TdApi.AuthorizationStateClosed.CONSTRUCTOR: case TdApi.AuthorizationStateClosed.CONSTRUCTOR:
return cluster return Mono.fromRunnable(() -> logger.trace("Received AuthorizationStateClosed from tdlib"))
.getEventBus() .then(cluster.getEventBus().<EndSessionMessage>rxRequest(this.botAddress + ".read-binlog", EMPTY).as(MonoUtils::toMono))
.<EndSessionMessage>rxRequest(this.botAddress + ".read-binlog", EMPTY).as(MonoUtils::toMono)
.doOnNext(l -> logger.info("Received binlog from server. Size: " + BinlogUtils.humanReadableByteCountBin(l.body().binlog().length))) .doOnNext(l -> logger.info("Received binlog from server. Size: " + BinlogUtils.humanReadableByteCountBin(l.body().binlog().length)))
.flatMap(latestBinlog -> this.saveBinlog(latestBinlog.body().binlog())) .flatMap(latestBinlog -> this.saveBinlog(latestBinlog.body().binlog()))
.doOnSuccess(s -> logger.info("Overwritten binlog from server")) .doOnSuccess(s -> logger.info("Overwritten binlog from server"))
@ -182,16 +197,20 @@ public class AsyncTdMiddleEventBusClient implements AsyncTdMiddle {
return Mono return Mono
.firstWithSignal( .firstWithSignal(
MonoUtils.castVoid(crash.asMono()), MonoUtils.castVoid(crash.asMono()),
cluster.getEventBus() Mono
.<TdResultMessage>rxRequest(botAddress + ".execute", req, deliveryOptions).as(MonoUtils::toMono) .fromRunnable(() -> logger.trace("Executing request {}", request))
.then(cluster.getEventBus().<TdResultMessage>rxRequest(botAddress + ".execute", req, deliveryOptions).as(MonoUtils::toMono))
.onErrorMap(ex -> ResponseError.newResponseError(request, botAlias, ex)) .onErrorMap(ex -> ResponseError.newResponseError(request, botAlias, ex))
.<TdResult<T>>flatMap(resp -> Mono.fromCallable(() -> { .<TdResult<T>>flatMap(resp -> Mono
if (resp.body() == null) { .fromCallable(() -> {
throw ResponseError.newResponseError(request, botAlias, new TdError(500, "Response is empty")); if (resp.body() == null) {
} else { throw ResponseError.newResponseError(request, botAlias, new TdError(500, "Response is empty"));
return resp.body().toTdResult(); } else {
} return resp.body().toTdResult();
})) }
})
)
.doOnSuccess(s -> logger.trace("Executed request"))
) )
.switchIfEmpty(Mono.defer(() -> Mono.fromCallable(() -> { .switchIfEmpty(Mono.defer(() -> Mono.fromCallable(() -> {
throw ResponseError.newResponseError(request, botAlias, new TdError(500, "Client is closed or response is empty")); throw ResponseError.newResponseError(request, botAlias, new TdError(500, "Client is closed or response is empty"));

View File

@ -12,6 +12,7 @@ import it.tdlight.tdlibsession.td.ResponseError;
import it.tdlight.tdlibsession.td.TdResult; import it.tdlight.tdlibsession.td.TdResult;
import it.tdlight.tdlibsession.td.direct.AsyncTdDirectImpl; import it.tdlight.tdlibsession.td.direct.AsyncTdDirectImpl;
import it.tdlight.tdlibsession.td.direct.AsyncTdDirectOptions; import it.tdlight.tdlibsession.td.direct.AsyncTdDirectOptions;
import it.tdlight.tdlibsession.td.direct.TelegramClientFactory;
import it.tdlight.tdlibsession.td.middle.AsyncTdMiddle; import it.tdlight.tdlibsession.td.middle.AsyncTdMiddle;
import it.tdlight.tdlibsession.td.middle.TdClusterManager; import it.tdlight.tdlibsession.td.middle.TdClusterManager;
import it.tdlight.utils.MonoUtils; import it.tdlight.utils.MonoUtils;
@ -26,21 +27,26 @@ public class AsyncTdMiddleDirect extends AbstractVerticle implements AsyncTdMidd
private static final Logger logger = LoggerFactory.getLogger(AsyncTdMiddleDirect.class); private static final Logger logger = LoggerFactory.getLogger(AsyncTdMiddleDirect.class);
private final TelegramClientFactory clientFactory;
protected AsyncTdDirectImpl td; protected AsyncTdDirectImpl td;
private String botAddress; private String botAddress;
private String botAlias; private String botAlias;
private final Empty<Object> closeRequest = Sinks.empty(); private final Empty<Object> closeRequest = Sinks.empty();
public AsyncTdMiddleDirect() { public AsyncTdMiddleDirect() {
this.clientFactory = new TelegramClientFactory();
} }
public static Mono<AsyncTdMiddle> getAndDeployInstance(TdClusterManager clusterManager, public static Mono<AsyncTdMiddle> getAndDeployInstance(TdClusterManager clusterManager,
String botAlias, String botAlias,
String botAddress) { String botAddress,
JsonObject implementationDetails) {
var instance = new AsyncTdMiddleDirect(); var instance = new AsyncTdMiddleDirect();
var options = clusterManager.newDeploymentOpts().setConfig(new JsonObject() var options = clusterManager.newDeploymentOpts().setConfig(new JsonObject()
.put("botAlias", botAlias) .put("botAlias", botAlias)
.put("botAddress", botAddress)); .put("botAddress", botAddress)
.put("implementationDetails", implementationDetails));
return clusterManager.getVertx() return clusterManager.getVertx()
.rxDeployVerticle(instance, options) .rxDeployVerticle(instance, options)
.as(MonoUtils::toMono) .as(MonoUtils::toMono)
@ -60,8 +66,12 @@ public class AsyncTdMiddleDirect extends AbstractVerticle implements AsyncTdMidd
throw new IllegalArgumentException("botAlias is not set!"); throw new IllegalArgumentException("botAlias is not set!");
} }
this.botAlias = botAlias; this.botAlias = botAlias;
var implementationDetails = config().getJsonObject("implementationDetails");
if (implementationDetails == null) {
throw new IllegalArgumentException("implementationDetails is not set!");
}
this.td = new AsyncTdDirectImpl(botAlias); this.td = new AsyncTdDirectImpl(clientFactory, implementationDetails, botAlias);
startPromise.complete(); startPromise.complete();
} }

View File

@ -29,19 +29,26 @@ public class AsyncTdMiddleLocal implements AsyncTdMiddle {
private final AsyncTdMiddleEventBusServer srv; private final AsyncTdMiddleEventBusServer srv;
private final One<AsyncTdMiddle> cli = Sinks.one(); private final One<AsyncTdMiddle> cli = Sinks.one();
private final JsonObject implementationDetails;
public AsyncTdMiddleLocal(TdClusterManager masterClusterManager, String botAlias, int botId) { public AsyncTdMiddleLocal(TdClusterManager masterClusterManager,
String botAlias,
int botId,
JsonObject implementationDetails) {
this.masterClusterManager = masterClusterManager; this.masterClusterManager = masterClusterManager;
this.botAlias = botAlias; this.botAlias = botAlias;
this.botId = botId; this.botId = botId;
this.implementationDetails = implementationDetails;
this.vertx = masterClusterManager.getVertx(); this.vertx = masterClusterManager.getVertx();
this.deploymentOptions = masterClusterManager this.deploymentOptions = masterClusterManager
.newDeploymentOpts() .newDeploymentOpts()
.setConfig(new JsonObject() .setConfig(new JsonObject()
.put("botId", botId) .put("botId", botId)
.put("botAlias", botAlias) .put("botAlias", botAlias)
.put("local", true)); .put("local", true)
.put("implementationDetails", implementationDetails)
);
this.srv = new AsyncTdMiddleEventBusServer(); this.srv = new AsyncTdMiddleEventBusServer();
} }
@ -51,7 +58,10 @@ public class AsyncTdMiddleLocal implements AsyncTdMiddle {
.single() .single()
.then(Mono.fromSupplier(() -> new AsyncTdMiddleEventBusClient(masterClusterManager))) .then(Mono.fromSupplier(() -> new AsyncTdMiddleEventBusClient(masterClusterManager)))
.zipWith(AsyncTdMiddleEventBusClient.retrieveBinlog(vertx, Path.of("binlogs"), botId)) .zipWith(AsyncTdMiddleEventBusClient.retrieveBinlog(vertx, Path.of("binlogs"), botId))
.flatMap(tuple -> tuple.getT1().start(botId, botAlias, true, tuple.getT2()).thenReturn(tuple.getT1())) .flatMap(tuple -> tuple
.getT1()
.start(botId, botAlias, true, implementationDetails, tuple.getT2())
.thenReturn(tuple.getT1()))
.onErrorMap(InitializationException::new) .onErrorMap(InitializationException::new)
.doOnNext(this.cli::tryEmitValue) .doOnNext(this.cli::tryEmitValue)
.doOnError(this.cli::tryEmitError) .doOnError(this.cli::tryEmitError)

View File

@ -18,6 +18,7 @@ import it.tdlight.tdlibsession.td.TdError;
import it.tdlight.tdlibsession.td.TdResultMessage; import it.tdlight.tdlibsession.td.TdResultMessage;
import it.tdlight.tdlibsession.td.direct.AsyncTdDirectImpl; import it.tdlight.tdlibsession.td.direct.AsyncTdDirectImpl;
import it.tdlight.tdlibsession.td.direct.AsyncTdDirectOptions; import it.tdlight.tdlibsession.td.direct.AsyncTdDirectOptions;
import it.tdlight.tdlibsession.td.direct.TelegramClientFactory;
import it.tdlight.tdlibsession.td.middle.ExecuteObject; import it.tdlight.tdlibsession.td.middle.ExecuteObject;
import it.tdlight.tdlibsession.td.middle.TdResultList; import it.tdlight.tdlibsession.td.middle.TdResultList;
import it.tdlight.tdlibsession.td.middle.TdResultListMessageCodec; import it.tdlight.tdlibsession.td.middle.TdResultListMessageCodec;
@ -30,6 +31,7 @@ import org.slf4j.LoggerFactory;
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;
import reactor.core.publisher.Sinks.Empty;
import reactor.core.publisher.Sinks.One; import reactor.core.publisher.Sinks.One;
import reactor.core.scheduler.Schedulers; import reactor.core.scheduler.Schedulers;
import reactor.util.function.Tuples; import reactor.util.function.Tuples;
@ -43,6 +45,7 @@ public class AsyncTdMiddleEventBusServer extends AbstractVerticle {
// Values configured from constructor // Values configured from constructor
private final AsyncTdDirectOptions tdOptions; private final AsyncTdDirectOptions tdOptions;
private final TelegramClientFactory clientFactory;
// Variables configured by the user at startup // Variables configured by the user at startup
private final One<Integer> botId = Sinks.one(); private final One<Integer> botId = Sinks.one();
@ -57,48 +60,55 @@ public class AsyncTdMiddleEventBusServer extends AbstractVerticle {
private final One<MessageConsumer<byte[]>> readyToReceiveConsumer = Sinks.one(); private final One<MessageConsumer<byte[]>> readyToReceiveConsumer = Sinks.one();
private final One<MessageConsumer<byte[]>> pingConsumer = Sinks.one(); private final One<MessageConsumer<byte[]>> pingConsumer = Sinks.one();
private final One<Flux<Void>> pipeFlux = Sinks.one(); private final One<Flux<Void>> pipeFlux = Sinks.one();
private final Empty<Void> terminatePingOverPipeFlux = Sinks.empty();
public AsyncTdMiddleEventBusServer() { public AsyncTdMiddleEventBusServer() {
this.tdOptions = new AsyncTdDirectOptions(WAIT_DURATION, 15); this.tdOptions = new AsyncTdDirectOptions(WAIT_DURATION, 50);
this.clientFactory = new TelegramClientFactory();
} }
@Override @Override
public Completable rxStart() { public Completable rxStart() {
return MonoUtils.toCompletable(Mono return MonoUtils
.fromCallable(() -> { .toCompletable(MonoUtils
var botId = config().getInteger("botId"); .fromBlockingMaybe(() -> {
if (botId == null || botId <= 0) { logger.trace("Stating verticle");
throw new IllegalArgumentException("botId is not set!"); var botId = config().getInteger("botId");
} if (botId == null || botId <= 0) {
if (this.botId.tryEmitValue(botId).isFailure()) { throw new IllegalArgumentException("botId is not set!");
throw new IllegalStateException("Failed to set botId"); }
} if (this.botId.tryEmitValue(botId).isFailure()) {
var botAddress = "bots.bot." + botId; throw new IllegalStateException("Failed to set botId");
if (this.botAddress.tryEmitValue(botAddress).isFailure()) { }
throw new IllegalStateException("Failed to set botAddress"); var botAddress = "bots.bot." + botId;
} if (this.botAddress.tryEmitValue(botAddress).isFailure()) {
var botAlias = config().getString("botAlias"); throw new IllegalStateException("Failed to set botAddress");
if (botAlias == null || botAlias.isEmpty()) { }
throw new IllegalArgumentException("botAlias is not set!"); var botAlias = config().getString("botAlias");
} if (botAlias == null || botAlias.isEmpty()) {
if (this.botAlias.tryEmitValue(botAlias).isFailure()) { throw new IllegalArgumentException("botAlias is not set!");
throw new IllegalStateException("Failed to set botAlias"); }
} if (this.botAlias.tryEmitValue(botAlias).isFailure()) {
var local = config().getBoolean("local"); throw new IllegalStateException("Failed to set botAlias");
if (local == null) { }
throw new IllegalArgumentException("local is not set!"); var local = config().getBoolean("local");
} if (local == null) {
if (this.local.tryEmitValue(local).isFailure()) { throw new IllegalArgumentException("local is not set!");
throw new IllegalStateException("Failed to set local"); }
} var implementationDetails = config().getJsonObject("implementationDetails");
if (implementationDetails == null) {
throw new IllegalArgumentException("implementationDetails is not set!");
}
var td = new AsyncTdDirectImpl(botAlias); var td = new AsyncTdDirectImpl(clientFactory, implementationDetails, botAlias);
if (this.td.tryEmitValue(td).isFailure()) { if (this.td.tryEmitValue(td).isFailure()) {
throw new IllegalStateException("Failed to set td instance"); throw new IllegalStateException("Failed to set td instance");
} }
return onSuccessfulStartRequest(td, botAddress, botAlias, botId, local); return onSuccessfulStartRequest(td, botAddress, botAlias, botId, local);
}) })
.flatMap(Mono::hide)); .flatMap(Mono::hide)
.doOnSuccess(s -> logger.trace("Stated verticle"))
);
} }
private Mono<Void> onSuccessfulStartRequest(AsyncTdDirectImpl td, String botAddress, String botAlias, int botId, boolean local) { private Mono<Void> onSuccessfulStartRequest(AsyncTdDirectImpl td, String botAddress, String botAlias, int botId, boolean local) {
@ -115,6 +125,8 @@ public class AsyncTdMiddleEventBusServer extends AbstractVerticle {
private Mono<Void> listen(AsyncTdDirectImpl td, String botAddress, String botAlias, int botId, boolean local) { private Mono<Void> listen(AsyncTdDirectImpl td, String botAddress, String botAlias, int botId, boolean local) {
return Mono.<Void>create(registrationSink -> { return Mono.<Void>create(registrationSink -> {
logger.trace("Preparing listeners");
MessageConsumer<ExecuteObject> executeConsumer = vertx.eventBus().consumer(botAddress + ".execute"); MessageConsumer<ExecuteObject> executeConsumer = vertx.eventBus().consumer(botAddress + ".execute");
if (this.executeConsumer.tryEmitValue(executeConsumer).isFailure()) { if (this.executeConsumer.tryEmitValue(executeConsumer).isFailure()) {
registrationSink.error(new IllegalStateException("Failed to set executeConsumer")); registrationSink.error(new IllegalStateException("Failed to set executeConsumer"));
@ -126,10 +138,12 @@ public class AsyncTdMiddleEventBusServer extends AbstractVerticle {
executeConsumer.endHandler(h -> sink.complete()); executeConsumer.endHandler(h -> sink.complete());
}) })
.flatMap(msg -> { .flatMap(msg -> {
logger.trace("Received execute request {}", msg.body());
var request = overrideRequest(msg.body().getRequest(), botId); var request = overrideRequest(msg.body().getRequest(), botId);
return td return td
.execute(request, msg.body().isExecuteDirectly()) .execute(request, msg.body().isExecuteDirectly())
.map(result -> Tuples.of(msg, result)); .map(result -> Tuples.of(msg, result))
.doOnSuccess(s -> logger.trace("Executed successfully"));
}) })
.handle((tuple, sink) -> { .handle((tuple, sink) -> {
var msg = tuple.getT1(); var msg = tuple.getT1();
@ -137,16 +151,21 @@ public class AsyncTdMiddleEventBusServer extends AbstractVerticle {
var replyOpts = new DeliveryOptions().setLocalOnly(local); var replyOpts = new DeliveryOptions().setLocalOnly(local);
var replyValue = new TdResultMessage(response.result(), response.cause()); var replyValue = new TdResultMessage(response.result(), response.cause());
try { try {
logger.trace("Replying with success response");
msg.reply(replyValue, replyOpts); msg.reply(replyValue, replyOpts);
sink.next(response); sink.next(response);
} catch (Exception ex) { } catch (Exception ex) {
logger.trace("Replying with error response: {}", ex.getLocalizedMessage());
msg.fail(500, ex.getLocalizedMessage()); msg.fail(500, ex.getLocalizedMessage());
sink.error(ex); sink.error(ex);
} }
}) })
.then() .then()
.subscribeOn(Schedulers.single()) .subscribeOn(Schedulers.single())
.subscribe(v -> {}, ex -> logger.error("Error when processing an execute request", ex)); .subscribe(v -> {},
ex -> logger.error("Error when processing an execute request", ex),
() -> logger.trace("Finished handling execute requests")
);
MessageConsumer<byte[]> readBinlogConsumer = vertx.eventBus().consumer(botAddress + ".read-binlog"); MessageConsumer<byte[]> readBinlogConsumer = vertx.eventBus().consumer(botAddress + ".read-binlog");
if (this.readBinlogConsumer.tryEmitValue(readBinlogConsumer).isFailure()) { if (this.readBinlogConsumer.tryEmitValue(readBinlogConsumer).isFailure()) {
@ -163,28 +182,37 @@ public class AsyncTdMiddleEventBusServer extends AbstractVerticle {
registrationSink.error(new IllegalStateException("Failed to set readyToReceiveConsumer")); registrationSink.error(new IllegalStateException("Failed to set readyToReceiveConsumer"));
return; return;
} }
Flux
// Pipe the data
var pipeSubscription = Flux
.<Message<byte[]>>create(sink -> { .<Message<byte[]>>create(sink -> {
readyToReceiveConsumer.handler(sink::next); readyToReceiveConsumer.handler(sink::next);
readyToReceiveConsumer.endHandler(h -> sink.complete()); readyToReceiveConsumer.endHandler(h -> sink.complete());
}) })
.take(1)
.limitRequest(1)
.single()
.flatMap(msg -> this.pipeFlux .flatMap(msg -> this.pipeFlux
.asMono() .asMono()
.timeout(Duration.ofSeconds(5)) .timeout(Duration.ofSeconds(5))
.map(pipeFlux -> Tuples.of(msg, pipeFlux))) .map(pipeFlux -> Tuples.of(msg, pipeFlux)))
.doOnNext(tuple -> { .doOnError(ex -> logger.error("Error when processing a ready-to-receive request", ex))
.flatMapMany(tuple -> {
var opts = new DeliveryOptions().setLocalOnly(local).setSendTimeout(Duration.ofSeconds(10).toMillis()); var opts = new DeliveryOptions().setLocalOnly(local).setSendTimeout(Duration.ofSeconds(10).toMillis());
tuple.getT1().reply(EMPTY, opts); tuple.getT1().reply(EMPTY, opts);
logger.trace("Replied to ready-to-receive");
// Start piping the data // Start piping the data
//noinspection CallingSubscribeInNonBlockingScope return tuple.getT2().doOnSubscribe(s -> {
tuple.getT2() logger.trace("Subscribed to updates pipe");
.subscribeOn(Schedulers.single()) });
.subscribe();
}) })
.then() .then()
.doOnSuccess(s -> logger.trace("Finished handling ready-to-receive requests (updates pipe ended)"))
.subscribeOn(Schedulers.single()) .subscribeOn(Schedulers.single())
.subscribe(v -> {}, ex -> logger.error("Error when processing a ready-to-receive request", ex)); // Don't handle errors here. Handle them in pipeFlux
.subscribe(v -> {});
MessageConsumer<byte[]> pingConsumer = vertx.eventBus().consumer(botAddress + ".ping"); MessageConsumer<byte[]> pingConsumer = vertx.eventBus().consumer(botAddress + ".ping");
if (this.pingConsumer.tryEmitValue(pingConsumer).isFailure()) { if (this.pingConsumer.tryEmitValue(pingConsumer).isFailure()) {
@ -202,7 +230,10 @@ public class AsyncTdMiddleEventBusServer extends AbstractVerticle {
}) })
.then() .then()
.subscribeOn(Schedulers.single()) .subscribeOn(Schedulers.single())
.subscribe(v -> {}, ex -> logger.error("Error when processing a ping request", ex)); .subscribe(v -> {},
ex -> logger.error("Error when processing a ping request", ex),
() -> logger.trace("Finished handling ping requests")
);
//noinspection ResultOfMethodCallIgnored //noinspection ResultOfMethodCallIgnored
@ -212,6 +243,7 @@ public class AsyncTdMiddleEventBusServer extends AbstractVerticle {
.andThen(readyToReceiveConsumer.rxCompletionHandler()) .andThen(readyToReceiveConsumer.rxCompletionHandler())
.andThen(pingConsumer.rxCompletionHandler()) .andThen(pingConsumer.rxCompletionHandler())
.subscribeOn(io.reactivex.schedulers.Schedulers.single()) .subscribeOn(io.reactivex.schedulers.Schedulers.single())
.doOnComplete(() -> logger.trace("Finished preparing listeners"))
.subscribe(registrationSink::success, registrationSink::error); .subscribe(registrationSink::success, registrationSink::error);
}); });
} }
@ -268,37 +300,37 @@ public class AsyncTdMiddleEventBusServer extends AbstractVerticle {
} }
private Mono<Void> pipe(AsyncTdDirectImpl td, String botAddress, String botAlias, int botId, boolean local) { private Mono<Void> pipe(AsyncTdDirectImpl td, String botAddress, String botAlias, int botId, boolean local) {
logger.trace("Preparing to pipe requests");
Flux<TdResultList> updatesFlux = td Flux<TdResultList> updatesFlux = td
.receive(tdOptions) .receive(tdOptions)
.flatMap(item -> Mono.defer(() -> { .takeUntil(item -> {
if (item instanceof Update) { if (item instanceof Update) {
var tdUpdate = (Update) item; var tdUpdate = (Update) item;
if (tdUpdate.getConstructor() == UpdateAuthorizationState.CONSTRUCTOR) { if (tdUpdate.getConstructor() == UpdateAuthorizationState.CONSTRUCTOR) {
var tdUpdateAuthorizationState = (UpdateAuthorizationState) tdUpdate; var updateAuthorizationState = (UpdateAuthorizationState) tdUpdate;
if (tdUpdateAuthorizationState.authorizationState.getConstructor() if (updateAuthorizationState.authorizationState.getConstructor() == AuthorizationStateClosed.CONSTRUCTOR) {
== AuthorizationStateClosed.CONSTRUCTOR) { return true;
logger.debug("Undeploying after receiving AuthorizationStateClosed");
return rxStop().as(MonoUtils::toMono).thenReturn(item);
} }
} }
} else if (item instanceof Error) { } else if (item instanceof Error) {
// An error in updates means that a fatal error occurred return true;
logger.debug("Undeploying after receiving a fatal error");
return rxStop().as(MonoUtils::toMono).thenReturn(item);
} }
return Mono.just(item); return false;
})) })
.flatMap(item -> Mono.fromCallable(() -> { .flatMap(update -> Mono.fromCallable(() -> {
if (item.getConstructor() == TdApi.Error.CONSTRUCTOR) { if (update.getConstructor() == TdApi.Error.CONSTRUCTOR) {
var error = (Error) item; var error = (Error) update;
throw new TdError(error.code, error.message); throw new TdError(error.code, error.message);
} else { } else {
return item; return update;
} }
})) }))
.bufferTimeout(tdOptions.getEventsSize(), local ? Duration.ofMillis(1) : Duration.ofMillis(100)) .bufferTimeout(tdOptions.getEventsSize(), local ? Duration.ofMillis(1) : Duration.ofMillis(100))
.windowTimeout(1, Duration.ofSeconds(5)) .doFinally(signalType -> terminatePingOverPipeFlux.tryEmitEmpty())
.flatMap(w -> w.defaultIfEmpty(Collections.emptyList())) .mergeWith(Flux
.interval(Duration.ofSeconds(5))
.map(l -> Collections.<TdApi.Object>emptyList())
.takeUntilOther(terminatePingOverPipeFlux.asMono()))
.map(TdResultList::new); .map(TdResultList::new);
var fluxCodec = new TdResultListMessageCodec(); var fluxCodec = new TdResultListMessageCodec();
@ -312,7 +344,33 @@ public class AsyncTdMiddleEventBusServer extends AbstractVerticle {
.sender(botAddress + ".updates", opts); .sender(botAddress + ".updates", opts);
var pipeFlux = updatesFlux var pipeFlux = updatesFlux
.flatMap(update -> updatesSender.rxWrite(update).as(MonoUtils::toMono).then()) .flatMap(updatesList -> updatesSender
.rxWrite(updatesList)
.as(MonoUtils::toMono)
.thenReturn(updatesList)
)
.flatMap(updatesList -> Flux
.fromIterable(updatesList.value())
.flatMap(item -> {
if (item instanceof Update) {
var tdUpdate = (Update) item;
if (tdUpdate.getConstructor() == UpdateAuthorizationState.CONSTRUCTOR) {
var tdUpdateAuthorizationState = (UpdateAuthorizationState) tdUpdate;
if (tdUpdateAuthorizationState.authorizationState.getConstructor()
== AuthorizationStateClosed.CONSTRUCTOR) {
logger.debug("Undeploying after receiving AuthorizationStateClosed");
return rxStop().as(MonoUtils::toMono).thenReturn(item);
}
}
} else if (item instanceof Error) {
// An error in updates means that a fatal error occurred
logger.debug("Undeploying after receiving a fatal error");
return rxStop().as(MonoUtils::toMono).thenReturn(item);
}
return Mono.just(item);
})
.then()
)
.doOnTerminate(() -> updatesSender.close(h -> { .doOnTerminate(() -> updatesSender.close(h -> {
if (h.failed()) { if (h.failed()) {
logger.error("Failed to close \"updates\" message sender"); logger.error("Failed to close \"updates\" message sender");
@ -324,7 +382,7 @@ public class AsyncTdMiddleEventBusServer extends AbstractVerticle {
.doOnError(ex2 -> logger.error("Unexpected error", ex2)) .doOnError(ex2 -> logger.error("Unexpected error", ex2))
.then(); .then();
}); });
MonoUtils.emitValue(this.pipeFlux, pipeFlux); return MonoUtils.emitValue(this.pipeFlux, pipeFlux)
return Mono.empty(); .doOnSuccess(s -> logger.trace("Prepared piping requests successfully"));
} }
} }

View File

@ -69,13 +69,20 @@ public class BinlogUtils {
.then(); .then();
} }
public static Mono<Void> cleanSessionPath(FileSystem vertxFilesystem, Path binlogPath, Path sessionPath) { public static Mono<Void> cleanSessionPath(FileSystem vertxFilesystem,
Path binlogPath,
Path sessionPath,
Path mediaPath) {
return vertxFilesystem return vertxFilesystem
.rxReadFile(binlogPath.toString()).as(MonoUtils::toMono) .rxReadFile(binlogPath.toString()).as(MonoUtils::toMono)
.flatMap(buffer -> vertxFilesystem .flatMap(buffer -> vertxFilesystem
.rxReadDir(sessionPath.toString(), "^(?!td.binlog$).*").as(MonoUtils::toMono) .rxReadDir(sessionPath.toString(), "^(?!td.binlog$).*").as(MonoUtils::toMono)
.flatMapMany(Flux::fromIterable) .flatMapMany(Flux::fromIterable)
.doOnNext(file -> logger.debug("Deleting file {}", file)) .doOnNext(file -> logger.debug("Deleting session file {}", file))
.flatMap(file -> vertxFilesystem.rxDeleteRecursive(file, true).as(MonoUtils::toMono))
.then(vertxFilesystem.rxReadDir(mediaPath.toString(), "^(?!td.binlog$).*").as(MonoUtils::toMono))
.flatMapMany(Flux::fromIterable)
.doOnNext(file -> logger.debug("Deleting media file {}", file))
.flatMap(file -> vertxFilesystem.rxDeleteRecursive(file, true).as(MonoUtils::toMono)) .flatMap(file -> vertxFilesystem.rxDeleteRecursive(file, true).as(MonoUtils::toMono))
.onErrorResume(ex -> Mono.empty()) .onErrorResume(ex -> Mono.empty())
.then() .then()

View File

@ -99,7 +99,7 @@ public class MonoUtils {
} }
public static <T> Mono<T> fromBlockingMaybe(Callable<T> callable) { public static <T> Mono<T> fromBlockingMaybe(Callable<T> callable) {
return Mono.fromCallable(callable).subscribeOn(Schedulers.boundedElastic()); return Mono.fromCallable(callable).publishOn(Schedulers.boundedElastic());
} }
public static <T> Mono<T> fromBlockingSingle(Callable<T> callable) { public static <T> Mono<T> fromBlockingSingle(Callable<T> callable) {
@ -471,7 +471,9 @@ public class MonoUtils {
@Override @Override
public io.vertx.core.streams.ReadStream<T> fetch(long amount) { public io.vertx.core.streams.ReadStream<T> fetch(long amount) {
if (fetchMode.get()) { if (fetchMode.get()) {
readCoreSubscription.request(amount); if (amount > 0) {
readCoreSubscription.request(amount);
}
} }
return this; return this;
} }
@ -498,6 +500,7 @@ public class MonoUtils {
@Override @Override
public void end(Handler<AsyncResult<Void>> handler) { public void end(Handler<AsyncResult<Void>> handler) {
/*
MonoUtils.emitCompleteFuture(sink).recover(error -> { MonoUtils.emitCompleteFuture(sink).recover(error -> {
if (error instanceof EmissionException) { if (error instanceof EmissionException) {
var sinkError = (EmissionException) error; var sinkError = (EmissionException) error;
@ -514,6 +517,8 @@ public class MonoUtils {
drainSubscription.dispose(); drainSubscription.dispose();
} }
}).onComplete(handler); }).onComplete(handler);
*/
MonoUtils.emitCompleteFuture(sink).onComplete(handler);
} }
@Override @Override
@ -621,7 +626,9 @@ public class MonoUtils {
@Override @Override
public io.vertx.core.streams.ReadStream<T> fetch(long amount) { public io.vertx.core.streams.ReadStream<T> fetch(long amount) {
if (fetchMode.get()) { if (fetchMode.get()) {
readCoreSubscription.request(amount); if (amount > 0) {
readCoreSubscription.request(amount);
}
} }
return this; return this;
} }