Execute CompletableFuture which depends on an other task - java

I wrote a Play framework web application and I have the following problem. I have some DAOs that access the database, and sometimes one request to the database relies on information from another request.
Here is one of the problems: I execute the getLicenseByKey method in an asynchronous way and get the result. Now I can execute version_dao.getVersionUntilX() with the result of the license_dao request. The problem here is that the .get() function of CompletableFuture is executed on the HttpExecutionContext (blocking one of the HTTP threads of Akka (Play framework)), and if this database request takes a long time, the thread is blocked.
So how can I execute asynchronously the license_dao.getLicenseByKey() and then, with the result of this method, execute the version_dao.getVersionUntilX() method also asynchronously? And if both are finished, I want to return the HTTP result from the HttpExecutionContext of Play.
public CompletionStage<Result> showDownloadScreen(String license_key) {
return license_dao.getLicenseByKey(license_key).thenApplyAsync(license -> {
try {
if(license!=null) {
if(license.isRegistered()) {
return ok(views.html.download.render(license,
version_dao.getVersionUntilX(license.getUpdates_until())
.toCompletableFuture().get()));
}else {
return redirect(routes.LicenseActivationController.showActivationScreen(license_key));
}
}else {
return redirect(routes.IndexController.index("Insert Key can not be found!"));
}
} catch (InterruptedException | ExecutionException e) {
return redirect(routes.IndexController.index("Error while checking the Verison"));
}
}, httpExecutionContext.current());
}

Use thenComposeAsync() instead of thenApplyAsync(), and have your inner lambda also return a CompletableFuture:
public CompletionStage<Result> showDownloadScreen(String license_key) {
return license_dao.getLicenseByKey(license_key).thenComposeAsync(license -> {
try {
if(license!=null) {
if(license.isRegistered()) {
return version_dao.getVersionUntilX(license.getUpdates_until())
.toCompletableFuture()
.thenApply(v -> ok(views.html.download.render(license, v)));
} else {
return CompletableFuture.completedFuture(redirect(routes.LicenseActivationController.showActivationScreen(license_key)));
}
} else {
return CompletableFuture.completedFuture(redirect(routes.IndexController.index("Insert Key can not be found!")));
}
} catch (InterruptedException | ExecutionException e) {
return CompletableFuture.completedFuture(redirect(routes.IndexController.index("Error while checking the Verison")));
}
}, httpExecutionContext.current());
}
Since that lambda is quite complex, it would also be worth extracting it to a separate method, and do some more cleanup.

Related

why jpa not save data at a time

