CompletableFuture - skip errors and take only validate data - java

I have a specific code which is working fine if webclient response is OK. If any error, then the get() method throwing error and the thread blocked forever.
#SneakyThrows
public List<ResponseData> validateExpression(List<RequestData> RequestDataList, Data data) {
System.out.println(Instant.now());
final List<Mono<ResponseData>> monoList = new ArrayList<>();
RequestDataList.parallelStream().forEach(requestData -> {
try {
ObjectMapper mapper = new ObjectMapper();
log.info("Diversity API request data:");
log.info(mapper.writeValueAsString(requestData));
Mono<ResponseData> monoResponse = webClient
.post()
.uri("http://...")
.contentType(MediaType.APPLICATION_JSON)
.header(API_KEY_HEADER, config.getApiKey())
.body(Mono.just(requestData), RequestData.class)
.retrieve()
.bodyToMono(ResponseData.class);
System.out.println("create mono response lazy initialization");
monoList.add(monoResponse);
} catch (Exception e) {
log.info(e.getMessage());
}
});
System.out.println(Instant.now());
CompletableFuture<List<ResponseData>> futureCount = new CompletableFuture<>();
List<ResponseData> responseDataList = new ArrayList<>();
Mono.zip(monoList, Arrays::asList)
.flatMapIterable(objects -> objects)
.doOnComplete(() -> {
futureCount.complete(responseDataList);
}).subscribe(responseData -> {
responseDataList.add((ResponseData) responseData);
});
return futureCount.get();
}
It is working fine with successful case. If there is any error from the webclient it is throwing error and thread blocked forever.
How to skip the errors and get only validate response data ?
How to avoid deadLock on this case?

You should look at CompletableFuture::get method's doc:
It throws three different checked exceptions. Lombok's #SneakyThrows annotation hides them so they aren't managed by your method. You should probably add a try/catch block to manage these exceptions and skip the errors if you want so.

Related

How to catch exception and throw my own exception inside ExpressionEvaluatingRequestHandlerAdvice and handle it through my own exception handler?

