WebClient hanging when using share() followed by block() call but calling block() only returns with an error - spring-batch

I am using the Spring WebFlux WebClient in a spring batch application and I am getting an error when I call block. The code is really simple but I am getting an error when the application I try launching a job from a Rest endpoint on a controller in the batch job.
The rest endpoint is like this:
#RequestMapping("/migration/products/catalog
class ProductController{
private final Job job;
ResponseEntity<Map<String,Object> loadProductCatalog(){
// Code to launch Product Catalog Job
}
}
Here is the method the calls a remote client to get Product Catalog information that can be used by the Controller to load information about products
public ProductInfo findProductInfo() {
try{
String url =....;
return webClient.get().uri(url)
.accept(MediaType.APPLICATION_JSON).retrieve().
bodyToMono(ProductInfo.class).share().block();
}catch(Exception e){
log.error("Exception during retrieval of ProductInfo Data [}", e);
return null;
}
}
The findProductInfo method is wrapped in a Service which is used to retrieve ProductInfo in the Controller.
I am using share() because the block() call to the Rest controller just hangs.
However, if I simply call block() with first calling share() the call to the controller returns but throws the following error. I'm very new to using WebFlux so I have no idea what is going on. I'd appreciate some help in deciphering what is going on and a solution to this problem
java.lang.IllegalStateException: block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-nio-2
When I use share() followed by block() my application hangs when I invoke the rest endpoint. However, if I use block() alone the method returns

Solved: My job was running in a single thread, so the share().block() was blocking the main thread of the job launcher. I figured out that from observing the task executor was synchronous and that gave me the clue, it suddenly made sense. I configured a task executor so the job would run in its own thread and viola!

Related

Vertx not able to handle internal code error to client automatically