i want save data and check the data after call save method
but the value is not present in same request
i have two method depend each other
the two function communcation with each other by kafka
the first method save the data and after save using jpa call second method
find the recourd from database using jpa
and check the instanse using isPresent()
but in the second method i cant find the data save
but after this request i can find data
return exciption NoSuchElement
Try out several ways like:
1-use flush and saveAndFlush
2-sleep method 10000 milsec
3-use entityManger with #Transactional
but all of them not correct
i want showing you my two method from code:
i have producer and consumer
and this is SaveOrder method (first method):
note : where in the first method have all ways i used
#PersistenceContext
private EntityManager entityManager;
#Transactional
public void saveOrder(Long branchId,AscOrderDTO ascOrderDTO) throws Exception {
ascOrderDTO.validation();
if (ascOrderDTO.getId() == null) {
ascOrderDTO.setCreationDate(Instant.now());
ascOrderDTO.setCreatedBy(SecurityUtils.getCurrentUserLogin().get());
//add user
ascOrderDTO.setStoreId(null);
String currentUser=SecurityUtils.getCurrentUserLogin().get();
AppUser appUser=appUserRepository.findByLogin(currentUser);
ascOrderDTO.setAppUserId(appUser.getId());
}
log.debug("Request to save AscOrder : {}", ascOrderDTO);
AscOrder ascOrder = ascOrderMapper.toEntity(ascOrderDTO);
//send notify to branch
if(!branchService.orderOk())
{
throw new BadRequestAlertException("branch not accept order", "check order with branch", "branch");
}
ascOrder = ascOrderRepository.save(ascOrder);
/*
* log.debug("start sleep"); Thread.sleep(10000); log.debug("end sleep");
*/
entityManager.setFlushMode(FlushModeType.AUTO);
entityManager.flush();
entityManager.clear();
//ascOrderRepository.flush();
try {
producerOrder.addOrder(branchId,ascOrder.getId(),true);
stateMachineHandler.stateMachine(OrderEvent.EMPTY, ascOrder.getId());
stateMachineHandler.handling(ascOrder.getId());
//return ascOrderMapper.toDto(ascOrder);
}
catch (Exception e) {
// TODO: handle exception
ascOrderRepository.delete(ascOrder);
throw new BadRequestAlertException("cannot deliver order to Branch", "try agine", "Try!");
}
}
in this code go to producer :
producerOrder.addOrder(branchId,ascOrder.getId(),true);
and this is my producer:
public void addOrder(Long branchId, Long orderId, Boolean isAccept) throws Exception {
ObjectMapper obj = new ObjectMapper();
try {
Map<String, String> map = new HashMap<>();
map.put("branchId", branchId.toString());
map.put("orderId", orderId.toString());
map.put("isAccept", isAccept.toString());
kafkaTemplate.send("orderone", obj.writeValueAsString(map));
}
catch (Exception e) {
throw new Exception(e.getMessage());
}
}
and in this code go to consumer:
kafkaTemplate.send("orderone", obj.writeValueAsString(map));
this is my consumer:
#KafkaListener(topics = "orderone", groupId = "groupId")
public void processAddOrder(String mapping) throws Exception {
try {
log.debug("i am in consumer add Order");
ObjectMapper mapper = new ObjectMapper(); Map<String, String> result = mapper.readValue(mapping,
HashMap.class);
branchService.acceptOrder(Long.parseLong(result.get("branchId")),Long.parseLong(result.get("orderId")),
Boolean.parseBoolean(result.get("isAccept")));
log.debug(result.toString());
}
catch (Exception e) {
throw new Exception(e.getMessage());
}
}
**and this code go to AcceptOrder (second method) : **
branchService.acceptOrder(Long.parseLong(result.get("branchId")),Long.parseLong(result.get("orderId")),
Boolean.parseBoolean(result.get("isAccept")));
this is my second method :
public AscOrderDTO acceptOrder(Long branchId, Long orderId, boolean acceptable) throws Exception {
ascOrderRepository.flush();
try {
if (branchId == null || orderId == null || !acceptable) {
throw new BadRequestAlertException("URl invalid query", "URL", "Check your Input");
}
if (!branchRepository.findById(branchId).isPresent() || !ascOrderRepository.findById(orderId).isPresent()) {
throw new BadRequestAlertException("cannot find branch or Order", "URL", "Check your Input");
}
/*
* if (acceptable) { ascOrder.setStatus(OrderStatus.PREPARING); } else {
* ascOrder.setStatus(OrderStatus.PENDING); }
*/
Branch branch = branchRepository.findById(branchId).get();
AscOrder ascOrder = ascOrderRepository.findById(orderId).get();
ascOrder.setDiscount(50.0);
branch.addOrders(ascOrder);
branchRepository.save(branch);
log.debug("///////////////////////////////Add order sucess////////////////////////////////////////////////");
return ascOrderMapper.toDto(ascOrder);
} catch (Exception e) {
// TODO: handle exception
throw new Exception(e.getMessage());
}
}
Adding Thread.sleep() inside saveOrder makes no sense.
processAddOrder executes on a completely different thread, with a completely different persistence context. All the while, your transaction from saveOrder might still be ongoing, with none of the changes made visible to other transactions.
Try splitting saveOrder into a transactional method and sending the notification, making sure that the transaction ends before the event handling has a chance to take place.
(Note that this approach introduces at-most-once semantics. You have been warned)

