I'm struggling to use the Micronaut HTTPClient for multiple calls to a third-party REST service without receiving a io.micronaut.http.client.exceptions.ReadTimeoutException
To remove the third-party dependency, the problem can be reproduced using a simple Micronaut app calling it's own service.
Example Controller:
@Controller("/")
public class TestController {
@Inject
private TestClient client;
@Get("service")
String service() {
return "Hello World Service";
}
@Get("mproxy")
String multiproxy() {
StringBuffer sb = new StringBuffer();
for(int i=0;i<20;i++){
sb.append(client.getService());
}
return sb.toString();
}
@Get("proxy")
String proxy() {
return client.getService();
}
}
Test Client:
@Client("http://localhost:8080")
public interface TestClient {
@Get("/service")
String getService();
}
Calling the /service end-point directly from using curl, ab or postman produces no errors.
Calling the /mproxy end-point will throw an exception
ERROR i.m.r.intercept.RecoveryInterceptor - Type [clienttest.TestClient$Intercepted] executed with error: Read Timeout
io.micronaut.http.client.exceptions.ReadTimeoutException: Read Timeout
at io.micronaut.http.client.exceptions.ReadTimeoutException.<clinit>(ReadTimeoutException.java:26)
at io.micronaut.http.client.netty.DefaultHttpClient$12.exceptionCaught(DefaultHttpClient.java:2316)
at io.netty.channel.AbstractChannelHandlerContext.invokeExceptionCaught(AbstractChannelHandlerContext.java:302)
at io.netty.channel.AbstractChannelHandlerContext.invokeExceptionCaught(AbstractChannelHandlerContext.java:281)
at io.netty.channel.AbstractChannelHandlerContext.fireExceptionCaught(AbstractChannelHandlerContext.java:273)
at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireExceptionCaught(CombinedChannelDuplexHandler.java:424)
at io.netty.channel.ChannelHandlerAdapter.exceptionCaught(ChannelHandlerAdapter.java:92)
at io.netty.channel.CombinedChannelDuplexHandler$1.fireExceptionCaught(CombinedChannelDuplexHandler.java:145)
at io.netty.channel.ChannelInboundHandlerAdapter.exceptionCaught(ChannelInboundHandlerAdapter.java:143)
at io.netty.channel.CombinedChannelDuplexHandler.exceptionCaught(CombinedChannelDuplexHandler.java:231)
at io.netty.channel.AbstractChannelHandlerContext.invokeExceptionCaught(AbstractChannelHandlerContext.java:302)
at io.netty.channel.AbstractChannelHandlerContext.invokeExceptionCaught(AbstractChannelHandlerContext.java:281)
at io.netty.channel.AbstractChannelHandlerContext.fireExceptionCaught(AbstractChannelHandlerContext.java:273)
at io.netty.handler.timeout.ReadTimeoutHandler.readTimedOut(ReadTimeoutHandler.java:98)
at io.netty.handler.timeout.ReadTimeoutHandler.channelIdle(ReadTimeoutHandler.java:90)
at io.netty.handler.timeout.IdleStateHandler$ReaderIdleTimeoutTask.run(IdleStateHandler.java:504)
at io.netty.handler.timeout.IdleStateHandler$AbstractIdleTask.run(IdleStateHandler.java:476)
at io.netty.util.concurrent.PromiseTask.runTask(PromiseTask.java:98)
at io.netty.util.concurrent.ScheduledFutureTask.run(ScheduledFutureTask.java:170)
at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:164)
at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:472)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:500)
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:831)
Alternatively, the same exception is thrown if the /proxy endpoint is tested via ab
ab -c 5 -n 200 localhost:8080/proxy
or via mulitple calls with postman.
This is for micronaut version 2.5.5 with an absolutely vanilla template app, no connection-pooling or time-outs specified in application.yml.
It appears to error after 4 connections/clients but changing connection-pooling and timeouts seems to not change the result. Am I missing some client config?
If this isn't going to throw an exception then I don't know what is going to.
This is caused by using blocking
code within Netty's event loop
.
The code over here is making a blocking request 20 times in a row which cause the machine to break. I don't know what data is coming from the client but I would never recommend to do it in this manner.
for(int i=0;i<20;i++){
sb.append(client.getService());
}
Key message: don't block the event loop
To solve this what you can do is make your request Asynchronous
. To do this
make use of RxJava
. RxJava allows you to perform the operations in asynchronous manner. It provides you some very useful observables and operators.
The only other way: Run this operator on another thread so, that the main thread doesn't get blocked but this may not work very efficiently and still cause problem.
To get start with RxJava follow the link: https://factoryhr.medium.com/understanding-java-rxjava-for-beginners-5eacb8de12ca
Micronaut Tutorial Reactive: https://piotrminkowski.com/2019/11/12/micronaut-tutorial-reactive/