Bugfixes
This commit is contained in:
parent
e1e18d61cf
commit
8b71b9e2cb
13
README.md
13
README.md
@ -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)
|
2
pom.xml
2
pom.xml
@ -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>
|
||||||
|
@ -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(
|
||||||
|
@ -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 -> {
|
||||||
|
client.initialize((TdApi.Object object) -> {
|
||||||
|
updatesSink.next(object);
|
||||||
// Close the emitter if receive closed state
|
// Close the emitter if receive closed state
|
||||||
if (object.getConstructor() == UpdateAuthorizationState.CONSTRUCTOR
|
if (object.getConstructor() == UpdateAuthorizationState.CONSTRUCTOR
|
||||||
&& ((UpdateAuthorizationState) object).authorizationState.getConstructor()
|
&& ((UpdateAuthorizationState) object).authorizationState.getConstructor()
|
||||||
== AuthorizationStateClosed.CONSTRUCTOR) {
|
== AuthorizationStateClosed.CONSTRUCTOR) {
|
||||||
logger.debug("Received closed status from tdlib");
|
logger.debug("Received closed status from tdlib");
|
||||||
closedFromTd.tryEmitValue(true);
|
closedFromTd.tryEmitValue(true);
|
||||||
emitter.complete();
|
updatesSink.complete();
|
||||||
}
|
}
|
||||||
}, emitter::error, emitter::error);
|
}, updatesSink::error, updatesSink::error);
|
||||||
try {
|
|
||||||
this.td.tryEmitValue(client).orThrow();
|
if (td.tryEmitValue(client).isFailure()) {
|
||||||
} catch (Exception ex) {
|
updatesSink.error(new TdError(500, "Failed to emit td client"));
|
||||||
emitter.error(ex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send close if the stream is disposed before tdlib is closed
|
// Send close if the stream is disposed before tdlib is closed
|
||||||
emitter.onDispose(() -> {
|
updatesSink.onDispose(() -> {
|
||||||
// Try to emit false, so that if it has not been closed from tdlib, now it is explicitly false.
|
// Try to emit false, so that if it has not been closed from tdlib, now it is explicitly false.
|
||||||
closedFromTd.tryEmitValue(false);
|
closedFromTd.tryEmitValue(false);
|
||||||
|
|
||||||
closedFromTd.asMono()
|
closedFromTd.asMono().filter(isClosedFromTd -> !isClosedFromTd).doOnNext(x -> {
|
||||||
.filter(isClosedFromTd -> !isClosedFromTd)
|
|
||||||
.doOnNext(x -> {
|
|
||||||
logger.warn("The stream has been disposed without closing tdlib. Sending TdApi.Close()...");
|
logger.warn("The stream has been disposed without closing tdlib. Sending TdApi.Close()...");
|
||||||
client.send(new Close(),
|
client.send(new Close(),
|
||||||
result -> logger.warn("Close result: {}", result),
|
result -> logger.warn("Close result: {}", result),
|
||||||
ex -> logger.error("Error when disposing td client", ex)
|
ex -> logger.error("Error when disposing td client", ex)
|
||||||
);
|
);
|
||||||
|
}).subscribeOn(Schedulers.single()).subscribe();
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.subscribeOn(Schedulers.single())
|
.subscribeOn(Schedulers.boundedElastic())
|
||||||
.subscribe();
|
);
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -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"
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 -> {
|
||||||
|
@ -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
|
||||||
|
.fromCallable(() -> {
|
||||||
if (resp.body() == null) {
|
if (resp.body() == null) {
|
||||||
throw ResponseError.newResponseError(request, botAlias, new TdError(500, "Response is empty"));
|
throw ResponseError.newResponseError(request, botAlias, new TdError(500, "Response is empty"));
|
||||||
} else {
|
} else {
|
||||||
return resp.body().toTdResult();
|
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"));
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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,15 +60,19 @@ 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
|
||||||
|
.fromBlockingMaybe(() -> {
|
||||||
|
logger.trace("Stating verticle");
|
||||||
var botId = config().getInteger("botId");
|
var botId = config().getInteger("botId");
|
||||||
if (botId == null || botId <= 0) {
|
if (botId == null || botId <= 0) {
|
||||||
throw new IllegalArgumentException("botId is not set!");
|
throw new IllegalArgumentException("botId is not set!");
|
||||||
@ -88,17 +95,20 @@ public class AsyncTdMiddleEventBusServer extends AbstractVerticle {
|
|||||||
if (local == null) {
|
if (local == null) {
|
||||||
throw new IllegalArgumentException("local is not set!");
|
throw new IllegalArgumentException("local is not set!");
|
||||||
}
|
}
|
||||||
if (this.local.tryEmitValue(local).isFailure()) {
|
var implementationDetails = config().getJsonObject("implementationDetails");
|
||||||
throw new IllegalStateException("Failed to set local");
|
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,9 +300,58 @@ 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) {
|
||||||
|
var tdUpdate = (Update) item;
|
||||||
|
if (tdUpdate.getConstructor() == UpdateAuthorizationState.CONSTRUCTOR) {
|
||||||
|
var updateAuthorizationState = (UpdateAuthorizationState) tdUpdate;
|
||||||
|
if (updateAuthorizationState.authorizationState.getConstructor() == AuthorizationStateClosed.CONSTRUCTOR) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (item instanceof Error) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
})
|
||||||
|
.flatMap(update -> Mono.fromCallable(() -> {
|
||||||
|
if (update.getConstructor() == TdApi.Error.CONSTRUCTOR) {
|
||||||
|
var error = (Error) update;
|
||||||
|
throw new TdError(error.code, error.message);
|
||||||
|
} else {
|
||||||
|
return update;
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.bufferTimeout(tdOptions.getEventsSize(), local ? Duration.ofMillis(1) : Duration.ofMillis(100))
|
||||||
|
.doFinally(signalType -> terminatePingOverPipeFlux.tryEmitEmpty())
|
||||||
|
.mergeWith(Flux
|
||||||
|
.interval(Duration.ofSeconds(5))
|
||||||
|
.map(l -> Collections.<TdApi.Object>emptyList())
|
||||||
|
.takeUntilOther(terminatePingOverPipeFlux.asMono()))
|
||||||
|
.map(TdResultList::new);
|
||||||
|
|
||||||
|
var fluxCodec = new TdResultListMessageCodec();
|
||||||
|
var opts = new DeliveryOptions()
|
||||||
|
.setLocalOnly(local)
|
||||||
|
.setSendTimeout(Duration.ofSeconds(30).toMillis())
|
||||||
|
.setCodecName(fluxCodec.name());
|
||||||
|
|
||||||
|
MessageProducer<TdResultList> updatesSender = vertx
|
||||||
|
.eventBus()
|
||||||
|
.sender(botAddress + ".updates", opts);
|
||||||
|
|
||||||
|
var pipeFlux = updatesFlux
|
||||||
|
.flatMap(updatesList -> updatesSender
|
||||||
|
.rxWrite(updatesList)
|
||||||
|
.as(MonoUtils::toMono)
|
||||||
|
.thenReturn(updatesList)
|
||||||
|
)
|
||||||
|
.flatMap(updatesList -> Flux
|
||||||
|
.fromIterable(updatesList.value())
|
||||||
|
.flatMap(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) {
|
||||||
@ -287,32 +368,9 @@ public class AsyncTdMiddleEventBusServer extends AbstractVerticle {
|
|||||||
return rxStop().as(MonoUtils::toMono).thenReturn(item);
|
return rxStop().as(MonoUtils::toMono).thenReturn(item);
|
||||||
}
|
}
|
||||||
return Mono.just(item);
|
return Mono.just(item);
|
||||||
}))
|
})
|
||||||
.flatMap(item -> Mono.fromCallable(() -> {
|
.then()
|
||||||
if (item.getConstructor() == TdApi.Error.CONSTRUCTOR) {
|
)
|
||||||
var error = (Error) item;
|
|
||||||
throw new TdError(error.code, error.message);
|
|
||||||
} else {
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
.bufferTimeout(tdOptions.getEventsSize(), local ? Duration.ofMillis(1) : Duration.ofMillis(100))
|
|
||||||
.windowTimeout(1, Duration.ofSeconds(5))
|
|
||||||
.flatMap(w -> w.defaultIfEmpty(Collections.emptyList()))
|
|
||||||
.map(TdResultList::new);
|
|
||||||
|
|
||||||
var fluxCodec = new TdResultListMessageCodec();
|
|
||||||
var opts = new DeliveryOptions()
|
|
||||||
.setLocalOnly(local)
|
|
||||||
.setSendTimeout(Duration.ofSeconds(30).toMillis())
|
|
||||||
.setCodecName(fluxCodec.name());
|
|
||||||
|
|
||||||
MessageProducer<TdResultList> updatesSender = vertx
|
|
||||||
.eventBus()
|
|
||||||
.sender(botAddress + ".updates", opts);
|
|
||||||
|
|
||||||
var pipeFlux = updatesFlux
|
|
||||||
.flatMap(update -> updatesSender.rxWrite(update).as(MonoUtils::toMono).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"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
|
@ -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,8 +471,10 @@ 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()) {
|
||||||
|
if (amount > 0) {
|
||||||
readCoreSubscription.request(amount);
|
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,8 +626,10 @@ 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()) {
|
||||||
|
if (amount > 0) {
|
||||||
readCoreSubscription.request(amount);
|
readCoreSubscription.request(amount);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user