Completable Future with Timeout not working - java

I am new Completable Future. I am trying to call a method parallel for a list of elements (which are arguments) and then combine the results to create a final response. I am also trying to set up timeout of 50 ms so that if the call doesn't return in 50 ms I will return a default value.
So far I ve tried this :
{
List<ItemGroup> result = Collections.synchronizedList(Lists.newArrayList());
try {
List<CompletableFuture> completableFutures = response.getItemGroupList().stream()
.map(inPutItemGroup ->
CompletableFuture.runAsync(() -> {
final ItemGroup itemGroup = getUpdatedItemGroup(inPutItemGroup); //call which I am tryin to make parallel
// this is thread safe
if (null != itemGroup) {
result.add(itemGroup); //output of the call
}
}, executorService).acceptEither(timeoutAfter(50, TimeUnit.MILLISECONDS),inPutItemGroup)) //this line throws error
.collect(Collectors.toList());
// this will wait till all threads are completed
CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[completableFutures.size()]))
.join();
} catch (final Throwable t) {
final String errorMsg = String.format("Exception occurred while rexecuting parallel call");
log.error(errorMsg, e);
result = response.getItemGroupList(); //default value - return the input value if error
}
Response finalResponse = Response.builder()
.itemGroupList(result)
.build();
}
private <T> CompletableFuture<T> timeoutAfter(final long timeout, final TimeUnit unit) {
CompletableFuture<T> result = new CompletableFuture<T>();
//Threadpool with 1 thread for scheduling a future that completes after a timeout
ScheduledExecutorService delayer = Executors.newScheduledThreadPool(1);
String message = String.format("Process timed out after %s %s", timeout, unit.name().toLowerCase());
delayer.schedule(() -> result.completeExceptionally(new TimeoutException(message)), timeout, unit);
return result;
}
But I keep getting error saying :
error: incompatible types: ItemGroup cannot be converted to Consumer<? super Void>
[javac] itemGroup))
incompatible types: inference variable T has incompatible bounds
[javac] .collect(Collectors.toList());
[javac] ^
[javac] equality constraints: CompletableFuture
[javac] lower bounds: Object
[javac] where T is a type-variable:
Can some one please tell me what I am doing wrong here ? And please correct me if I am going in the wrong direction.
Thanks.

Instead of
acceptEither(timeoutAfter(50, TimeUnit.MILLISECONDS), inPutItemGroup))
you’d need
applyToEither(timeoutAfter(50, TimeUnit.MILLISECONDS), x -> inPutItemGroup)
to compile the code. “accept” is an action consuming a value without returning a new value, “apply” is an action that produces a new value.
However, there’s still a logical error. The future returned by timeoutAfter will be completed exceptionally, so dependent stages will get completed exceptionally as well, without evaluating functions, so this chaining method is not suitable for replacing an exception with a default value.
Even worse, fixing this would create a new future which gets completed by either source future, but that does not affect the result.add(itemGroup) action performed in one of the source futures. In your code, the resulting future is only used to wait for the completion, but not for evaluating the result. So when your timeout elapses, you would stop waiting for the completion, whereas there still might be background threads modifying the list.
The correct logic is to separate the steps of fetching the value, which can get superseded by a default value on timeout, and the step of adding the result, either the fetched value or default value, to the result list. Then, you can wait for the completion of all add actions. On a timeout, there might be still ongoing getUpdatedItemGroup evaluations (there is no way to stop their execution), but their result would be ignored, so it doesn’t affect the result list.
It’s also worth pointing out that creating a new ScheduledExecutorService for every list element (that is not shut down after use, to make matters worse), is not the right approach.
// result must be effectively final
List<ItemGroup> result = Collections.synchronizedList(new ArrayList<>());
List<ItemGroup> endResult = result;
ScheduledExecutorService delayer = Executors.newScheduledThreadPool(1);
try {
CompletableFuture<?>[] completableFutures = response.getItemGroupList().stream()
.map(inPutItemGroup ->
timeoutAfter(delayer, 50, TimeUnit.MILLISECONDS,
CompletableFuture.supplyAsync(
() -> getUpdatedItemGroup(inPutItemGroup), executorService),
inPutItemGroup)
.thenAccept(itemGroup -> {
// this is thread safe, but questionable,
// e.g. the result list order is not maintained
if(null != itemGroup) result.add(itemGroup);
})
)
.toArray(CompletableFuture<?>[]::new);
// this will wait till all threads are completed
CompletableFuture.allOf(completableFutures).join();
} catch(final Throwable t) {
String errorMsg = String.format("Exception occurred while executing parallel call");
log.error(errorMsg, e);
endResult = response.getItemGroupList();
}
finally {
delayer.shutdown();
}
Response finalResponse = Response.builder()
.itemGroupList(endResult)
.build();
private <T> CompletableFuture<T> timeoutAfter(ScheduledExecutorService es,
long timeout, TimeUnit unit, CompletableFuture<T> f, T value) {
es.schedule(() -> f.complete(value), timeout, unit);
return f;
}
Here, the supplyAsync produces a CompletableFuture which will provide the result of the getUpdatedItemGroup evaluation. The timeoutAfter invocation will schedule a completion with a default value after the timeout, without creating a new future, then, the dependent action chained via thenAccept will add the result value to the result list.
Note that a synchronizedList allows adding elements from multiple threads, but adding from multiple threads will result in an unpredictable order, unrelated to the order of the source list.

