I send request to webflux demo server. use springboot version 2.4.1 , create no quantity limit nuboundedElastic-evictor thread and always keep timed waiting status.
test script
ab -n 10000 -c 1000 -T "application/x-www-form-urlencoded" -p test.txt http://127.0.0.1:8080/router/rest/1
controller code
@RestController
@RequestMapping("/router/rest")
public class BaseController {
@RequestMapping("1")
public Mono<String> mono(){
return Mono.just("111");
}
}
lots of boundedElastic-evictor TIMED_WAITING make memory up obvious.
"boundedElastic-evictor-9998" #10042 daemon prio=5 os_prio=0 cpu=0.00ms elapsed=130.64s tid=0x0000023b82f71dc0 nid=0xe694 waiting on condition [0x0000008cf91fe000]
java.lang.Thread.State: TIMED_WAITING (parking)
at jdk.internal.misc.Unsafe.park(java.base@15.0.1/Native Method)
- parking to wait for <0x000000070aba6988> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.parkNanos(java.base@15.0.1/LockSupport.java:252)
finally
Switching to version 2.3.7 resolved
Comment From: rstoyanchev
Can you provide more details around "no quantity limit nuboundedElastic-evictor thread"? I'm also not sure what you mean by "make memory up obvious".
Comment From: Justubborn
Can you provide more details around "no quantity limit nuboundedElastic-evictor thread"? I'm also not sure what you mean by "make memory up obvious".
after send 1000 request ,the program create 1000 thread name "boundedElastic-evictor" and java memory from 50M increase to 500M
Comment From: jun-lee
+1 Spring boot 2.4.0 + Webflux has just the same issue.
Comment From: rstoyanchev
Can you provide more details around "no quantity limit nuboundedElastic-evictor thread"?
I still need this. For example a snippet of how you configure this.
Comment From: rstoyanchev
Actually is it possible to provide a sample app? It's unclear where the boundedElastic
use comes from.
Comment From: Justubborn
Actually is it possible to provide a sample app? It's unclear where the
boundedElastic
use comes from.
Below is all the code, no additional configuration:
@RestController
@RequestMapping("/router/rest")
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@RequestMapping("1")
public Mono<String> mono(){
return Mono.just("111");
}
}
Below is the thread stack. You can tell from the stack log where boundedElastic
came from.
"reactor-http-nio-2@5381" daemon prio=5 tid=0x2a nid=NA runnable
java.lang.Thread.State: RUNNABLE
at reactor.core.scheduler.BoundedElasticScheduler.lambda$static$0(BoundedElasticScheduler.java:75)
at reactor.core.scheduler.BoundedElasticScheduler$$Lambda$382.45019084.newThread(Unknown Source:-1)
at java.util.concurrent.ThreadPoolExecutor$Worker.<init>(ThreadPoolExecutor.java:619)
at java.util.concurrent.ThreadPoolExecutor.addWorker(ThreadPoolExecutor.java:932)
at java.util.concurrent.ThreadPoolExecutor.ensurePrestart(ThreadPoolExecutor.java:1603)
at java.util.concurrent.ScheduledThreadPoolExecutor.delayedExecute(ScheduledThreadPoolExecutor.java:334)
at java.util.concurrent.ScheduledThreadPoolExecutor.scheduleAtFixedRate(ScheduledThreadPoolExecutor.java:573)
at reactor.core.scheduler.BoundedElasticScheduler.start(BoundedElasticScheduler.java:175)
at reactor.core.scheduler.Schedulers.newBoundedElastic(Schedulers.java:498)
at reactor.core.scheduler.Schedulers.newBoundedElastic(Schedulers.java:454)
at org.springframework.http.codec.multipart.DefaultPartHttpMessageReader.<init>(DefaultPartHttpMessageReader.java:77)
at org.springframework.http.codec.support.ServerDefaultCodecsImpl.extendTypedReaders(ServerDefaultCodecsImpl.java:72)
at org.springframework.http.codec.support.BaseDefaultCodecs.getTypedReaders(BaseDefaultCodecs.java:275)
at org.springframework.http.codec.support.BaseCodecConfigurer.getReaders(BaseCodecConfigurer.java:98)
at org.springframework.http.codec.support.DefaultServerCodecConfigurer.getReaders(DefaultServerCodecConfigurer.java:27)
at org.springframework.web.server.adapter.DefaultServerWebExchange.initFormData(DefaultServerWebExchange.java:143)
at org.springframework.web.server.adapter.DefaultServerWebExchange.<init>(DefaultServerWebExchange.java:131)
at org.springframework.web.server.adapter.HttpWebHandlerAdapter.createExchange(HttpWebHandlerAdapter.java:243)
at org.springframework.web.server.adapter.HttpWebHandlerAdapter.handle(HttpWebHandlerAdapter.java:229)
at org.springframework.boot.web.reactive.context.WebServerManager$DelayedInitializationHttpHandler.handle(WebServerManager.java:97)
at org.springframework.http.server.reactive.ReactorHttpHandlerAdapter.apply(ReactorHttpHandlerAdapter.java:65)
at org.springframework.http.server.reactive.ReactorHttpHandlerAdapter.apply(ReactorHttpHandlerAdapter.java:40)
at reactor.netty.http.server.HttpServer$HttpServerHandle.onStateChange(HttpServer.java:628)
at reactor.netty.ReactorNetty$CompositeConnectionObserver.onStateChange(ReactorNetty.java:612)
at reactor.netty.transport.ServerTransport$ChildObserver.onStateChange(ServerTransport.java:453)
at reactor.netty.http.server.HttpServerOperations.onInboundNext(HttpServerOperations.java:510)
at reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:94)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
at reactor.netty.http.server.HttpTrafficHandler.channelRead(HttpTrafficHandler.java:208)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436)
at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:324)
at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:311)
at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:432)
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:276)
at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:719)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:655)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:581)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493)
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.lang.Thread.run(Thread.java:748)
Comment From: jun-lee
Ok, I found the root cause.
https://github.com/spring-projects/spring-framework/blob/499be70a717b8d20c544bc2eac4fe5dacedc7f28/spring-web/src/main/java/org/springframework/http/codec/support/ServerDefaultCodecsImpl.java#L72
https://github.com/spring-projects/spring-framework/blob/499be70a717b8d20c544bc2eac4fe5dacedc7f28/spring-web/src/main/java/org/springframework/http/codec/multipart/DefaultPartHttpMessageReader.java#L77
From the above points, a Http request has a Content-Type: "application/x-www-form-urlencoded" causes boundedElastic-evictor-# garbage everytime.
Comment From: jun-lee
A quick workaround would be adding a config.
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@Override
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
DefaultPartHttpMessageReader multipartReader = new DefaultPartHttpMessageReader();
configurer.defaultCodecs().multipartReader(multipartReader);
}
}
Comment From: rstoyanchev
Thanks for the extra details. This is a combination of DefaultPartHttpMessageReader
creating resources internally and DefaultServerWebExchange
re-creating the codecs per request. We need to make this more efficient and also ensure those resources support a full lifecycle with start and stop.