I have a verticle which accepts REST request, get data from other verticle through event bus and respond back to client.
vertx.exceptionHandler(event -> logger.error("Vertx exception ", event));
router.get("/api/v1/:param").handler(this::routerHandler);
public void routerHandler(RoutingContext rc) {
vertx.eventBus().request("data", param,
result -> {
if (result.succeeded()) {
logger.info("Request handled successfully");
// intentionally creating exception body() will return String
JsonObject jsonObject = (JsonObject) result.result().body();
rc.response().end(jsonObject)
}else{
logger.error("Request failed");
}
}
When a exception is raised it is printed in exception handler that I setup in vertx instance but after that the vertx is not reporting back the exception immediately to client instead it waits for timeout(30 secs) to occur.
I tried attaching error handler to router object and failure handler to route object but nothing helps to report the exception immediately to client. I know I can have a try catch and report the error in catch block. But I want know if there is any other way to handle this like Servlet or Spring MVC reports back to client even though the exception is not handled in code.
router.errorHandler(500,routingContext -> {
System.out.println(routingContext.failed());
routingContext.response().end("Exception ");
});
router.route().handler(BodyHandler.create()).failureHandler(routingContext -> {
Uncaught exceptions are reported to the context exceptionHandler. By default it prints the exception to the console.
You can configure it but you will not get a reference to the corresponding HTTP request anyway (the exception may come from different things).
Most problems like this are usually found during unit/integration/acceptance testing.
And for the remainders you could set a timeout handler on your router definition to make sure the request is ended before the default 30 seconds.
If you don't want to miss any uncaught exception, you should switch to the Vert.x Rxified API. When using RxJava, any exception thrown will be reported to the subscriber.

Webflux WebClient asynchronous Request and processing Mono

I am new to webflux and am not able to find the right material to continue with the implementation.
I want to issue a request and process the response asynchronously. In this case service call takes about 8-10 ms to respond, so we issue the request and continue doing other work, and look for the response when it is needed for further processing.
Mono<Map<String,Price>> resp = webClient.post()
.uri("/{type}",isCustomerPricing ? "customer" : "profile")
.body(Mono.just(priceDetailsRequest),PriceDetailsRequest.class)
.retrieve().bodyToMono(customerPriceDetailsType);
How do we make this call execute asynchronously on a different thread.(I tried subscriberOn with Schedulers.single/ Scheuldes.parallel), but didn't see the call getting executed until Mono.block() is called.
How do we achieve ?
We want this call execute in parallel on a separate thread, so the
current thread can continue with other work
When processing completes, set response to context
When the current thread looks for the response, if the service has not
completed, block until the call completes
You don't need to block for consuming the response. Just assign an operator to consume the response in the same chain. An example is given below.
Mono<Map<String,Price>> resp = webClient.post()
.uri("/{type}",isCustomerPricing ? "customer" : "profile")
.body(Mono.just(priceDetailsRequest),PriceDetailsRequest.class)
.retrieve()
.bodyToMono(CustomerPriceDetailsType.class)
.map(processor::responseToDatabaseEntity) // Create a persistable entity from the response
.map(priceRepository::save) // Save the entity to the database
.subscribe(); //This is to ensure that the flux is triggered.
Alternatively you can provide a consumer as a parameter of the subscribe() method.

vertx timeout if async result is failed

I am seeing a timeout in the browser when the server-side service ends in a failed result. Everything works fine if the service call succeeds but it seems as though the browser never receives a response if the call fails.
My service passes a result handler to a DAO containing the following code:
final SQLConnection conn = ar.result();
conn.updateWithParams(INSERT_SQL, params, insertAsyncResult -> {
if (insertAsyncResult.failed()) {
conn.close();
resultHandler.handle(ServiceException.fail(1, "TODO"));
} else {
resultHandler.handle(Future.succeededFuture());
}
});
I'm not sure where to go from here. How do I debug what the framework is sending back to the client?
The problem was that I needed to register a ServiceExceptionMessageCodec in an intermediate Verticle, one that was sitting between the browser and the Verticle that was performing the database operation.

Spring Cloud Netflix: Remote service error handling with Feign

We are trying to put Spring Cloud Netflix into production environment. For now we encounter a problem about business logic error handling.
We're using Feign as HTTP REST client. Microservice A needs to invoke microservice B which is deployed in different JVM(or physical server). Microservice B may return some error message which belongs to business. For instance A needs to query order information from B but the order ID may not exist so B has to return the error message that tells A this order doesn't exist. A has to do if-else judgement from the return message to determine if there are erorrs, then code will be like the following snippet:
//remoteServiceA is an interface annotated with #FeignClient
resultA = remoteServiceA.foo();
if (resultA.hasError) {
} else {
}
resultB = remoteServiceB.foo();
if (resultB.hasError) {
} else {
}
// ... ...
There are so many if-else so that it's not graceful enough. What we want is remoteServieA.foo() can throw a self-defined runtime exception such as OrderNotExistException. Any idea to achieve this goal?
I've solved this problem.
I customized the ErrorDecoder component of Feign, in which I can throw my own exception according to the HTTP original response.
If you have Hystrix enabled, you should be able to wrap you serviceA.foo() in a try block and throw an exception in your remote service.
try {
serviceA.foo();
} catch(HystrixRuntimeException ex) {
throw new OrderNotExistException("Error message");
}
You still have to take in account that you can catch that kind of exception if your remote service doesn't answer, or if an other error happens. Maybe you can find information the exception about what happened and decide if you should throw your exception.
First thing that comes to my mind but work in one of my project.

Hystrix getting access to the current execution state within fallback

I successfully configured spring-cloud (via spring-cloud-starter-hystrix) to wrap a call to a service.
This all works fine and looks like the following:
#Component
public class MyService {
#HystrixCommand(fallbackMethod = "fallback")
public void longRunning() {
// this could fail
}
public void fallback() {
// fallback code
}
}
My question now is, I would like to log some statistics about the execution error in longRunning()
Trying to access HystrixRequestLog.getCurrentRequest() within the fallback method throws
java.lang.IllegalStateException: HystrixRequestContext.initializeContext() must be called at the beginning of each request before RequestVariable functionality can be used.
I am looking for a simple way to log the exception of longRunning if the fallback is called.
testing with v1.0.0.RC2
To see a stack trace you can just enable DEBUG logging in com.netflix.hystrix.
As far as I can tell, to use the HystrixRequestContext the caller of MyService has to call HystrixRequestContext.initializeContext() before using the service. That sucks, so if anyone has a better idea, I'm interested.
Starting from Javanica v1.4.21, it allows fallback method to have an argument of Throwable type for accessing the command execution exception like so:
public void fallback(Throwable e) {
// fallback code
LOGGER.error(e.getMessage());
}
To get this feature, your build config needs to override the older version of Javanica pulled in by Spring Cloud.