tdlib-session-container/src/main/java/it/tdlight/tdlibsession/td/middle/server/AsyncTdMiddleEventBusServer...

404 lines
16 KiB
Java
Raw Normal View History

package it.tdlight.tdlibsession.td.middle.server;
2021-01-23 18:49:21 +01:00
import io.reactivex.Completable;
import io.vertx.core.eventbus.DeliveryOptions;
2021-01-24 23:08:14 +01:00
import io.vertx.core.eventbus.ReplyException;
import io.vertx.core.eventbus.ReplyFailure;
2021-01-23 18:49:21 +01:00
import io.vertx.reactivex.core.AbstractVerticle;
2021-01-22 17:31:09 +01:00
import io.vertx.reactivex.core.eventbus.Message;
import io.vertx.reactivex.core.eventbus.MessageConsumer;
2021-01-23 18:49:21 +01:00
import io.vertx.reactivex.core.eventbus.MessageProducer;
2020-10-14 15:14:54 +02:00
import it.tdlight.jni.TdApi;
import it.tdlight.jni.TdApi.AuthorizationStateClosed;
import it.tdlight.jni.TdApi.Error;
2021-01-23 22:33:52 +01:00
import it.tdlight.jni.TdApi.Function;
import it.tdlight.jni.TdApi.SetTdlibParameters;
import it.tdlight.jni.TdApi.Update;
import it.tdlight.jni.TdApi.UpdateAuthorizationState;
2021-01-23 22:33:52 +01:00
import it.tdlight.tdlibsession.remoteclient.TDLibRemoteClient;
2021-01-24 04:21:47 +01:00
import it.tdlight.tdlibsession.td.TdError;
import it.tdlight.tdlibsession.td.direct.AsyncTdDirectImpl;
2021-01-13 04:00:43 +01:00
import it.tdlight.tdlibsession.td.direct.AsyncTdDirectOptions;
2021-01-24 19:13:46 +01:00
import it.tdlight.tdlibsession.td.direct.TelegramClientFactory;
import it.tdlight.tdlibsession.td.middle.ExecuteObject;
import it.tdlight.tdlibsession.td.middle.TdResultList;
import it.tdlight.tdlibsession.td.middle.TdResultListMessageCodec;
2021-01-26 19:22:55 +01:00
import it.tdlight.tdlibsession.td.middle.TdResultMessage;
2021-01-23 22:33:52 +01:00
import it.tdlight.utils.BinlogUtils;
import it.tdlight.utils.MonoUtils;
2021-01-24 23:08:14 +01:00
import java.net.ConnectException;
import java.time.Duration;
2021-09-29 11:39:38 +02:00
import java.util.List;
2021-09-30 18:22:50 +02:00
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
2021-02-20 21:25:11 +01:00
import org.warp.commonutils.log.Logger;
import org.warp.commonutils.log.LoggerFactory;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
2021-01-23 18:49:21 +01:00
import reactor.core.scheduler.Schedulers;
import reactor.util.function.Tuples;
2021-01-23 18:49:21 +01:00
public class AsyncTdMiddleEventBusServer extends AbstractVerticle {
// Static values
2021-01-23 18:49:21 +01:00
protected static final Logger logger = LoggerFactory.getLogger("TdMiddleServer");
public static final byte[] EMPTY = new byte[0];
public static final Duration WAIT_DURATION = Duration.ofSeconds(1);
// Values configured from constructor
2021-01-13 04:00:43 +01:00
private final AsyncTdDirectOptions tdOptions;
2021-01-24 19:13:46 +01:00
private final TelegramClientFactory clientFactory;
// Variables configured by the user at startup
2021-09-30 18:22:50 +02:00
private final AtomicReference<Integer> botId = new AtomicReference<>();
private final AtomicReference<String> botAddress = new AtomicReference<>();
private final AtomicReference<String> botAlias = new AtomicReference<>();
// Variables configured at startup
2021-09-30 18:22:50 +02:00
private final AtomicReference<AsyncTdDirectImpl> td = new AtomicReference<>();
private final AtomicReference<MessageConsumer<ExecuteObject>> executeConsumer = new AtomicReference<>();
private final AtomicReference<MessageConsumer<byte[]>> readBinlogConsumer = new AtomicReference<>();
private final AtomicReference<MessageConsumer<byte[]>> readyToReceiveConsumer = new AtomicReference<>();
private final AtomicReference<MessageConsumer<byte[]>> pingConsumer = new AtomicReference<>();
private final AtomicReference<Flux<Void>> pipeFlux = new AtomicReference<>();
2021-01-23 18:49:21 +01:00
public AsyncTdMiddleEventBusServer() {
2021-02-15 02:29:49 +01:00
this.tdOptions = new AsyncTdDirectOptions(WAIT_DURATION, 100);
2021-01-24 19:13:46 +01:00
this.clientFactory = new TelegramClientFactory();
}
2021-01-23 18:49:21 +01:00
@Override
public Completable rxStart() {
2021-01-24 19:13:46 +01:00
return MonoUtils
.toCompletable(MonoUtils
.fromBlockingMaybe(() -> {
logger.trace("Stating verticle");
var botId = config().getInteger("botId");
if (botId == null || botId <= 0) {
throw new IllegalArgumentException("botId is not set!");
}
2021-09-30 18:22:50 +02:00
this.botId.set(botId);
2021-01-24 19:13:46 +01:00
var botAddress = "bots.bot." + botId;
2021-09-30 18:22:50 +02:00
this.botAddress.set(botAddress);
2021-01-24 19:13:46 +01:00
var botAlias = config().getString("botAlias");
if (botAlias == null || botAlias.isEmpty()) {
throw new IllegalArgumentException("botAlias is not set!");
}
2021-09-30 18:22:50 +02:00
this.botAlias.set(botAlias);
2021-01-24 19:13:46 +01:00
var local = config().getBoolean("local");
if (local == null) {
throw new IllegalArgumentException("local is not set!");
}
var implementationDetails = config().getJsonObject("implementationDetails");
if (implementationDetails == null) {
throw new IllegalArgumentException("implementationDetails is not set!");
}
2021-01-24 19:13:46 +01:00
var td = new AsyncTdDirectImpl(clientFactory, implementationDetails, botAlias);
2021-09-30 18:22:50 +02:00
this.td.set(td);
2021-09-09 20:14:00 +02:00
return new OnSuccessfulStartRequestInfo(td, botAddress, botAlias, botId, local);
2021-01-24 19:13:46 +01:00
})
2021-09-09 20:14:00 +02:00
.flatMap(r -> onSuccessfulStartRequest(r.td, r.botAddress, r.botAlias, r.botId, r.local))
2021-01-24 19:13:46 +01:00
.doOnSuccess(s -> logger.trace("Stated verticle"))
);
}
2021-09-09 20:14:00 +02:00
private static class OnSuccessfulStartRequestInfo {
public final AsyncTdDirectImpl td;
public final String botAddress;
public final String botAlias;
public final int botId;
public final boolean local;
2021-09-09 20:24:55 +02:00
public OnSuccessfulStartRequestInfo(AsyncTdDirectImpl td, String botAddress, String botAlias, int botId,
boolean local) {
2021-09-09 20:14:00 +02:00
this.td = td;
this.botAddress = botAddress;
this.botAlias = botAlias;
this.botId = botId;
this.local = local;
}
}
2021-09-09 20:24:55 +02:00
private Mono<Void> onSuccessfulStartRequest(AsyncTdDirectImpl td, String botAddress, String botAlias, int botId,
boolean local) {
return td
.initialize()
2021-09-30 18:22:50 +02:00
.then(this.pipe(td, botAddress, local))
.then(this.listen(td, botAddress, botId, local))
.doOnSuccess(s -> logger.info("Deploy and start of bot \"" + botAlias + "\": ✅ Succeeded"))
.doOnError(ex -> logger.error("Deploy and start of bot \"" + botAlias + "\": ❌ Failed", ex));
}
2021-09-30 18:22:50 +02:00
private Mono<Void> listen(AsyncTdDirectImpl td, String botAddress, int botId, boolean local) {
return Mono.<Void>create(registrationSink -> {
2021-01-24 19:13:46 +01:00
logger.trace("Preparing listeners");
2021-01-23 18:49:21 +01:00
MessageConsumer<ExecuteObject> executeConsumer = vertx.eventBus().consumer(botAddress + ".execute");
2021-09-30 18:22:50 +02:00
this.executeConsumer.set(executeConsumer);
2021-01-23 22:33:52 +01:00
Flux
.<Message<ExecuteObject>>create(sink -> {
executeConsumer.handler(sink::next);
executeConsumer.endHandler(h -> sink.complete());
})
.flatMap(msg -> {
var body = msg.body();
2021-01-26 12:34:59 +01:00
var request = overrideRequest(body.getRequest(), botId);
if (logger.isTraceEnabled()) {
logger.trace("Received execute request {}", request);
}
2021-01-23 22:33:52 +01:00
return td
2021-01-26 12:34:59 +01:00
.execute(request, body.isExecuteDirectly())
.single()
.timeout(Duration.ofSeconds(60 + 30))
.doOnSuccess(s -> logger.trace("Executed successfully. Request was {}", request))
2021-09-30 18:22:50 +02:00
.onErrorResume(ex -> Mono.fromRunnable(() -> msg.fail(500, ex.getLocalizedMessage())))
.flatMap(response -> Mono.fromCallable(() -> {
var replyOpts = new DeliveryOptions().setLocalOnly(local);
var replyValue = new TdResultMessage(response.result(), response.cause());
try {
logger.trace("Replying with success response. Request was {}", request);
msg.reply(replyValue, replyOpts);
return response;
} catch (Exception ex) {
2021-09-09 20:24:55 +02:00
logger.debug("Replying with error response: {}. Request was {}", ex.getLocalizedMessage(),
request);
msg.fail(500, ex.getLocalizedMessage());
throw ex;
}
}).subscribeOn(Schedulers.boundedElastic()));
2021-01-23 22:33:52 +01:00
})
.then()
2021-01-29 01:29:10 +01:00
.subscribeOn(Schedulers.parallel())
2021-01-24 19:13:46 +01:00
.subscribe(v -> {},
ex -> logger.error("Fatal error when processing an execute request."
+ " Can't process further requests since the subscription has been broken", ex),
2021-01-24 19:13:46 +01:00
() -> logger.trace("Finished handling execute requests")
);
2021-01-23 22:33:52 +01:00
MessageConsumer<byte[]> readBinlogConsumer = vertx.eventBus().consumer(botAddress + ".read-binlog");
2021-09-30 18:22:50 +02:00
this.readBinlogConsumer.set(readBinlogConsumer);
2021-01-24 03:15:45 +01:00
BinlogUtils
.readBinlogConsumer(vertx, readBinlogConsumer, botId, local)
2021-01-29 01:29:10 +01:00
.subscribeOn(Schedulers.parallel())
2021-01-24 03:15:45 +01:00
.subscribe(v -> {}, ex -> logger.error("Error when processing a read-binlog request", ex));
2021-01-23 22:33:52 +01:00
2021-09-09 20:24:55 +02:00
MessageConsumer<byte[]> readyToReceiveConsumer = vertx.eventBus().consumer(botAddress
+ ".ready-to-receive");
2021-09-30 18:22:50 +02:00
this.readyToReceiveConsumer.set(readyToReceiveConsumer);
2021-01-24 19:13:46 +01:00
2021-01-25 16:06:05 +01:00
// Pipe the data
2021-09-30 18:22:50 +02:00
Flux
2021-01-23 22:33:52 +01:00
.<Message<byte[]>>create(sink -> {
2021-01-24 03:15:45 +01:00
readyToReceiveConsumer.handler(sink::next);
readyToReceiveConsumer.endHandler(h -> sink.complete());
})
2021-05-22 14:47:12 +02:00
.take(1, true)
2021-01-24 19:13:46 +01:00
.single()
2021-01-26 16:29:45 +01:00
.doOnNext(s -> logger.trace("Received ready-to-receive request from client"))
2021-09-30 18:22:50 +02:00
.map(msg -> Tuples.of(msg, Objects.requireNonNull(pipeFlux.get(), "PipeFlux is empty")))
2021-01-24 19:13:46 +01:00
.doOnError(ex -> logger.error("Error when processing a ready-to-receive request", ex))
2021-01-26 16:29:45 +01:00
.doOnNext(s -> logger.trace("Replying to ready-to-receive request"))
2021-01-24 19:13:46 +01:00
.flatMapMany(tuple -> {
2021-09-09 20:24:55 +02:00
var opts = new DeliveryOptions().setLocalOnly(local)
.setSendTimeout(Duration.ofSeconds(10).toMillis());
2021-01-24 19:13:46 +01:00
2021-01-24 03:15:45 +01:00
tuple.getT1().reply(EMPTY, opts);
2021-01-24 19:13:46 +01:00
logger.trace("Replied to ready-to-receive");
2021-01-24 03:15:45 +01:00
2021-01-26 16:29:45 +01:00
logger.trace("Start piping data");
2021-01-24 03:15:45 +01:00
// Start piping the data
2021-09-30 18:22:50 +02:00
return tuple.getT2().doOnSubscribe(s -> logger.trace("Subscribed to updates pipe"));
2021-01-23 22:33:52 +01:00
})
.then()
2021-01-24 19:13:46 +01:00
.doOnSuccess(s -> logger.trace("Finished handling ready-to-receive requests (updates pipe ended)"))
2021-01-26 12:34:59 +01:00
.subscribeOn(Schedulers.boundedElastic())
2021-01-24 19:13:46 +01:00
// Don't handle errors here. Handle them in pipeFlux
.subscribe(v -> {});
2021-01-24 03:15:45 +01:00
MessageConsumer<byte[]> pingConsumer = vertx.eventBus().consumer(botAddress + ".ping");
2021-09-30 18:22:50 +02:00
this.pingConsumer.set(pingConsumer);
2021-01-24 03:15:45 +01:00
Flux
.<Message<byte[]>>create(sink -> {
pingConsumer.handler(sink::next);
pingConsumer.endHandler(h -> sink.complete());
})
.concatMap(msg -> Mono.fromCallable(() -> {
2021-09-09 20:24:55 +02:00
var opts = new DeliveryOptions().setLocalOnly(local)
.setSendTimeout(Duration.ofSeconds(10).toMillis());
2021-01-24 03:15:45 +01:00
msg.reply(EMPTY, opts);
2021-01-26 12:34:59 +01:00
return null;
}))
2021-01-24 03:15:45 +01:00
.then()
2021-01-26 12:34:59 +01:00
.subscribeOn(Schedulers.boundedElastic())
2021-01-24 19:13:46 +01:00
.subscribe(v -> {},
ex -> logger.error("Error when processing a ping request", ex),
() -> logger.trace("Finished handling ping requests")
);
2021-01-23 22:33:52 +01:00
executeConsumer
.rxCompletionHandler()
.andThen(readBinlogConsumer.rxCompletionHandler())
2021-01-24 03:15:45 +01:00
.andThen(readyToReceiveConsumer.rxCompletionHandler())
.andThen(pingConsumer.rxCompletionHandler())
2021-01-25 16:06:05 +01:00
.as(MonoUtils::toMono)
.doOnSuccess(s -> logger.trace("Finished preparing listeners"))
2021-01-29 01:29:10 +01:00
.subscribeOn(Schedulers.parallel())
2021-01-25 16:06:05 +01:00
.subscribe(v -> {}, registrationSink::error, registrationSink::success);
})
2021-02-14 22:59:20 +01:00
.subscribeOn(Schedulers.boundedElastic());
}
2021-01-23 22:33:52 +01:00
/**
* Override some requests
*/
private Function overrideRequest(Function request, int botId) {
2021-09-30 18:22:50 +02:00
if (request.getConstructor() == SetTdlibParameters.CONSTRUCTOR) {
// Fix session directory locations
var setTdlibParamsObj = (SetTdlibParameters) request;
setTdlibParamsObj.parameters.databaseDirectory = TDLibRemoteClient.getSessionDirectory(botId).toString();
setTdlibParamsObj.parameters.filesDirectory = TDLibRemoteClient.getMediaDirectory(botId).toString();
2021-01-23 22:33:52 +01:00
}
2021-09-30 18:22:50 +02:00
return request;
2021-01-23 22:33:52 +01:00
}
2021-01-23 18:49:21 +01:00
@Override
public Completable rxStop() {
2021-09-30 18:22:50 +02:00
return MonoUtils.toCompletable(Mono
.fromRunnable(() -> logger.info("Undeploy of bot \"" + botAlias.get() + "\": stopping"))
.then(Mono
.fromCallable(executeConsumer::get)
.flatMap(executeConsumer -> executeConsumer.rxUnregister().as(MonoUtils::toMono))
.doOnSuccess(s -> logger.trace("Unregistered execute consumer"))
)
.then(MonoUtils.fromBlockingEmpty(() -> {
var readBinlogConsumer = this.readBinlogConsumer.get();
if (readBinlogConsumer != null) {
Mono
// ReadBinLog will live for another 10 minutes.
// Since every consumer of ReadBinLog is identical, this should not pose a problem.
.delay(Duration.ofMinutes(10))
.then(readBinlogConsumer.rxUnregister().as(MonoUtils::toMono))
.subscribe();
}
}))
.then(Mono
.fromCallable(readyToReceiveConsumer::get)
.flatMap(ec -> ec.rxUnregister().as(MonoUtils::toMono))
)
.then(Mono
.fromCallable(pingConsumer::get)
.flatMap(ec -> ec.rxUnregister().as(MonoUtils::toMono))
2021-01-25 01:20:33 +01:00
)
2021-09-30 18:22:50 +02:00
.doOnError(ex -> logger.error("Undeploy of bot \"" + botAlias.get() + "\": stop failed", ex))
.doOnTerminate(() -> logger.info("Undeploy of bot \"" + botAlias.get() + "\": stopped"))
2021-01-25 01:20:33 +01:00
);
}
2021-09-30 18:22:50 +02:00
private Mono<Void> pipe(AsyncTdDirectImpl td, String botAddress, boolean local) {
2021-01-24 19:13:46 +01:00
logger.trace("Preparing to pipe requests");
2021-09-30 18:22:50 +02:00
Flux<TdResultList> updatesFlux = td
.receive(tdOptions)
2021-01-24 19:13:46 +01:00
.takeUntil(item -> {
2021-01-24 04:21:47 +01:00
if (item instanceof Update) {
var tdUpdate = (Update) item;
if (tdUpdate.getConstructor() == UpdateAuthorizationState.CONSTRUCTOR) {
2021-01-24 19:13:46 +01:00
var updateAuthorizationState = (UpdateAuthorizationState) tdUpdate;
return updateAuthorizationState.authorizationState.getConstructor()
== AuthorizationStateClosed.CONSTRUCTOR;
}
} else
return item instanceof Error;
2021-01-24 19:13:46 +01:00
return false;
})
.flatMap(update -> Mono.fromCallable(() -> {
2021-01-24 19:13:46 +01:00
if (update.getConstructor() == TdApi.Error.CONSTRUCTOR) {
var error = (Error) update;
2021-01-24 04:21:47 +01:00
throw new TdError(error.code, error.message);
2021-01-23 18:49:21 +01:00
} else {
2021-01-24 19:13:46 +01:00
return update;
}
}))
2021-02-15 02:29:49 +01:00
.limitRate(Math.max(1, tdOptions.getEventsSize()))
2021-09-29 11:39:38 +02:00
//.transform(normal -> new BufferTimeOutPublisher<>(normal,Math.max(1, tdOptions.getEventsSize()),
// local ? Duration.ofMillis(1) : Duration.ofMillis(100), false))
2021-03-10 12:35:56 +01:00
//.bufferTimeout(Math.max(1, tdOptions.getEventsSize()), local ? Duration.ofMillis(1) : Duration.ofMillis(100))
2021-09-29 11:39:38 +02:00
.map(List::of)
2021-02-15 02:29:49 +01:00
.limitRate(Math.max(1, tdOptions.getEventsSize()))
2021-01-23 18:49:21 +01:00
.map(TdResultList::new);
var fluxCodec = new TdResultListMessageCodec();
2021-01-23 18:49:21 +01:00
var opts = new DeliveryOptions()
.setLocalOnly(local)
.setSendTimeout(Duration.ofSeconds(30).toMillis())
.setCodecName(fluxCodec.name());
MessageProducer<TdResultList> updatesSender = vertx
.eventBus()
.sender(botAddress + ".updates", opts);
2021-01-24 03:15:45 +01:00
var pipeFlux = updatesFlux
.concatMap(updatesList -> updatesSender
2021-01-24 19:13:46 +01:00
.rxWrite(updatesList)
.as(MonoUtils::toMono)
.thenReturn(updatesList)
)
.concatMap(updatesList -> Flux
2021-01-24 19:13:46 +01:00
.fromIterable(updatesList.value())
.concatMap(item -> {
2021-01-24 19:13:46 +01:00
if (item instanceof Update) {
var tdUpdate = (Update) item;
if (tdUpdate.getConstructor() == UpdateAuthorizationState.CONSTRUCTOR) {
var tdUpdateAuthorizationState = (UpdateAuthorizationState) tdUpdate;
if (tdUpdateAuthorizationState.authorizationState.getConstructor()
== AuthorizationStateClosed.CONSTRUCTOR) {
2021-02-25 23:37:41 +01:00
logger.info("Undeploying after receiving AuthorizationStateClosed");
2021-01-24 19:13:46 +01:00
return rxStop().as(MonoUtils::toMono).thenReturn(item);
}
}
} else if (item instanceof Error) {
// An error in updates means that a fatal error occurred
2021-02-25 23:37:41 +01:00
logger.info("Undeploying after receiving a fatal error");
2021-01-24 19:13:46 +01:00
return rxStop().as(MonoUtils::toMono).thenReturn(item);
}
return Mono.just(item);
})
.then()
)
2021-01-23 18:49:21 +01:00
.doOnTerminate(() -> updatesSender.close(h -> {
if (h.failed()) {
logger.error("Failed to close \"updates\" message sender");
}
}))
.onErrorResume(ex -> {
2021-01-24 23:08:14 +01:00
boolean printDefaultException = true;
if (ex instanceof ReplyException) {
ReplyException replyException = (ReplyException) ex;
if (replyException.failureCode() == -1 && replyException.failureType() == ReplyFailure.NO_HANDLERS) {
2021-09-09 20:24:55 +02:00
logger.warn("Undeploying, the flux has been terminated because no more"
+ " handlers are available on the event bus. {}", replyException.getMessage());
2021-01-24 23:08:14 +01:00
printDefaultException = false;
}
} else if (ex instanceof ConnectException || ex instanceof java.nio.channels.ClosedChannelException) {
2021-09-09 20:24:55 +02:00
logger.warn("Undeploying, the flux has been terminated because the consumer"
+ " disconnected from the event bus. {}", ex.getMessage());
2021-01-24 23:08:14 +01:00
printDefaultException = false;
}
if (printDefaultException) {
logger.warn("Undeploying after a fatal error in a served flux", ex);
}
2021-01-23 18:49:21 +01:00
return td.execute(new TdApi.Close(), false)
.doOnError(ex2 -> logger.error("Unexpected error", ex2))
2021-01-24 23:08:14 +01:00
.doOnSuccess(s -> logger.debug("Emergency Close() signal has been sent successfully"))
.then(rxStop().as(MonoUtils::toMono));
2021-01-24 03:15:45 +01:00
});
2021-01-24 23:08:14 +01:00
2021-09-30 18:22:50 +02:00
return MonoUtils.fromBlockingEmpty(() -> {
this.pipeFlux.set(pipeFlux);
logger.trace("Prepared piping requests successfully");
});
}
}