I'm using Spring integration and scatter gather pattern in my project. Here I've applied the Rest Template timeout and also I'm using ExpressionEvaluatingRequestHandlerAdvice() to catch timeout exception. But I want to catch that exception in failure flow and want to throw my own custom exception and I have my own exception handler in place to handle that exception so that I can show proper error message to the user. But here the exception is being thrown but my custom exception handler is not able to handle that exception so user is not getting my custom msg back.
//configuration
#Bean
public IntegrationFlow mainFlow() {
return flow ->
flow.split()
.channel(c -> c.executor(Executors.newCachedThreadPool()))
.scatterGather(
scatterer ->
scatterer
.applySequence(true)
.recipientFlow(flow1())
.recipientFlow(flow2()),
gatherer ->
gatherer
.releaseLockBeforeSend(true)
.releaseStrategy(group -> group.size() == 1))
.aggregate()
.to(anotherFlow());
}
#Bean
public IntegrationFlow flow2() {
return flow -> {
flow.channel(c -> c.executor(Executors.newCachedThreadPool()))
.handle(
Http.outboundGateway(
"http://localhost:4444/test", dummyService.restTemplate())
.httpMethod(HttpMethod.POST)
.expectedResponseType(String.class),
c -> c.advice(expressionAdvice()));
};
}
#Bean
public Advice expressionAdvice() {
ExpressionEvaluatingRequestHandlerAdvice advice =
new ExpressionEvaluatingRequestHandlerAdvice();
advice.setSuccessChannelName("success.input");
advice.setOnSuccessExpressionString("payload + ' was successful'");
advice.setFailureChannelName("failure.input");
advice.setOnFailureExpressionString("'Failed ' + #exception.cause.message");
advice.setReturnFailureExpressionResult(true);
advice.setTrapException(true);
return advice;
}
#Bean
public IntegrationFlow success() {
return f -> f.handle(System.out::println);
}
#Bean
public IntegrationFlow failure() {
return f ->
f.handle(
(p, h) -> {
if (p.toString().contains("Read timed out"))
throw new MyCustomException(ErrorCode.TIMEOUT_ERROR.getErrorData());
else throw new MyCustomException(ErrorCode.SERVICE_ERROR.getErrorData());
});
}
//DummyService class
#Configuration
public class DummyService {
private final int TIMEOUT = (int) TimeUnit.SECONDS.toMillis(6);
#Bean
public RestTemplate restTemplate()
{
HttpComponentsClientHttpRequestFactory requestFactory =
new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
requestFactory.setConnectTimeout(TIMEOUT);
requestFactory.setReadTimeout(TIMEOUT);
RestTemplate restTemplate = new RestTemplate(requestFactory);
return restTemplate;
}
}
Here I'm trying to throw new exception in failure() flow but exception is being thrown properly but my custom exception handler framework is not able to catch that exception. In all other cases it's able to catch but inside the spring integration configuration class it's not working.
When you do an async hand-off like with your flow.channel(c -> c.executor(Executors.newCachedThreadPool())), you cannot re-throw exceptions from that thread: the control is lost. You can have an errorChannel header populated for this flow, so an async ErrorHandler on the task executor is going to publish the ErrorMessage properly to that channel for some logic. From there you can return so-called compensation message for waiting gateway - gatherer in your case. What is very important is to preserve request headers. Otherwise it won't be able to correlated request with reply.
UPDATE
Your understanding of async error handling is a bit not correct.
The ExecutorChannel wraps a provided Executor into an ErrorHandlingTaskExecutor, which has the logic like this:
public void execute(final Runnable task) {
this.executor.execute(() -> {
try {
task.run();
}
catch (Throwable t) { //NOSONAR
ErrorHandlingTaskExecutor.this.errorHandler.handleError(t);
}
});
}
So, as you see that errorHandler is called inside the task, where re-throwing an exception from there will just end-user in void, according to your ThreadPoolExecutor:
protected void afterExecute(Runnable r, Throwable t) { }
That's why we talk about a compensation message instead of re-throwing some custom exception. That compensation message, though, could be a new ErrorMessage, but it has to preserve headers from request. something like this:
#ServiceActivator(inputChannel = "scatterGatherErrorChannel")
public Message<?> processAsyncScatterError(MessagingException payload) {
return MessageBuilder.withPayload(payload.getCause())
.copyHeaders(payload.getFailedMessage().getHeaders())
.build();
}
This message is going to be returned to the scatter-gather as a reply. Probably you are not interested in this, so you can bypass a reply for scatter-gather and propagate your custom error directly to the origin gateway's error channel. Something like this:
#ServiceActivator(inputChannel = "scatterGatherErrorChannel")
public Message<?> processAsyncScatterError(MessagingException payload) {
MessageHeaders requestHeaders = payload.getFailedMessage().getHeaders();
return MessageBuilder.withPayload(payload.getCause())
.copyHeaders(requestHeaders)
.setHeader(MessageHeaders.REPLY_CHANNEL, requestHeaders.get("originalErrorChannel"))
.build();
}
That originalErrorChannel comes really from the request message to the scatter-gather and is waited by the origin gateway.

Spring WebClient - How to handle error scenarios

