Recommended way to execute async tasks with RXJava - java

I'm new to RxJava and I'm trying to understand the best/recommended way to perform long running tasks asynchronously (e.g. network requests). I've read through a lot of examples online but would appreciate some feedback.
The following code works (it prints 'one', 'two', then 'User: x' ... etc) but should I really be creating/managing Threads manually?
Thanks in advance!
public void start() throws Exception {
System.out.println("one");
observeUsers()
.flatMap(users -> Observable.from(users))
.subscribe(user -> System.out.println(String.format("User: %s", user.toString()));
System.out.println("two");
}
Observable<List<User>> observeUsers() {
return Observable.<List<User>>create(s -> {
Thread thread = new Thread(() -> getUsers(s));
thread.start();
});
}
void getUsers(final Subscriber s) {
s.onNext(userService.getUsers());
s.onCompleted();
}
// userService.getUsers() fetches users from a web service.

Instead of managing your own thread try using the defer() operator. Meaning replace observeUsers() with Observable.defer(() -> Observable.just(userService.getUsers())). Then you can use the RxJava Schedulers to control what threads are used during subscription and observation. Here's your code modified with the above suggestions.
Observable.defer(() -> Observable.just(userService.getUsers()))
.flatMap(users -> Observable.from(users))
.subscribeOn(Schedulers.newThread())
.observeOn(Schedulers.trampoline())
.subscribe(user -> System.out.println(String.format("User: %s", user.toString()));

Related

Starting a new thread and returning the main thread Spring webflux methods subscribeOn

I have a Spring Webflux application in which I want to spawn a new Thread for a time consuming operation and as soon as the new thread is spawned return the main thread so that client is not kept waiting
Please note the time consuming method is not a WebClient call but a service layer function call to download files from a content storage. I have defined a CallableTask class as below
public class DocumentProcessorCallableTask<T> {
public Mono<T> execute(Callable<T> task) {
return Mono.defer(
() -> Mono.fromCallable(task)
.doOnError(throwable ->
Mono.error(new
ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR))));
}
}
And the method in which I want to spawn a new thread is as below. The method named downloadAndUploadSaveLandsDocuments is the one which makes a call to the content storage to get documents
private Mono<MarsTxnResponse> subscribeOnScheduler(MarsTxnResponse rsp, Long clientId) {
return new DocumentProcessorCallableTask<Mono<MarsTxnResponse>>()
.execute(() -> downloadAndUploadSaveLandsDocuments(rsp, clientId))
.subscribeOn(Schedulers.boundedElastic())
.thenReturn(rsp);
The problem is that a new thread is created but the webflux code inside downloadAndUploadSaveLandsDocuments is not executed.
It gets executed when I change the above code to below BUT the main thread waits until the newly spawned thread completes which I dont want.
Does subscribeOn and publishOn work only from the context of a Webclient call?
return new DocumentProcessorCallableTask<Mono<MarsTxnResponse>>()
.execute(() -> downloadAndUploadSaveLandsDocuments(rsp, clientId))
.flatMap(marsTxnResponseMono -> marsTxnResponseMono)
.subscribeOn(Schedulers.boundedElastic());
The method which calls the content storage is as below.It does not enter Flux.fromIterable in the first case but does enter it in the second case.
public Mono<MarsTxnResponse> downloadAndUploadSaveLandsDocuments(
MarsTxnResponse rsp,
Long clientId) {
return Flux.fromIterable(rsp.getExpandedWebLtLandList())
.flatMap(webLtLandFromRequest -> {
return downloadDocuments(rsp, webLtLandFromRequest, clientId);
}).collectList()
.flatMap(objects -> {
return documentAsyncUploadService.uploadFilesIfExists(rsp);
});
}
I am using spring-boot 2.4.13 and spring-boot-starter-webflux version 2.4.13
Finally the code which worked
return Mono.just(rsp)
.flatMap(
marsTxnResponse -> {
new DocumentProcessorCallableTask<Mono<MarsTxnResponse>>()
.execute(() -> downloadAndUploadSaveLandsDocuments(rsp, clientId))
.flatMap(marsTxnResponseMono -> marsTxnResponseMono)
.subscribeOn(Schedulers.boundedElastic())
.subscribe();
return Mono.just(rsp);
});
The most important thing to add was subscribe() after subscribeOn(Schedulers.boundedElastic())

Bring Uni event back to the caller thread

Given a subscription in a Quarkus application:
Uni.createFrom.Item(1)
.chain { it -> processA(it) }
.emitOn(Infrastructure.getDefaultWorkerPool())
.chain { it -> processB(it) }
.chain { it -> processC(it) }
.subscribe().with{ it -> processD(it) }
If I understand correctly, processA will be executed on the caller thread (so if it is in a Verticle, it should be on a IO thread), processB and processC will be executed on the worker threads, processD will be on the caller/IO thread again.
How can I make processC being called on the IO thread, while processB still on the worker thread? Is there a simple method to bring the event back to the caller thread?
Edit: Right now I am using the following workaround:
Uni.createFrom.Item(1)
.chain { it -> processA(it) }
.chain { i -> Uni.createFrom().future { Infrastructure.getDefaultWorkerPool().submit{ processB(i) } } }
.chain { it -> processC(it) }
.subscribe().with{ it -> processD(it) }
You would need to capture the Vert.x Context and switch back to it.
Something like this would work (using Java, as my Kotlin is not great):
Context context = Vertx.currentContext();
Uni.createFrom().item(1)
.chain(it -> processA(it))
.emitOn(Infrastructure.getDefaultWorkerPool())
.chain(it -> processB(it))
.emitOn(runnable -> context.runOnContext(ignored -> runnable.run())
.chain(it -> process(it))
.subscribe().with(it -> processD(it));

Mono vs CompletableFuture

CompletableFuture executes a task on a separate thread ( uses a thread-pool ) and provides a callback function. Let's say I have an API call in a CompletableFuture. Is that an API call blocking? Would the thread be blocked till it does not get a response from the API? ( I know main thread/tomcat thread will be non-blocking, but what about the thread on which CompletableFuture task is executing? )
Mono is completely non-blocking, as far as I know.
Please shed some light on this and correct me if I am wrong.
CompletableFuture is Async. But is it non-blocking?
One which is true about CompletableFuture is that it is truly async, it allows you to run your task asynchronously from the caller thread and the API such as thenXXX allows you to process the result when it becomes available. On the other hand, CompletableFuture is not always non-blocking. For example, when you run the following code, it will be executed asynchronously on the default ForkJoinPool:
CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
}
return 1;
});
It is clear that the Thread in ForkJoinPool that executes the task will be blocked eventually which means that we can't guarantee that the call will be non-blocking.
On the other hand, CompletableFuture exposes API which allows you to make it truly non-blocking.
For example, you can always do the following:
public CompletableFuture myNonBlockingHttpCall(Object someData) {
var uncompletedFuture = new CompletableFuture(); // creates uncompleted future
myAsyncHttpClient.execute(someData, (result, exception -> {
if(exception != null) {
uncompletedFuture.completeExceptionally(exception);
return;
}
uncompletedFuture.complete(result);
})
return uncompletedFuture;
}
As you can see, the API of CompletableFuture future provides you with the complete and completeExceptionally methods that complete your execution whenever it is needed without blocking any thread.
Mono vs CompletableFuture
In the previous section, we got an overview of CF behavior, but what is the central difference between CompletableFuture and Mono?
It worth to mention that we can do blocking Mono as well. No one prevents us from writing the following:
Mono.fromCallable(() -> {
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
}
return 1;
})
Of course, once we subscribe to the future, the caller thread will be blocked. But we can always work around that by providing an additional subscribeOn operator. Nevertheless, the broader API of Mono is not the key feature.
In order to understand the main difference between CompletableFuture and Mono, lets back to previously mentioned myNonBlockingHttpCall method implementation.
public CompletableFuture myUpperLevelBusinessLogic() {
var future = myNonBlockingHttpCall();
// ... some code
if (something) {
// oh we don't really need anything, let's just throw an exception
var errorFuture = new CompletableFuture();
errorFuture.completeExceptionally(new RuntimeException());
return errorFuture;
}
return future;
}
In the case of CompletableFuture, once the method is called, it will eagerly execute HTTP call to another service/resource. Even though we will not really need the result of the execution after verifying some pre/post conditions, it starts the execution, and additional CPU/DB-Connections/What-Ever-Machine-Resources will be allocated for this work.
In contrast, the Mono type is lazy by definition:
public Mono myNonBlockingHttpCallWithMono(Object someData) {
return Mono.create(sink -> {
myAsyncHttpClient.execute(someData, (result, exception -> {
if(exception != null) {
sink.error(exception);
return;
}
sink.success(result);
})
});
}
public Mono myUpperLevelBusinessLogic() {
var mono = myNonBlockingHttpCallWithMono();
// ... some code
if (something) {
// oh we don't really need anything, let's just throw an exception
return Mono.error(new RuntimeException());
}
return mono;
}
In this case, nothing will happen until the final mono is subscribed. Thus, only when Mono returned by the myNonBlockingHttpCallWithMono method, will be subscribed, the logic provided to Mono.create(Consumer) will be executed.
And we can go even further. We can make our execution much lazier. As you might know, Mono extends Publisher from the Reactive Streams specification. The screaming feature of Reactive Streams is backpressure support. Thus, using the Mono API we can do execution only when the data is really needed, and our subscriber is ready to consume them:
Mono.create(sink -> {
AtomicBoolean once = new AtomicBoolean();
sink.onRequest(__ -> {
if(!once.get() && once.compareAndSet(false, true) {
myAsyncHttpClient.execute(someData, (result, exception -> {
if(exception != null) {
sink.error(exception);
return;
}
sink.success(result);
});
}
});
});
In this example, we execute data only when subscriber called Subscription#request so by doing that it declared its readiness to receive data.
Summary
CompletableFuture is async and can be non-blocking
CompletableFuture is eager. You can't postpone the execution. But you can cancel them (which is better than nothing)
Mono is async/non-blocking and can easily execute any call on different Thread by composing the main Mono with different operators.
Mono is truly lazy and allows postponing execution startup by the subscriber presence and its readiness to consume data.
Building up on Oleh's answer, a possible lazy solution for CompletableFuture would be
public CompletableFuture myNonBlockingHttpCall(CompletableFuture<ExecutorService> dispatch, Object someData) {
var uncompletedFuture = new CompletableFuture(); // creates uncompleted future
dispatch.thenAccept(x -> x.submit(() -> {
myAsyncHttpClient.execute(someData, (result, exception -> {
if(exception != null) {
uncompletedFuture.completeExceptionally(exception);
return;
}
uncompletedFuture.complete(result);
})
}));
return uncompletedFuture;
}
Then, later on you simply do
dispatch.complete(executor);
That would make CompletableFuture equivalent to Mono, but without backpressure, I guess.

Equivalent of VertX CompositeFuture in RxJava

The VertX example for when you need to query multiple asynchronous resources and use them all in a single operation is:
Future<HttpServer> httpServerFuture = Future.future();
httpServer.listen(httpServerFuture.completer());
Future<NetServer> netServerFuture = Future.future();
netServer.listen(netServerFuture.completer());
CompositeFuture.all(httpServerFuture, netServerFuture).setHandler(ar -> {
if (ar.succeeded()) {
// All servers started
} else {
// At least one server failed
}
});
We need to query two different databases and then use the results in business logic, but the flow is equivalent.
What's the VertX/RxJava equivalent?
Currently people are doing this by nesting a new .flatMap() call every time they need a new variable. I'm left feeling there must be a better way...
We don't actually need the queries to be concurrent but we need to cache both results and pass them to the business logic at the same time some how.
there are many ways to do this, but i've tried to pick an approach that tacks closely to your sample:
#Override
public void start(Future<Void> startFuture) throws Exception {
final HttpServer httpServer = vertx.createHttpServer();
final Completable initializeHttpServer = httpServer.rxListen().toCompletable();
final NetServer netServer = vertx.createNetServer();
final Completable initializeNetServer = netServer.rxListen().toCompletable();
initializeHttpServer.andThen(initializeNetServer)
.subscribe(
() -> { /* All servers started */ },
error -> { /* At least one server failed */ }
);
}
the rxListen() invocations are converted into Completable instances, which are then run serially upon subscription.
the subscriber's onComplete callback will be invoked when both servers are done binding to their respective ports, or...
the onError callback will be invoked if an exception occurs
(also, fwiw, "nesting" flatMap operations for something as trivial as this shouldn't be necessary. "chaining" such operations, however, would be idiomatic usage).
hope that helps!
--UPDATE--
having read the question more carefully, i now see that you were actually asking about how to handle the results of two discrete asynchronous operations.
an alternative to flatMap'ing your way to combining the results would be to use the zip operator, like so:
#Override
public void start(Future<Void> startFuture) throws Exception {
final Single<String> dbQuery1 = Single.fromCallable(() -> { return "db-query-result-1"; });
final Single<String> dbQuery2 = Single.fromCallable(() -> { return "db-query-result-2"; });
Single.zip(dbQuery1, dbQuery2, (result1, result2) -> {
// handle the results from both db queries
// (with Pair being a standard tuple-like class)
return new Pair(result1, result2);
})
.subscribe(
pair -> {
// handle the results
},
error -> {
// something went wrong
}
);
}
per the docs, zip allows you to specify a series of reactive types (Single, Observable, etc) along with a function to transform all the results at once, with the central idea being that it will not emit anything until all the sources have emitted once (or more, depending on the reactive type).

Vert.x 3, request handler POST, async CompletionStage or CompletableFuture

class MyVerticle extends AbstractVerticle {
void start ...
router.get("/api/v1/mypostcall").handler(routingContext -> {
...
// I have existing async code, which I want to plug here.
CompletionStage<String> jsonCompletable = ...
// How do I respond using the CompletionStage (or CompletableFuture) to VertX
// so it does not block here ?
}
I have also read about the vert.x context, but the routingContext is something else and the example is about unit tests ...
Do you have any example of integration between Java CompletionStage or must I use RxJava ?
There nothing special with CompletableFuture. It's like any async api. Simple example, that follows vertx thread model. Computation happens on some computational context, and result writes on vertx event loop:
public static CompletionStage<String> calculateAsync() {
CompletableFuture<String> completableFuture = new CompletableFuture<>();
Executors.newCachedThreadPool().submit(() -> {
Thread.sleep(5000);
completableFuture.complete("Hello");
return null;
});
return completableFuture;
}
public static void main(String[] args) {
Vertx vertx = Vertx.vertx();
Router router = Router.router(vertx);
Router.router(vertx).get("/api").handler(ctx -> {
calculateAsync().whenComplete((result, e) -> vertx.runOnContext(none -> {
ctx.response().end(result);
}));
});
vertx.createHttpServer().requestHandler(router::accept).listen(8080);
}
If all your stages are non-blocking and follow the Vert.x threading model (meaning always use the event loop), just use the "handle" method from your completable future in which you write the response (routingContext.response().end(...)) or report a failure.
If you are integrating stages not following the Vert.x threading model, you can use https://github.com/cescoffier/vertx-completable-future that provides a way to always call the stage in the "right" thread.

Categories