Fix broken completable futures

This commit is contained in:
Andrea Cavalli 2022-01-23 12:58:10 +01:00
parent ee6a0534a8
commit 68e904681d
4 changed files with 70 additions and 19 deletions

View File

@ -1,9 +1,9 @@
package it.tdlight.reactiveapi; package it.tdlight.reactiveapi;
import static it.tdlight.reactiveapi.AtomixUtils.fromCf;
import static java.util.Collections.unmodifiableSet; import static java.util.Collections.unmodifiableSet;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import static java.util.concurrent.CompletableFuture.failedFuture; import static java.util.concurrent.CompletableFuture.failedFuture;
import static reactor.core.publisher.Mono.fromCompletionStage;
import com.google.common.primitives.Longs; import com.google.common.primitives.Longs;
import io.atomix.cluster.messaging.MessagingException; import io.atomix.cluster.messaging.MessagingException;
@ -163,7 +163,7 @@ public class AtomixReactiveApi implements ReactiveApi {
var removeObsoleteDiskSessions = diskChangesMono var removeObsoleteDiskSessions = diskChangesMono
.flatMapIterable(diskChanges -> diskChanges.removedIds) .flatMapIterable(diskChanges -> diskChanges.removedIds)
.concatMap(removedIds -> fromCompletionStage(() -> destroySession(removedIds, nodeId))) .concatMap(removedIds -> fromCf(() -> destroySession(removedIds, nodeId)))
.then(); .then();
var addedDiskSessionsFlux = diskChangesMono var addedDiskSessionsFlux = diskChangesMono
@ -200,7 +200,7 @@ public class AtomixReactiveApi implements ReactiveApi {
// Listen for create-session signals // Listen for create-session signals
Mono<Subscription> createSessionSubscriptionMono; Mono<Subscription> createSessionSubscriptionMono;
if (nodeId != null) { if (nodeId != null) {
createSessionSubscriptionMono = fromCompletionStage(() -> atomix createSessionSubscriptionMono = fromCf(() -> atomix
.getEventService() .getEventService()
.subscribe("create-session", CreateSessionRequest::deserializeBytes, req -> { .subscribe("create-session", CreateSessionRequest::deserializeBytes, req -> {
if (req instanceof LoadSessionFromDiskRequest) { if (req instanceof LoadSessionFromDiskRequest) {
@ -216,7 +216,7 @@ public class AtomixReactiveApi implements ReactiveApi {
// Listen for revive-session signals // Listen for revive-session signals
Mono<Subscription> reviveSessionSubscriptionMono; Mono<Subscription> reviveSessionSubscriptionMono;
if (nodeId != null) { if (nodeId != null) {
reviveSessionSubscriptionMono = fromCompletionStage(() -> atomix reviveSessionSubscriptionMono = fromCf(() -> atomix
.getEventService() .getEventService()
.subscribe("revive-session", (Long userId) -> this.getLocalDiskSession(userId).flatMap(sessionAndId -> { .subscribe("revive-session", (Long userId) -> this.getLocalDiskSession(userId).flatMap(sessionAndId -> {
var diskSession = sessionAndId.diskSession(); var diskSession = sessionAndId.diskSession();
@ -325,8 +325,8 @@ public class AtomixReactiveApi implements ReactiveApi {
} }
// Register the session instance to the distributed nodes map // Register the session instance to the distributed nodes map
return Mono return AtomixUtils
.fromCompletionStage(() -> userIdToNodeId.put(userId, nodeId).thenApply(Optional::ofNullable)) .fromCf(() -> userIdToNodeId.put(userId, nodeId).thenApply(Optional::ofNullable))
.flatMap(prevDistributed -> { .flatMap(prevDistributed -> {
if (prevDistributed.isPresent() && prevDistributed.get().value() != null && if (prevDistributed.isPresent() && prevDistributed.get().value() != null &&
!Objects.equals(this.nodeId, prevDistributed.get().value())) { !Objects.equals(this.nodeId, prevDistributed.get().value())) {
@ -388,10 +388,10 @@ public class AtomixReactiveApi implements ReactiveApi {
// Lock sessions creation // Lock sessions creation
return Mono return Mono
.usingWhen(Mono.fromCompletionStage(sessionModificationLock::lock), .usingWhen(AtomixUtils.fromCf(sessionModificationLock::lock),
lockVersion -> unlockedSessionCreationMono, lockVersion -> unlockedSessionCreationMono,
lockVersion -> Mono lockVersion -> AtomixUtils
.fromCompletionStage(sessionModificationLock::unlock) .fromCf(sessionModificationLock::unlock)
.doOnTerminate(() -> LOG.trace("Released session modification lock for session request: {}", req)) .doOnTerminate(() -> LOG.trace("Released session modification lock for session request: {}", req))
) )
.doOnNext(resp -> LOG.debug("Handled session request {}, the response is: {}", req, resp)) .doOnNext(resp -> LOG.debug("Handled session request {}, the response is: {}", req, resp))
@ -410,7 +410,7 @@ public class AtomixReactiveApi implements ReactiveApi {
} }
private Mono<Long> nextFreeLiveId() { private Mono<Long> nextFreeLiveId() {
return Mono.fromCompletionStage(nextSessionLiveId::nextId); return fromCf(nextSessionLiveId::nextId);
} }
public Atomix getAtomix() { public Atomix getAtomix() {
@ -425,8 +425,8 @@ public class AtomixReactiveApi implements ReactiveApi {
public Mono<Map<Long, String>> getAllUsers() { public Mono<Map<Long, String>> getAllUsers() {
return Flux.defer(() -> { return Flux.defer(() -> {
var it = userIdToNodeId.entrySet().iterator(); var it = userIdToNodeId.entrySet().iterator();
var hasNextMono = fromCompletionStage(it::hasNext); var hasNextMono = fromCf(it::hasNext);
var strictNextMono = fromCompletionStage(it::next) var strictNextMono = fromCf(it::next)
.map(elem -> Map.entry(elem.getKey(), elem.getValue().value())); .map(elem -> Map.entry(elem.getKey(), elem.getValue().value()));
var nextOrNothingMono = hasNextMono.flatMap(hasNext -> { var nextOrNothingMono = hasNextMono.flatMap(hasNext -> {
@ -459,8 +459,8 @@ public class AtomixReactiveApi implements ReactiveApi {
@Override @Override
public Mono<Long> resolveUserLiveId(long userId) { public Mono<Long> resolveUserLiveId(long userId) {
return Mono return AtomixUtils
.fromCompletionStage(() -> atomix .fromCf(() -> atomix
.getEventService() .getEventService()
.send(SubjectNaming.getDynamicIdResolveSubject(userId), .send(SubjectNaming.getDynamicIdResolveSubject(userId),
userId, userId,
@ -494,7 +494,7 @@ public class AtomixReactiveApi implements ReactiveApi {
@Override @Override
public Mono<Void> close() { public Mono<Void> close() {
var atomixStopper = Mono.fromCompletionStage(this.atomix::stop).timeout(Duration.ofSeconds(8), Mono.empty()); var atomixStopper = fromCf(this.atomix::stop).timeout(Duration.ofSeconds(8), Mono.empty());
var kafkaStopper = Mono.fromRunnable(kafkaProducer::close).subscribeOn(Schedulers.boundedElastic()); var kafkaStopper = Mono.fromRunnable(kafkaProducer::close).subscribeOn(Schedulers.boundedElastic());
return Mono.when(atomixStopper, kafkaStopper); return Mono.when(atomixStopper, kafkaStopper);
} }

View File

@ -1,5 +1,7 @@
package it.tdlight.reactiveapi; package it.tdlight.reactiveapi;
import static it.tdlight.reactiveapi.AtomixUtils.fromCf;
import io.atomix.cluster.messaging.ClusterEventService; import io.atomix.cluster.messaging.ClusterEventService;
import io.atomix.cluster.messaging.MessagingException; import io.atomix.cluster.messaging.MessagingException;
import it.tdlight.jni.TdApi; import it.tdlight.jni.TdApi;
@ -8,6 +10,8 @@ import it.tdlight.reactiveapi.Event.Request;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.TimeoutException;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers; import reactor.core.scheduler.Schedulers;
@ -36,7 +40,7 @@ public class AtomixReactiveApiMultiClient implements ReactiveApiMultiClient, Aut
@Override @Override
public <T extends TdApi.Object> Mono<T> request(long userId, long liveId, TdApi.Function<T> request, Instant timeout) { public <T extends TdApi.Object> Mono<T> request(long userId, long liveId, TdApi.Function<T> request, Instant timeout) {
return Mono.fromCompletionStage(() -> { return fromCf(() -> {
if (closed) { if (closed) {
return CompletableFuture.failedFuture(new TdError(500, "Session is closed")); return CompletableFuture.failedFuture(new TdError(500, "Session is closed"));
} }
@ -56,6 +60,10 @@ public class AtomixReactiveApiMultiClient implements ReactiveApiMultiClient, Aut
}).onErrorMap(ex -> { }).onErrorMap(ex -> {
if (ex instanceof MessagingException.NoRemoteHandler) { if (ex instanceof MessagingException.NoRemoteHandler) {
return new TdError(404, "Bot #IDU" + userId + " (live id: " + liveId + ") is not found on the cluster"); return new TdError(404, "Bot #IDU" + userId + " (live id: " + liveId + ") is not found on the cluster");
} else if (ex instanceof CompletionException && ex.getCause() instanceof TimeoutException) {
return new TdError(408, "Request Timeout", ex);
} else if (ex instanceof TimeoutException) {
return new TdError(408, "Request Timeout", ex);
} else { } else {
return ex; return ex;
} }

View File

@ -0,0 +1,29 @@
package it.tdlight.reactiveapi;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.function.Supplier;
import reactor.core.publisher.Mono;
public class AtomixUtils {
public static <T> Mono<T> fromCf(Supplier<? extends CompletableFuture<? extends T>> completableFutureSupplier) {
return Mono.create(sink -> {
var cf = completableFutureSupplier.get();
cf.whenComplete((result, ex) -> {
if (ex != null) {
if (ex instanceof CompletionException) {
sink.error(ex.getCause());
} else {
sink.error(ex);
}
} else if (result != null) {
sink.success(result);
} else {
sink.success();
}
});
sink.onCancel(() -> cf.cancel(true));
});
}
}

View File

@ -21,6 +21,7 @@ import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletionException;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import org.apache.commons.lang3.SerializationException; import org.apache.commons.lang3.SerializationException;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -62,8 +63,8 @@ abstract class BaseAtomixReactiveApiClient implements ReactiveApiClient, AutoClo
.take(1, true) .take(1, true)
.singleOrEmpty() .singleOrEmpty()
.switchIfEmpty(emptyIdErrorMono) .switchIfEmpty(emptyIdErrorMono)
.flatMap(liveId -> Mono .flatMap(liveId -> AtomixUtils
.fromCompletionStage(() -> eventService.send("session-" + liveId + "-requests", .fromCf(() -> eventService.send("session-" + liveId + "-requests",
new Request<>(liveId, request, timeout), new Request<>(liveId, request, timeout),
LiveAtomixReactiveApiClient::serializeRequest, LiveAtomixReactiveApiClient::serializeRequest,
LiveAtomixReactiveApiClient::deserializeResponse, LiveAtomixReactiveApiClient::deserializeResponse,
@ -73,6 +74,8 @@ abstract class BaseAtomixReactiveApiClient implements ReactiveApiClient, AutoClo
.onErrorMap(ex -> { .onErrorMap(ex -> {
if (ex instanceof MessagingException.NoRemoteHandler) { if (ex instanceof MessagingException.NoRemoteHandler) {
return new TdError(404, "Bot #IDU" + this.userId + " (liveId: " + liveId + ") is not found on the cluster"); return new TdError(404, "Bot #IDU" + this.userId + " (liveId: " + liveId + ") is not found on the cluster");
} else if (ex instanceof CompletionException && ex.getCause() instanceof TimeoutException) {
return new TdError(408, "Request Timeout", ex);
} else if (ex instanceof TimeoutException) { } else if (ex instanceof TimeoutException) {
return new TdError(408, "Request Timeout", ex); return new TdError(408, "Request Timeout", ex);
} else { } else {
@ -80,13 +83,24 @@ abstract class BaseAtomixReactiveApiClient implements ReactiveApiClient, AutoClo
} }
}) })
) )
.handle((item, sink) -> { .<T>handle((item, sink) -> {
if (item instanceof TdApi.Error error) { if (item instanceof TdApi.Error error) {
sink.error(new TdError(error.code, error.message)); sink.error(new TdError(error.code, error.message));
} else { } else {
//noinspection unchecked //noinspection unchecked
sink.next((T) item); sink.next((T) item);
} }
})
.onErrorMap(ex -> {
if (ex instanceof MessagingException.NoRemoteHandler) {
return new TdError(404, "Bot #IDU" + this.userId + " is not found on the cluster");
} else if (ex instanceof CompletionException && ex.getCause() instanceof TimeoutException) {
return new TdError(408, "Request Timeout", ex);
} else if (ex instanceof TimeoutException) {
return new TdError(408, "Request Timeout", ex);
} else {
return ex;
}
}); });
} }