The signature of acceptEither looks like this:
public CompletableFuture<Void> acceptEither(
CompletionStage<? extends T> other,
Consumer<? super T> action
) {
And the line which is throwing the error looks like this:
.acceptEither(
timeoutAfter(50, TimeUnit.MILLISECONDS),
inPutItemGroup
)
So you see that you try to pass an ItemGroup as a Consumer<? super T> where T was infered to be Void and hence you get the expected error:
error: incompatible types: ItemGroup cannot be converted to Consumer<? super Void>

Related

Java - CompletableFutures - How can i cancel all futures if there are exceptions

I have a method (included below) to return the values of a list of CompletableFutures.
The method is supposed to:
be able to timeout after a given time.
be able to cancel all futures if there are more than n amount of exceptions.
The first point works well and indeed bombs out after it passed the timeout limit. (I still need to call exectuorService.shutdownNow() afterwards to return to the caller). The problem I'm having is with the second thing I'm trying to accomplish.
Lets say i have a list of 20,000 futures and all of them will have an exception, then why let all of them execute, if I see that there are too many exceptions then i assume that something is wrong with all of the futures andI want to cancel them.
In addition i would love to have a timeout on each future individually how long it may take, but this also would'nt work, unassuming for the same reason outlined below.
It seems that the reason is, because when I call allDoneFuture.thenApply(), at this point it waits and lets all the futures complete, either successfully or exceptionally. Only after all of them completed does it go through each future and fetches its result. At that point what good does it do to cancel, when they have completed already.
I would much appreciate if someone can show me how to accomplish this specific need: "Monitor the exceptions, and the individual timeouts, and based on that cancel all others".
Thanks.
Below is the method I wrote:
/**
* #param futures a list of completable futures
* #param timeout how long to allow the futures to run before throwing exception
* #param timeUnit unit of timeout
* #param allowedExceptions how many of the futures do we tolerate exceptions,
* NOTE: if an exception is thrown from the futures it will return null, until it reaches the allowedExceptions threshold
* */
public static <T> List<T> extractFromFutures(List<CompletableFuture<T>> futures, int timeout, TimeUnit timeUnit, int allowedExceptions) {
CompletableFuture<Void> allDoneFuture = CompletableFuture
.allOf(futures.toArray(new CompletableFuture[futures.size()]));
try {
AtomicInteger exceptionCount = new AtomicInteger(0);
return allDoneFuture.thenApply(v ->//when all are done
futures.stream().
map(future -> {
try {
//if only I could set an individual timeout
return future.get(timeout, timeUnit);
} catch (Exception e) {
future.cancel(true);
int curExceptionCnt = exceptionCount.incrementAndGet();
if(curExceptionCnt >= allowedExceptions){
//I would've hoped that it will throw it to the calling try-catch
//and then cancel all futures, but it doesn't
throw new RuntimeException(e);
}
else{
return null;
}
}
}).
collect(Collectors.<T>toList())
).get(timeout, timeUnit);
} catch (Exception e) {
allDoneFuture.cancel(true);
throw new RuntimeException(e);
}
}
To cancel all of the remaining futures after a certain number of exceptions you can call exceptionally on each of them and increment the exception count and possibly cancel them inside of that.
For individual timeouts you could create a class that holds the future with its timeout then sort them based on the timeout and call get with the timeout minus the elapsed time.
static class FutureWithTimeout<T> {
CompletableFuture<T> f;
long timeout;
TimeUnit timeUnit;
FutureWithTimeout(CompletableFuture<T> f, long timeout, TimeUnit timeUnit) {
this.f = f;
this.timeout = timeout;
this.timeUnit = timeUnit;
}
}
public static <T> List<T> extractFromFutures(List<FutureWithTimeout<T>> futures, int allowedExceptions) {
AtomicInteger exceptionCount = new AtomicInteger(0);
futures.forEach(f -> f.f.exceptionally(t -> {
if(exceptionCount.getAndIncrement() == allowedExceptions){
futures.forEach(c -> c.f.cancel(false));
}
return null;
}));
long t = System.nanoTime();
return futures.stream()
.sorted(Comparator.comparingLong(f -> f.timeUnit.toNanos(f.timeout)))
.map(f -> {
try {
return f.f.get(Math.max(0, f.timeUnit.toNanos(f.timeout) - (System.nanoTime() - t)),
TimeUnit.NANOSECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException ex) {
f.f.cancel(false);
return null;
}
})
.collect(Collectors.toList());
}
Note that this may return the list in a different order than it was passed in. If you need it in the same order then you could change the map().collect() to a forEachOrdered and then re map them into their results after without sorting.
Also the mayInterruptIfRunning parameter to cancel has no effect on CompletableFuture so I changed it to false.
CompletableFuture completely ignores any call to cancel(true). I don't know why (presumably to simplify the API), but it sucks. If you want to make futures actually cancelable (where you can either manually check for interruption, or accept cancellation by blocking on a lock), then you have to use Future, not CompletableFuture.

What is Futures.transform() lambda argument in case the original ApiFuture / ListenableFuture failed or cancelled

I have a method that sends asynchronously a list of messages. Each send returns ApiFuture<String> (GCP version of Guava's ListenableFuture). I need this method to return one Future<Boolean>, so I
Create a list dependency on each ApiFuture<String>
Transform the resulting ApiFuture<List<String>> to a Future<Boolean> using ApiFutures.transform method
ApiFuture< List < String > > allSentFuture = ApiFutures.allAsList(futures);
return ApiFutures.transform(allSentFuture, val -> {
return true;
},
Executors.newCachedThreadPool()
);
My question is: what is the value of val argument of the lambda above if one or more of the original futures are failed/cancelled? Is the lambda even called in this case?
Thanks!
ApiFuture<V> forms a monad over the type V, and transform applies a function to the encapsulated value of type V. If the ApiFuture<V> does not contain a V value because it is failed or cancelled, then the transformed future is the same.
If you want to handle failures due to exceptions, you can use ApiFutures.catching() to produce an alternative result (e.g. Boolean.FALSE) instead.
If you want to transform cancellation into a successful value, I believe you would need to use ApiFuture.addListener directly, and have the listener complete a SettableApiFuture which you return. Then the listener (which will get called when the source future is cancelled) can check isCancelled to detect this case, or can catch and handle the CancellationException.
For example:
/**
* Adapt an iterable of {#link ApiFuture} instances into a single {#code ApiFuture}.
*/
static <T> ApiFuture<Boolean> adaptFutures(Iterable<ApiFuture<T>> futures) {
final SettableApiFuture<Boolean> result = SettableApiFuture.create();
final ApiFuture<List<T>> allFutures = ApiFutures.allAsList(futures);
allFutures.addListener(
() -> {
if (allFutures.isCancelled()) {
result.set(Boolean.FALSE);
return;
}
try {
allFutures.get();
result.set(Boolean.TRUE);
} catch (ExecutionException | InterruptedException ex) {
// Maybe log something here?
//
// Note that InterruptedException is actually impossible here
// because we're running in the listener callback, but the API
// still marks it as potentially thrown by .get() above.
//
// So if we reach here it means that the allAsList future failed.
result.set(Boolean.FALSE);
}
},
// Not normally safe, but we know our listener runs fast enough
// to run inline on the thread that completes the last future.
Runnable::run);
return result;
}

thenApply in CompletableFuture

In the following code
public CompletableFuture<String> getMyFuture(String input)
{
CompletableFuture<String> future = new CompletableFuture<String>().thenApply((result) -> result+ "::");
ExecutorService service = Executors.newFixedThreadPool(6);
service.submit(() -> {
try {
future.complete(getResult(input));
} catch (InterruptedException e) {
e.printStackTrace();
}
});
return future;
}
public String getResult(String input) throws InterruptedException
{
Thread.sleep(3000);
return "hello "+ input +" :" + LocalTime.now();
}
I am expecting the output to contain trailing "::" but program doesn't is "hello first :16:49:30.231
" Is my implementation of apply correct ?
You're invoking complete() method of the CompletionStage that you got at the first line (where you call "thenApply" method).
If your intention is to complete the CompletableFuture with some string value (future.complete(getResult(input))) and then apply some function, you'd better place thenApply() at the end (where you return the future).
public CompletableFuture<String> getMyFuture(String input)
{
CompletableFuture<String> future = new CompletableFuture<String>();
ExecutorService service = Executors.newFixedThreadPool(6);
service.submit(() -> {
try {
future.complete(getResult(input));
} catch (InterruptedException e) {
e.printStackTrace();
}
});
return future.thenApply(result -> result+ "::");
}
I don't know how to explain it in a more understandable way. But in short: you're calling complete() method on the wrong object reference inside your Runnable.
You are creating two CompletableFuture instances. The first, created via new CompletableFuture<String>() will never get completed, you don’t even keep a reference to it that would make completing it possible.
The second, created by calling .thenApply((result) -> result+ "::") on the first one, could get completed by evaluating the specified function once the first one completed, using the first’s result as an argument to the function. However, since the first never completes, the function becomes irrelevant.
But CompletableFuture instances can get completed by anyone, not just a function passed to a chaining method. The possibility to get completed is even prominently displayed in its class name. In case of multiple completion attempts, one would turn out to be the first one, winning the race and all subsequent completion attempts will be ignored. In your code, you have only one completion attempt, which will successfully complete it with the value returned by getResult, without any adaptations.
You could change your code to keep a reference to the first CompletableFuture instance to complete it manually, so that the second gets completed using the function passed to thenApply, but on the other hand, there is no need for manual completion here:
public CompletableFuture<String> getMyFuture(String input) {
ExecutorService service = Executors.newFixedThreadPool(6);
return CompletableFuture.supplyAsync(() -> getResult(input), service)
.thenApply(result -> result + "::");
}
public String getResult(String input) {
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(3));
return "hello "+ input +" :" + LocalTime.now();
}
When specifying the executor to supplyAsync, the function will be evaluated using that executor. More is not needed.
Needless to say, that’s just for example. You should never create a temporary thread pool executor, as the whole point of a thread pool executor is to allow reusing the threads (and you’re using only one of these six threads at all) and it should get shut down after use.

CompletableFuture get result of first future waiting for other futures depending of first one

I have following requirements.
CreateDocument
For document create many release notes (releaseNotesFuture)
For document create many parcels (parcelsFuture)
return objectId of document created in 1.
this is my current code:
public CompletableFuture<ObjectId> createDeliveryNoteDocument(String productId, List<String> releaseNotesIds) {
CompletableFuture<ObjectId> deliveryNoteFuture =
CompletableFuture
.supplyAsync(() -> sequenceServiceFeignClient.getNextValueForSequenceNameNoResponseEntity(DocumentType.DELIVERYNOTE.toString()))
.whenComplete((result, error) -> {
if (error != null)
logger.error("Unable to get next sequence number for DELIVERYNOTE sequence", error);
})
.thenCompose(seqNumber -> {
Set<ObjectAttribute> objectAttributes = new HashSet<>();
objectAttributes.add(new ObjectAttribute(Constants.Document.DOCUMENT_TYPE, DocumentType.DELIVERYNOTE.toString()));
objectAttributes.add(new ObjectAttribute(Constants.Document.DOCUMENT_NO, seqNumber));
objectAttributes.add(new ObjectAttribute(Constants.Document.PRODUCT_ID, productId));
return objectCommandService.createCustomObject(new ObjectTypeTableName(Constants.ObjectTables.DOCUMENT), objectAttributes);
});
CompletableFuture<Void> releaseNotesFuture =
deliveryNoteFuture
.thenComposeAsync(deliveryNoteId -> joinReleaseNotesWithDeliveryNote(deliveryNoteId, releaseNotesIds));
CompletableFuture<Void> parcelsFuture =
deliveryNoteFuture
.thenComposeAsync(deliveryNoteId -> changeParcelsStatusForReleaseNotes(releaseNotesIds));
return deliveryNoteFuture;
}
how could I wait for releaseNotesFuture and parcelsFuturecompletion and then return deliveryNoteFuture result or error if any of releaseNotesFuture or parcelsFuture finished exceptionally?
Instead of returning deliveryNoteFuture, you'll have to have a CompletableFuture that is completed when the releaseNotesFuture and parcelsFuture are completed. Furthermore, you'll want that future to then compose into the result of deliveryNoteFuture, since you want its ObjectId if the whole chain is successful.
Something like
return CompletableFuture.allOf(releaseNotesFuture, parcelsFuture).thenComposeAsync(r -> deliveryNoteFuture);
Since both releaseNotesFuture and parcelsFuture are dependent on deliveryNoteFuture, errors from deliveryNoteFuture will propagate through all these chains.
Similarly, if either of releaseNotesFuture or parcelsFuture fail, the CompletableFuture returned by allOf will be completed with that failure's cause and that cause will be propagated to the future returned by thenComposeAsync. Otherwise, the result of deliveryNoteFuture will be delivered.
As Holger suggests, since you only have those two futures, you could also use thenCombine
releaseNotesFuture.thenCombineAsync(parcelsFuture, (x, y) -> deliveryNoteFuture.join());
Here, the join() won't block since deliveryNoteFuture is definitely already completed. Again, if the deliveryNoteFuture had originally failed, its cause will be propagated down the chain ending with the CompletableFuture returned by thenCombineAsync.

Timeout if no items are emitted after specified time

I have a simple operation where an api call is being performed and the result is an Observable which emits the response:
apiService.getTeam()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Response>() {
...
});
I'd like to provide some feedback to the user if no items are emitted after a few seconds. I am aware of the .timeout() operator, however this would be applied to each item, so even if a few items are emitted, a few seconds after the last available item is emitted the timeout would cause an error.
I would only like to timeout the operation if no items are emitted. Is there a simple way to do this?
There is a timeout overload that you can use to timeout the first element and not timeout the rest:
Observable.never()
.timeout(() -> Observable.timer(1, TimeUnit.SECONDS), e -> Observable.never())
.toBlocking()
.subscribe(System.out::println, Throwable::printStackTrace);
Here, the second parameter simply returns an Observable that will never emit and thus subsequent elements won't timeout.
The below snippet can solve your problem:
public static void main(String[] args) {
boolean itemEmitted[] = { false };
long identifier = -1l;
Observable.interval(10, TimeUnit.SECONDS)
.mergeWith(Observable.just(identifier).delay(5, TimeUnit.SECONDS))
.map(v -> {
if (!itemEmitted[0] && v == identifier) { // item is not emitted and a timeout occurs
throw new RuntimeException("timeout");
}
itemEmitted[0] = true;
return v;
}).filter(v -> v != identifier).toBlocking()
.subscribe(v -> System.out.println(v));
}
Observable.interval is your source Observable that emits items which is merged with Observable.just with delay - whose combination acts similar to timeout. The output Observable is mapped to check if an actual item is emitted or whether timeout identifier occurred. If timeout, throw Exception. Later the output is filtered to remove the identifier from the list of emitted items.
You can play with this code by modifying the value in initial Observable.interval.

Categories

Resources