I got a group of messages I need to process in a Spring Async method:
List<CompletableFuture<Void>> futures = new ArrayList<>();
groupedMessages.forEach(value -> futures.add(asyncService.processEvent(value)));
CompletableFuture<Void> allFutures = CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]));
allFutures.get();
Here is my method:
public CompletableFuture<Void> processEvents(Message message) {
// Start timer here {
// Process message
// Web Service call, enrichment, etc
// No need to return a value
// } End timer here, if timeout exceeds, kill the thread, or just return completedFuture()
return CompletableFuture.completedFuture(null);
}
What I'm trying to do is run a list of async calls, and if one of them exceeds a specified time, that it will kill the thread. I don't need to throw an exception or anything. But when I get to allFutures.get(), I want to know that either the threads were completed or canceled due to timeout. If they got canceled, I got a retry process that will run the messages again.
Related
I have N tasks to execute, and the number of tasks is not fixed. The next task can only be executed after the previous task is completed. How can the entire task chain be executed asynchronously?
If the number of tasks is fixed, such as N=2, I can use the following code. How to do it if N is not fixed
public void futureTest() throws InterruptedException {
CompletableFuture<Integer> finalFuture = new CompletableFuture<>();
CompletableFuture<Integer> cf1 = doTask(1);
AtomicReference<CompletableFuture<Integer>> cf2 = new AtomicReference<>(new CompletableFuture<>());
cf1.whenComplete(((integer1, throwable1) -> {
if (throwable1 != null) {
finalFuture.completeExceptionally(throwable1);
return;
}
// when task1 complete then submit task2
cf2.set(doTask(2));
cf2.get().whenComplete(((integer2, throwable2) -> {
if (throwable2 != null) {
finalFuture.completeExceptionally(throwable2);
return;
}
finalFuture.complete(integer2);
}));
}));
finalFuture.whenComplete(((integer, throwable) -> {
System.out.println("all task is done");
}));
Thread.sleep(1000000);
}
private CompletableFuture<Integer> doTask(int index) {
CompletableFuture<Integer> cf = new CompletableFuture<>();
// Simulate task execution
THREAD_POOL.execute(() -> {
Thread.sleep(3_000);
cf.complete(index);
});
return cf;
}
I looked at Compeltable's API docs and none of them seem to solve my problem. I tried to use a loop to control the task submission, but all failed, unable to submit the next task after the previous task is completed
Refer to this answer on this thread Click here. Seems a duplicate of this question.
thenRun method is used to run the task after the previous future is completed successfully. This method will be skipped in case of any failures in previous stages.
whenComplete method is used as the final stage of execution chain. Here you will receive the composed result of all the other functions in the supply chain and you can choose to fail your future or handle exceptions accordingly inside this.
You can compose the futures for the individual tasks via CompletableFuture#thenCompose in a loop:
CompletableFuture<?> future = createFirstTask();
while (hasMoreTasks()) {
future = future.thenCompose(this::createNextTask);
}
Here every next tasks depends on the result of the previous one until no more task is left.
Conceptionally this is a fold operation, which unfortunately is not part of the API of CompletableFuture. But if you don't mind using my better future library (which is just a thin wrapper around CompleteableFuture), I just recently added support for folding streams of futures there.
// assume: serviceCall1 throws an exception after 1s, servserviceCall2 runs 10s without exception
CompletableFuture<String> serviceCall1Future = serviceCall1.execute();
CompletableFuture<String> serviceCall2Future = serviceCall2.execute();
CompletableFuture<Void> allOffFuture = CompletableFuture.allOf(serviceCall1Future, serviceCall2Future);
// does not work, will be called after thread 2 has finished
allOffFuture.exceptionally( ex -> { allOffFuture.cancel(true); return null; } );
try {
// waiting for threads to finish
allOffFuture.join();
} catch (CompletionException e) {
// does not work, here we come after thread 2 has finished
allOffFuture.cancel(true);
}
If one thread throws an exception, in my case it doesnt make any sense for the other thread(s) to keep on running, so I want them both (all in case of more than 2 threads) to stop . How can I achieve that ?
I guess something like this should work:
CompletableFuture<String> serviceCall1Future = serviceCall1.execute();
CompletableFuture<String> serviceCall2Future = serviceCall2.execute();
CompletableFuture<String> foo1 = serviceCall1Future.whenComplete((result,exception) -> {if(exception != null) serviceCall2Future.cancel(true);});
CompletableFuture<String> foo2 = serviceCall2Future.whenComplete((result,exception) -> {if(exception != null) serviceCall1Future.cancel(true);});
CompletableFuture<Void> allOffFuture = CompletableFuture.allOf(foo1, foo2);
// ... rest of your code
This cancels the other future when the one completes with an exception.
If you are using an ExecutorService with CompletableFuture, you can use Shutdowns methods like shutdown() or shutdownNow().
If you want to shut down the ExecutorService immediately, you can call the shutdownNow() method. This will attempt to stop all executing tasks right away, and skips all submitted but non-processed tasks. There are no guarantees given about the executing tasks. Perhaps they stop, perhaps the execute until the end. It is a best effort attempt. Here is an example of calling ExecutorService shutdownNow()
See -> https://jenkov.com/tutorials/java-util-concurrent/executorservice.html#executorservice-shutdown
I have the following observable:
ScheduledExecutorService executorService = Executors.newScheduledThreadPool( 1 );
Observable<List<Widget>> findWidgetsObservable = Observable.create( emitter -> {
executorService.scheduleWithFixedDelay( emitFindWidgets( emitter, 0, 30, TimeUnit.SECONDS );
} );
private Runnable emitFindWidgets( ObservableEmitter<List<Widgets>> emitter ) {
return () -> {
emitter.onNext( Collections.emptyList() ); // dummy empty array
};
}
And I'm returning it in a graphql-java subscription resolver like so:
ConnectableObservable<List<Widget>> connectableObservable = findWidgetsObservable.share().publish();
Disposable connectionDisposable = connectableObservable.connect();
return connectableObservable.toFlowable( BackpressureStrategy.LATEST )
The graphql subscription works as expected and emits data to the JavaScript graphql client, but when the client unsubscribes, my Runnable continues seemingly infinitely. That said, the flowable's doOnCancel() event handler IS being run.
In order to remedy this problem, I've attempted to do the following within the flowable's doOnCancel():
Disposable connectionDisposable = connectableObservable.connect();
return connectableObservable.toFlowable( BackpressureStrategy.LATEST ).doOnCancel( () -> {
findWidgetsObservable.toFuture().cancel( true );
connectionDisposable.dispose();
})
However, the Runnable continues omitting indefinitely. Is there any way I can solve this problem and completely stop the emits?
I did have one thought: scheduleWithFixedDelay returns a ScheduledFuture, which has a cancel() method, but I'm not sure that there's anyway I can do that when the scheduling itself is scoped within an observable! Any help is appreciated.
The runnable keeps on emitting because you are scheduling the emission on a scheduler that is not known/bound to observable stream.
When you dispose your connection, you stop receiving the items from upstream because the connection to upstream observable is cut. But since you are scheduling the emitter to run repeatedly on a separate scheduler, the runnable keeps running.
You can describe the custom scheduling behavior using a custom scheduler and passing it in subscribeOn(Your-Custom-Scheduler)
Also, you mentioned you can invoke cancel() on ScheduledFuture in doOnDispose().
But you should switch schedulers explicitly in the observable chain. Otherwise, it becomes harder to debug.
here is two options , just bit confuse which one is best to go.
Option 1:
ExecutorService es = Executors.newFixedThreadPool(4);
List<Runnable> tasks = getTasks();
CompletableFuture<?>[] futures = tasks.stream()
.map(task -> CompletableFuture.runAsync(task, es))
.toArray(CompletableFuture[]::new);
CompletableFuture.allOf(futures).join();
es.shutdown();
Option 2:
ExecutorService es = Executors.newFixedThreadPool(4);
List< Future<?>> futures = new ArrayList<>();
for(Runnable task : taskList) {
futures.add(es.submit(task));
}
for(Future<?> future : futures) {
try {
future.get();
}catch(Exception e){
// do logging and nothing else
}
}
es.shutdown();
Here putting future.get(); in try catch is good idea right?
Since you effectively hold each submitted Future in a list of of futures by doing this:
List< Future<?>> futures = new ArrayList<>();
for(Runnable task : taskList) {
futures.add(es.submit(task));
}
You can very easily check if all the submitted jobs are done executing just, by invoking the Future#isDone method, which wilkl return true or false based on whether the task has finished or not. You can check more on this on the related documentation here.
Hence, with the above in mind you could very well create a simple helper method, that will iterate the list of your futures and check on their state. For example:
private boolean areJobsDone() {
return futures.stream()
.allMatch(Future::isDone);
}
Note that in contrast to the Future#get method, isDone is non blocking (as it's not waiting for the task to return it's result) as it effectively queries it's state.
With this you can go ahead and check for the state of your runnables, blocking the flow with a loop that will invoke the helper method, before proceeding.
Hope this helps.
There is another way to wait for all tasks to complete. After you submitted all of your tasks, call
es.shutdown()
es.awaitTermination(Long.MAX_VALUE, TimeUnit.NANO_SECONDS)
Oracle's Java Docs read:
shutdown [...] Initiates an orderly shutdown in which previously submitted tasks are executed.
awaitTermination [...] Blocks until all tasks have completed execution after a shutdown request, or the timeout occurs, or the current thread is interrupted, whichever happens first.
Concerning the timeout: with the above values, the thread-pool will only terminate after about 300 years.
Similar to the Aris_Kortex' proposal,
List<CompletableFuture<?>> futures = new ArrayList<>();
for(Runnable task : taskList) {
futures.add(CompletableFuture.runAsync(task, es));
}
and then create the combined CompletableFuture:
CompletableFuture<Void> cf = CompletableFuture.allOf(futures.toArray(futires.size()));
then you can wait for all tasks synchronously:
cf.get();
synchronously with timeout:
cf.get(1, TimeUnit.SECOND);
of asynchronously:
cf.thenRun(()->{finalActions();});
I'm implementing some sort of chat application and I need some help. This is the simplified code:
//...
Boolean stop = false;
while(!stop) {
ServerRequest message = (ServerRequest) ois.readObject();
broadcastMessage((String)message.getData()); //this method sends the client's message to all the other clients on the server
stop = (System.nanoTime() - start >= handUpTime); // I want to let the client send his messages for no more than handUpTime seconds
} //...
I want to let a client to send his messages to the server for a certain amount of time (handUpTime) and then "block" him, but I don't know how to do this in an "elegant" manner. Of course, my code stumbles upon the ois.readObject() part, as the System waits to receive a message, and continues to run for more than handUpTime seconds. How can I solve this problem? I'm open to other approaches too.
You can try:
ExecutorService executorService = Executors.newSingleThreadExecutor();
Callable<Object> callable = () -> {
// Perform some blocking computation
return someObject
};
Future<Object> future = executorService.submit(callable);
Object result = future.get(YOUR_TIMEOUT, TimeUnit.SECONDS);
If the future.get() doesn't return in certain amount of time, it throws a TimeoutException so you should handle the exception. See this post.