We're using org.springframework.web.reactive.function.client.WebClient with
reactor.netty.http.client.HttpClient as part of Spring 5.1.9 to make requests using the exchange() method. The documentation for this method highlights the following:
... when using exchange(), it is the responsibility of the application
to consume any response content regardless of the scenario (success,
error, unexpected data, etc). Not doing so can cause a memory leak.
Our use of exchange() is rather basic, but the documentation for error scenarios is unclear to me and I want to be certain that we are correctly releasing resources for all outcomes. In essence, we have a blocking implementation which makes a request and returns the ResponseEntity regardless of the response code:
try {
...
ClientResponse resp = client.method(method).uri(uri).syncBody(body).exchange().block();
ResponseEntity<String> entity = resp.toEntity(String.class).block();
return entity;
} catch (Exception e) {
// log error details, return internal server error
}
If I understand the implementation, exchange() will always give us a response if the request was successfully dispatched, regardless of response code (e.g. 4xx, 5xx). In that scenario, we just need to invoke toEntity() to consume the response. My concern is for error scenarios (e.g. no response, low-level connection errors, etc). Will the above exception handling catch all other scenarios and will any of them have a response that needs to be consumed?
Note: ClientResponse.releaseBody() was only introduced in 5.2
The response have to be consumed when the request was made, but if you can't do the request probably an exception was be throwed before, and you will no have problems with response.
In the documentation says:
NOTE: When using a ClientResponse through the WebClient exchange() method, you have to make sure that the body is consumed or released by using one of the following methods:
body(BodyExtractor)
bodyToMono(Class) or bodyToMono(ParameterizedTypeReference)
bodyToFlux(Class) or bodyToFlux(ParameterizedTypeReference)
toEntity(Class) or toEntity(ParameterizedTypeReference)
toEntityList(Class) or toEntityList(ParameterizedTypeReference)
toBodilessEntity()
releaseBody()
You can also use bodyToMono(Void.class) if no response content is expected. However keep in mind the connection will be closed, instead of being placed back in the pool, if any content does arrive. This is in contrast to releaseBody() which does consume the full body and releases any content received.
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/reactive/function/client/ClientResponse.html
You can try to use .retrieve() instead .exchange() and handle errors as your preference.
public Mono<String> someMethod() {
return webClient.method(method)
.uri(uri)
.retrieve()
.onStatus(
(HttpStatus::isError), // or the code that you want
(it -> handleError(it.statusCode().getReasonPhrase())) //handling error request
)
.bodyToMono(String.class);
}
private Mono<? extends Throwable> handleError(String message) {
log.error(message);
return Mono.error(Exception::new);
}
In this example I used Exception but you can create some exception more specific and then use some exception handler to return the http status that you want.
Is not recommended to use block, a better way is pass the stream forward.
create some exception classes
Autowired ObjectMapper
Create a method that returns Throwable
Create a custom class for Error.
return webClient
.get()
.uri(endpoint)
.retrieve()
.bodyToMono(Model.class)
.onErrorMap(WebClientException.class, this::handleHttpClientException);
private Throwable handleHttpClientException(Throwable ex) {
if (!(ex instanceof WebClientResponseException)) {
LOG.warn("Got an unexpected error: {}, will rethrow it", ex.toString());
return ex;
}
WebClientResponseException wcre = (WebClientResponseException)ex;
switch (wcre.getStatusCode()) {
case NOT_FOUND -> throw new NotFoundException(getErrorMessage(wcre));
case BAD_REQUEST -> throw new BadRequestException(getErrorMessage(wcre));
default -> {
LOG.warn("Got a unexpected HTTP error: {}, will rethrow it", wcre.getStatusCode());
LOG.warn("Error body: {}", wcre.getResponseBodyAsString());
return ex;
}
}
}
private String getErrorMessage(WebClientResponseException ex) {
try {
return mapper.readValue(ex.getResponseBodyAsString(), HttpErrorInfo.class).getMessage();
} catch (IOException ioe) {
return ex.getMessage();
}
}

Limit max threads while using ParallelFlux