Vert.x request handler and blocking db queries

How can i translate this piece of code correctly in vert.x?
normally, in spring or in a simple sevlet with a template engine to output an html response i'll do like this
function test(request, response) {
templatecontext tc = getContext();
init conditions
if (condition1) {
retrieve data from db ({
asyncresult -> {
tc.put("data1", data1)
})
} else if (condition2) {
other code
if (condition 2.1) {
retrieve data from db ({
asyncresult -> {
tc.put("data2", data2)
})
}
}
get other data from db and put in context
template.eval("templatefile", tc)
write to response
}
the problem is, retrieving data from database is an handler of asyncresult, so i cannot grant that template evaluation is made with data1 or data2, because retrieving of async without falling in a callback hell.
I've not really understood rxjava2, but i feel i'm trying to kill a bean with a spoon.
You can use futures and composition. See the ComposeExample in vertx-examples repo:
public class ComposeExample extends AbstractVerticle {
#Override
public void start() throws Exception {
Future<String> future = anAsyncAction();
future.compose(this::anotherAsyncAction)
.setHandler(ar -> {
if (ar.failed()) {
System.out.println("Something bad happened");
ar.cause().printStackTrace();
} else {
System.out.println("Result: " + ar.result());
}
});
}
private Future<String> anAsyncAction() {
Future<String> future = Future.future();
// mimic something that take times
vertx.setTimer(100, l -> future.complete("world"));
return future;
}
private Future<String> anotherAsyncAction(String name) {
Future<String> future = Future.future();
// mimic something that take times
vertx.setTimer(100, l -> future.complete("hello " + name));
return future;
}
}

Returning 500 internal error when CompleteableFuture throws exception

I have a rest controller that has an endpoint:
#GET
#Path("/reindex-record")
public String reindexRecord(#QueryParam("id") String id) {
if (StringUtils.isEmpty(id)) {
CompletableFuture.runAsync(
() -> runWithException(Reindexer::reindexAll));
} else {
CompletableFuture.runAsync(() -> runWithException(
() -> Reindexer.reindexOne(id)));
}
// return "ok" or throw WebApplciationException from runWithException method below
}
and here is my wrapper method - both methods - reindexAll and reindexOne throw checked exceptions so decided to use wrapper method and interface:
public interface RunnableWithException {
void run() throws Exception;
}
private void runWithException(RunnableWithException task) {
try {
task.run();
} catch (Exception e) {
log.error("Error occured during async task execution", e);
throw new WebApplicationException(
Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("Internal error occurred").build());
}
}
The problem is that I want to run this task asnychronously using CompleteableFuture and give a response only after given task is done or if there was an error throw WebApplicationException with INTERNAL_SERVER_ERROR status.
How would you implement that in my use-case with if/else?
EDIT:
As of now I have this method:
#GET
#Path("/reindex-record")
public String reindexRecord(#QueryParam("id") String id) throws ExecutionException,
InterruptedException {
CompletableFuture<Void> task;
if (StringUtils.isEmpty(id)) {
task = CompletableFuture.runAsync(
() -> runWithException(Reindexer::reindexAll));
} else {
task = CompletableFuture.runAsync(() -> runWithException(
() -> Reindexer.reindexOne(id)));
}
return task.thenApply(x -> "ok")
.exceptionally(throwable -> {
log.error("Error occured during async task execution", throwable);
throw new WebApplicationException(Response.status(Response.Status.SERVICE_UNAVAILABLE)
.entity("Internal error occurred. Try again later")
.build());
}).get();
}
But if error is thrown by any of Reindexer methods I'm still getting status 500 with data:
{
"code": 500,
"message": "There was an error processing your request. It has been logged (ID 03f09a62b62b1649)."
}
Instead of 503 defined in my exceptionally block.
Using dropwizard with JAX-RS if that matters.
You can change the body of your method to this:
#GET
#Path("/reindex-record")
public String reindexRecord(#QueryParam("id") String id) {
final CompletableFuture<Void> future;
if (StringUtils.isEmpty(id)) {
future = CompletableFuture.runAsync(
() -> runWithException(Reindexer::reindexAll));
} else {
future = CompletableFuture.runAsync(
() -> runWithException(() -> Reindexer.reindexOne(id)));
}
// this will block
future.get();
return "ok";
}
By storing the future, you can then call the get() method on it, which will block until the future is finished.
From the javadoc of CompletableFuture.get():
Waits if necessary for this future to complete, and then returns its result.
The problem is that you are using exceptionally() for something it wasn't intended. CompletableFutureis intented to be used in a chain of CompletableFutures where the output of one feeds into the next one. What happens if one of the CompletableFutures throws an exception? You can use exceptionally to catch that and return a new fallback ComletableFuture for the next step of the chain to use instead.
You're not doing that, you just throw an WebApplicationException. The CompleteableFuture views that as a failure in the chain and wraps your WebApplicationException inside an ExecutionException. Dropwizard only sees the ExecutionException (it doesn't inspect any wrapped ones) and throws the generic 500 response.
The answer is just do the future.get(); in #Lino's answer, but wrapped in a try...catch block for ExecutionException, and then throw WebApplicationException from the catch.
try {
// this will block
future.get();
} catch (ExecutioException) {
throw new WebApplicationException(
Response.status(Response.Status.INTERNAL_SERVER_ERROR)
.entity("Internal error occurred").build());
}
return "ok";
You might be able to shorten the whole throw new WebApplicationException(... to simple throw new InternalServerErrorException()

Retry logic with CompletableFuture

I need to submit a task in an async framework I'm working on, but I need to catch for exceptions, and retry the same task multiple times before "aborting".
The code I'm working with is:
int retries = 0;
public CompletableFuture<Result> executeActionAsync() {
// Execute the action async and get the future
CompletableFuture<Result> f = executeMycustomActionHere();
// If the future completes with exception:
f.exceptionally(ex -> {
retries++; // Increment the retry count
if (retries < MAX_RETRIES)
return executeActionAsync(); // <--- Submit one more time
// Abort with a null value
return null;
});
// Return the future
return f;
}
This currently doesn't compile because the return type of the lambda is wrong: it expects a Result, but the executeActionAsync returns a CompletableFuture<Result>.
How can I implement this fully async retry logic?
Chaining subsequent retries can be straight-forward:
public CompletableFuture<Result> executeActionAsync() {
CompletableFuture<Result> f=executeMycustomActionHere();
for(int i=0; i<MAX_RETRIES; i++) {
f=f.exceptionally(t -> executeMycustomActionHere().join());
}
return f;
}
Read about the drawbacks below
This simply chains as many retries as intended, as these subsequent stages won’t do anything in the non-exceptional case.
One drawback is that if the first attempt fails immediately, so that f is already completed exceptionally when the first exceptionally handler is chained, the action will be invoked by the calling thread, removing the asynchronous nature of the request entirely. And generally, join() may block a thread (the default executor will start a new compensation thread then, but still, it’s discouraged). Unfortunately, there is neither, an exceptionallyAsync or an exceptionallyCompose method.
A solution not invoking join() would be
public CompletableFuture<Result> executeActionAsync() {
CompletableFuture<Result> f=executeMycustomActionHere();
for(int i=0; i<MAX_RETRIES; i++) {
f=f.thenApply(CompletableFuture::completedFuture)
.exceptionally(t -> executeMycustomActionHere())
.thenCompose(Function.identity());
}
return f;
}
demonstrating how involved combining “compose” and an “exceptionally” handler is.
Further, only the last exception will be reported, if all retries failed. A better solution should report the first exception, with subsequent exceptions of the retries added as suppressed exceptions. Such a solution can be build by chaining a recursive call, as hinted by Gili’s answer, however, in order to use this idea for exception handling, we have to use the steps to combine “compose” and “exceptionally” shown above:
public CompletableFuture<Result> executeActionAsync() {
return executeMycustomActionHere()
.thenApply(CompletableFuture::completedFuture)
.exceptionally(t -> retry(t, 0))
.thenCompose(Function.identity());
}
private CompletableFuture<Result> retry(Throwable first, int retry) {
if(retry >= MAX_RETRIES) return CompletableFuture.failedFuture(first);
return executeMycustomActionHere()
.thenApply(CompletableFuture::completedFuture)
.exceptionally(t -> { first.addSuppressed(t); return retry(first, retry+1); })
.thenCompose(Function.identity());
}
CompletableFuture.failedFuture is a Java 9 method, but it would be trivial to add a Java 8 compatible backport to your code if needed:
public static <T> CompletableFuture<T> failedFuture(Throwable t) {
final CompletableFuture<T> cf = new CompletableFuture<>();
cf.completeExceptionally(t);
return cf;
}
Instead of implementing your own retry logic, I recommend using a proven library like failsafe, which has built-in support for futures (and seems more popular than guava-retrying). For your example, it would look something like:
private static RetryPolicy retryPolicy = new RetryPolicy()
.withMaxRetries(MAX_RETRIES);
public CompletableFuture<Result> executeActionAsync() {
return Failsafe.with(retryPolicy)
.with(executor)
.withFallback(null)
.future(this::executeMycustomActionHere);
}
Probably you should avoid .withFallback(null) and just have let the returned future's .get() method throw the resulting exception so the caller of your method can handle it specifically, but that's a design decision you'll have to make.
Other things to think about include whether you should retry immediately or wait some period of time between attempts, any sort of recursive backoff (useful when you're calling a web service that might be down), and whether there are specific exceptions that aren't worth retrying (e.g. if the parameters to the method are invalid).
I think I was successfully. Here's an example class I created and the test code:
RetriableTask.java
public class RetriableTask
{
protected static final int MAX_RETRIES = 10;
protected int retries = 0;
protected int n = 0;
protected CompletableFuture<Integer> future = new CompletableFuture<Integer>();
public RetriableTask(int number) {
n = number;
}
public CompletableFuture<Integer> executeAsync() {
// Create a failure within variable timeout
Duration timeoutInMilliseconds = Duration.ofMillis(1*(int)Math.pow(2, retries));
CompletableFuture<Integer> timeoutFuture = Utils.failAfter(timeoutInMilliseconds);
// Create a dummy future and complete only if (n > 5 && retries > 5) so we can test for both completion and timeouts.
// In real application this should be a real future
final CompletableFuture<Integer> taskFuture = new CompletableFuture<>();
if (n > 5 && retries > 5)
taskFuture.complete(retries * n);
// Attach the failure future to the task future, and perform a check on completion
taskFuture.applyToEither(timeoutFuture, Function.identity())
.whenCompleteAsync((result, exception) -> {
if (exception == null) {
future.complete(result);
} else {
retries++;
if (retries >= MAX_RETRIES) {
future.completeExceptionally(exception);
} else {
executeAsync();
}
}
});
// Return the future
return future;
}
}
Usage
int size = 10;
System.out.println("generating...");
List<RetriableTask> tasks = new ArrayList<>();
for (int i = 0; i < size; i++) {
tasks.add(new RetriableTask(i));
}
System.out.println("issuing...");
List<CompletableFuture<Integer>> futures = new ArrayList<>();
for (int i = 0; i < size; i++) {
futures.add(tasks.get(i).executeAsync());
}
System.out.println("Waiting...");
for (int i = 0; i < size; i++) {
try {
CompletableFuture<Integer> future = futures.get(i);
int result = future.get();
System.out.println(i + " result is " + result);
} catch (Exception ex) {
System.out.println(i + " I got exception!");
}
}
System.out.println("Done waiting...");
Output
generating...
issuing...
Waiting...
0 I got exception!
1 I got exception!
2 I got exception!
3 I got exception!
4 I got exception!
5 I got exception!
6 result is 36
7 result is 42
8 result is 48
9 result is 54
Done waiting...
Main idea and some glue code (failAfter function) come from here.
Any other suggestions or improvement are welcome.
util class:
public class RetryUtil {
public static <R> CompletableFuture<R> retry(Supplier<CompletableFuture<R>> supplier, int maxRetries) {
CompletableFuture<R> f = supplier.get();
for(int i=0; i<maxRetries; i++) {
f=f.thenApply(CompletableFuture::completedFuture)
.exceptionally(t -> {
System.out.println("retry for: "+t.getMessage());
return supplier.get();
})
.thenCompose(Function.identity());
}
return f;
}
}
usage:
public CompletableFuture<String> lucky(){
return CompletableFuture.supplyAsync(()->{
double luckNum = Math.random();
double luckEnough = 0.6;
if(luckNum < luckEnough){
throw new RuntimeException("not luck enough: " + luckNum);
}
return "I'm lucky: "+luckNum;
});
}
#Test
public void testRetry(){
CompletableFuture<String> retry = RetryUtil.retry(this::lucky, 10);
System.out.println("async check");
String join = retry.join();
System.out.println("lucky? "+join);
}
output
async check
retry for: java.lang.RuntimeException: not luck enough: 0.412296354211683
retry for: java.lang.RuntimeException: not luck enough: 0.4099777199676573
lucky? I'm lucky: 0.8059089479049389
I recently solved a similar problem using the guava-retrying library.
Callable<Result> callable = new Callable<Result>() {
public Result call() throws Exception {
return executeMycustomActionHere();
}
};
Retryer<Boolean> retryer = RetryerBuilder.<Result>newBuilder()
.retryIfResult(Predicates.<Result>isNull())
.retryIfExceptionOfType(IOException.class)
.retryIfRuntimeException()
.withStopStrategy(StopStrategies.stopAfterAttempt(MAX_RETRIES))
.build();
CompletableFuture.supplyAsync( () -> {
try {
retryer.call(callable);
} catch (RetryException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
});
Here is an approach that will work for any CompletionStage subclass and does not return a dummy CompletableFuture that does nothing more than wait to get updated by other futures.
/**
* Sends a request that may run as many times as necessary.
*
* #param request a supplier initiates an HTTP request
* #param executor the Executor used to run the request
* #return the server response
*/
public CompletionStage<Response> asyncRequest(Supplier<CompletionStage<Response>> request, Executor executor)
{
return retry(request, executor, 0);
}
/**
* Sends a request that may run as many times as necessary.
*
* #param request a supplier initiates an HTTP request
* #param executor the Executor used to run the request
* #param tries the number of times the operation has been retried
* #return the server response
*/
private CompletionStage<Response> retry(Supplier<CompletionStage<Response>> request, Executor executor, int tries)
{
if (tries >= MAX_RETRIES)
throw new CompletionException(new IOException("Request failed after " + MAX_RETRIES + " tries"));
return request.get().thenComposeAsync(response ->
{
if (response.getStatusInfo().getFamily() != Response.Status.Family.SUCCESSFUL)
return retry(request, executor, tries + 1);
return CompletableFuture.completedFuture(response);
}, executor);
}
maybe it's late but hopes someone might find this useful, I recently solved this problem for retrying rest API call on failure. In my case, I have to retry on 500 HTTP status code, below is my rest client code (we are using WSClient from play framework) you can change it to whatever rest-client as per requirement.
int MAX_RETRY = 3;
CompletableFuture<WSResponse> future = new CompletableFuture<>();
private CompletionStage<WSResponse> getWS(Object request,String url, int retry, CompletableFuture future) throws JsonProcessingException {
ws.url(url)
.post(Json.parse(mapper.writeValueAsString(request)))
.whenCompleteAsync((wsResponse, exception) -> {
if(wsResponse.getStatus() == 500 && retry < MAX_RETRY) {
try {
getWS(request, retry+1, future);
} catch (IOException e) {
throw new Exception(e);
}
}else {
future.complete(wsResponse);
}
});
return future;
}
This code will return immediately if the status code is 200 or other than 500 whereas if HTTP status is 500 it will retry 3 times.
Inspired by theazureshadow's answer. His or her answer was great but doesn't work with new version of FailSafe. The below code works with
<dependency>
<groupId>dev.failsafe</groupId>
<artifactId>failsafe</artifactId>
<version>3.3.0</version>
</dependency>
solution:
RetryPolicy<Object> retryPolicy = RetryPolicy.builder()
.withMaxRetries(MAX_RETRY)
.withBackoff(INITIAL_DELAY, MAX_DELAY, ChronoUnit.SECONDS)
.build();
Fallback<Object> fallback = Fallback.of((AuditEvent) null);
public CompletableFuture<Object> executeAsync(Runnable asyncTask) {
return Failsafe.with(fallback)
.compose(retryPolicy)
.with(executorService)
.onFailure(e -> LOG.error(e.getException().getMessage()))
.getAsync(() -> asyncTask());
}
We needed to retry a task based on an error condition.
public static <T> CompletableFuture<T> retryOnCondition(Supplier<CompletableFuture<T>> supplier,
Predicate<Throwable> retryPredicate, int maxAttempts) {
if (maxAttempts <= 0) {
throw new IllegalArgumentException("maxAttempts can't be <= 0");
}
return retryOnCondition(supplier, retryPredicate, null, maxAttempts);
}
private static <T> CompletableFuture<T> retryOnCondition(
Supplier<CompletableFuture<T>> supplier, Predicate<Throwable> retryPredicate,
Throwable lastError, int attemptsLeft) {
if (attemptsLeft == 0) {
return CompletableFuture.failedFuture(lastError);
}
return supplier.get()
.thenApply(CompletableFuture::completedFuture)
.exceptionally(error -> {
boolean doRetry = retryPredicate.test(error);
int attempts = doRetry ? attemptsLeft - 1 : 0;
return retryOnCondition(supplier, retryPredicate, error, attempts);
})
.thenCompose(Function.identity());
}
Usage:
public static void main(String[] args) {
retryOnCondition(() -> myTask(), e -> {
//log exception
return e instanceof MyException;
}, 3).join();
}
I would suggest using resilience4j for this use case. It's very handy!!
Introduction: resilience4j-retry and its Javadoc: Retry
They have method to decorate completionStage directly as below:
default <T> java.util.concurrent.CompletionStage<T> executeCompletionStage​(java.util.concurrent.ScheduledExecutorService scheduler,
java.util.function.Supplier<java.util.concurrent.CompletionStage<T>> supplier)

activity retry job after 503 error

I have many service tasks that call rest services and sometimes the service is not available. I want in my JavaDelegate to be able to infinitely retry the job:
#Override
public void execute(DelegateExecution execution)
{
try
{
//call_rest_service
}
catch (Exception503 error)
{
CommandContext commandContext = Context.getCommandContext();
JobEntity jobEntity = commandContext.getJobEntityManager().findById(job.getId());
jobEntity.setRetries(10);
//then throw original error
}
}
But this does not seem to work!
I think this is a SLOPPY way of doing things, but if you are sure that is what you want to do, I suggest you do something like this :
#Override
public void execute(DelegateExecution execution)
{
int retryMax = 10, retryCount =0;
while (retryCount++ < retryMax){
try
{
//call_rest_service
}
catch (Exception503 error)
{
// sleep to not DDoS the server
Thread.sleep(500);
}
}
}

Categories