How can i limit threads for task, that being executed in parallel? The issue is simple - while my scheduler working, i can't do anything else (fetch some info using postman etc). Is there any way to solve this problem?
Also, i've tryed to set number of threads in flux, for example, using parallel(3).runOn(Schedulers.parallel()) and still my programm is blocked.
#Scheduled(fixedRate = 60000L)
#PostConstruct
public void fillMap() {
Flux.fromIterable(proxyParserService.getProxyList())
.parallel()
.runOn(Schedulers.parallel())
.flatMap(geoDataService::getData)
//some logic here...
Also worh mentioning, that i have flatmap method with opening connections in parallel:
public Mono<Address> getData(Address proxy) {
WebClient webClient = WebClient.builder()
.baseUrl(String.format(URL, proxy.getHost()))
.build();
WebClient.RequestBodyUriSpec request = webClient.method(HttpMethod.GET);
return request.retrieve()
.onStatus(HttpStatus::isError, clientResponse -> {
log.error("Error while calling endpoint {} with status code {}",
URL, clientResponse.statusCode());
throw new RuntimeException("Error while calling geolocation endpoint");
})
.bodyToMono(Address.class)

How to handle error responses in a chain of CompletableFutures?

I have a long chain of completable futures in my project, with each step calling a backend API, which can give multiple error responses and one success response. Now, after parsing the response, I need to judge if it's an error, then I need to show to the user. I also need to know which stage in my chain, produced this error.
My approach right now (shown below) is to throw a Runtime Exception whenever I encounter an error response, and then append exceptionally block to my chain. I feel that this is not the best way to do it, since a runtime exception doesn't fit in this scenario. It also makes my code ugly, since I have to do it whenever I process a response, leading to an extra exception check. Is there a better way to do it?
CompletableFuture.supplyAsync(() -> {
//some api call
Response response = request.send();
if(response.hasError()){ //this is what I am doing right now
logger.error("this is error response");
throw new ResponseErrorException("Error response received for request");
}
})
This is basically repeated for every step in the chain.
Summary: If I get a failure response in any of the steps in a CompletableFuture chain, what's a good way to propagate it to the user?
Edit: If there's no better approach, please feel free to share your views on my approach.
My suggestion is using Decorator pattern for the responses. Suggest you have something like this
CompletableFuture
.supplyAsync(() -> {
//some api call
Response response = request.send();
if(response.hasError()){ //this is what I am doing right now
throw new ResponseErrorException("Error response received for request");
}
})
.thenApply(() -> {
//some api call
Response response = request.send();
if(response.hasError()){ //this is what I am doing right now
throw new ResponseErrorException("Another Error response received for request");
}
})
.exceptionally(ex -> "Error: " + ex.getMessage());
and if you would like to avoid duplication in throwing exceptions you could use following approach
CompletableFuture
.supplyAsync(() -> {
//some api call
Response response = ThrowExceptionOnErrorResponse(request.send());
})
.thenApply(() -> {
//some api call
Response response = ThrowExceptionOnErrorResponse(request.send());
}
})
.exceptionally(ex -> "Error: " + ex.getMessage());
class ThrowExceptionOnError implements Response {
Response originalResponse;
ThrowExceptionOnError(Response originalResp) {
if(response.hasError()) {
throw new ResponseErrorException("Another Error response received for request");
}
this.originalResponse = originalResponse;
}

Exception handling in rxjava

I am trying to get accustomed to rxjava and I am trying to call the below QuoteReader in an Observable. I am not sure how to handle the exception thrown,
public class QuoteReader {
public Map<String, Object> getQuote() throws IOException{
OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder().url("http://quotes.rest/qod.json").build();
Gson gson = new Gson();
Map<String, Object> responseMap = null;
try(Response response = okHttpClient.newCall(request).execute()) {
responseMap = gson.fromJson(response.body().string(), Map.class);
System.out.println("response map : "+responseMap);
} catch(IOException ioe) {
ioe.printStackTrace();
throw ioe;
} finally {
okHttpClient = null;
request = null;
}
return responseMap;
}
}
The following is the rx code I am trying to write,
rx.Observable.just(new QuoteReader().getQuote()) //compile time error saying unhandled exception
.subscribe(System.out::println);
How should I update the code to handle the exception. Thanks!
Use fromCallable that allows your method to throw (plus, it gets evaluated lazily and not before you even get into the Observable world):
rx.Observable.fromCallable(() -> new QuoteReader().getQuote())
.subscribe(System.out::println, Throwable::printStackTrace);
There is another factory method for an Observable, which is called create. This gives you an Observer as input of a lambda expression. Observer is a kind of callback object with three methods: onNext(T t), onError(Throwable e) and onCompleted. I read you're new to RxJava, so here is a little extra as a sidenote: the Rx contract specifies that an Observer can receive zero or more onNext calls, followed by maybe either an onError or onCompleted call. In regular expression for this looks like: onNext* (onError|onCompleted)?.
Now that you know this, you can implement your operation using Observable.create:
Observable.create(observer -> {
try {
observer.onNext(new QuoteReader.getQuote());
observer.onCompleted();
}
catch (Throwable e) {
observer.onError(e);
}
}
Notice that this code doesn't do anything until you subscribe to this Observable as you already did in your question!
Observable pipeline does not allow you throw Exceptions. You must use runtimeExceptions. So changing your code it should looks like.
try(Response response = okHttpClient.newCall(request).execute()) {
responseMap = gson.fromJson(response.body().string(), Map.class);
System.out.println("response map : "+responseMap);
} catch(IOException ioe) {
ioe.printStackTrace();
new RuntimeException(ioe);
You can see a practical example here https://github.com/politrons/reactive/blob/master/src/test/java/rx/observables/errors/ObservableExceptions.java

Categories