Search code examples
javaspringmultithreadingtomcathttprequest

The request object has been recycled and is no longer associated with this facade


I have a custom Filter with a BlockingQueue requestQueue in my Spring application. When the http request limit is exceeded, i put a request in it and call it as the request limit is reset. But when executing an http request, an exception occurs. How i can fix this?

Exception:

java.lang.IllegalStateException: The request object has been recycled and is no longer associated with this facade
    at org.apache.catalina.connector.RequestFacade.checkFacade(RequestFacade.java:855) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
    at org.apache.catalina.connector.RequestFacade.isAsyncSupported(RequestFacade.java:733) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
    at jakarta.servlet.ServletRequestWrapper.isAsyncSupported(ServletRequestWrapper.java:378) ~[tomcat-embed-core-10.1.19.jar:6.0]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.19.jar:10.1.19]
@Component
@Slf4j
@EnableScheduling
public class ApiRequestFilter implements Filter {
    private final AtomicInteger resetCounter = new AtomicInteger(0);
    private final int MAX_NUMB_OF_REQUESTS_PER_SECOND = 2;
    private final BlockingQueue<Runnable> requestQueue = new LinkedBlockingQueue<>();
    @Override
    public void doFilter(ServletRequest servletRequest,
                         ServletResponse servletResponse,
                         FilterChain filterChain) throws IOException, ServletException {
        if (servletRequest instanceof HttpServletRequest httpServletRequest){
            resetCounter.getAndIncrement();

            String uri = httpServletRequest.getRequestURI();
            log.warn("Incoming request: {}, request counter in the present: {}", uri, resetCounter.get());

            if(resetCounter.get() > MAX_NUMB_OF_REQUESTS_PER_SECOND){

                try {
                    requestQueue.put(() -> {
                        try {
                            filterChain.doFilter(servletRequest, servletResponse);
                        } catch (IOException | ServletException e) {
                            log.error("Error when executing a request from the queue", e);
                        }
                    });
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            } else{
                filterChain.doFilter(servletRequest, servletResponse);
            }
        } else {
            ((HttpServletResponse) servletResponse).sendError(400, "Bad Request");
        }
    }

    @Scheduled(timeUnit = TimeUnit.SECONDS, fixedDelay = 1, initialDelay = 0)
    private void resetRequestCounter(){
        if (resetCounter.get() > 0) resetCounter.set(0);

        while (!requestQueue.isEmpty()) {
            try {
                requestQueue.take().run();
            } catch (InterruptedException e) {
                log.error("Error when executing a request from the queue", e);
            }
        }
    }
}

I found a similar problem, but unfortunately I did not find a solution to it :(


Solution

  • The problem you mentioned also applies to Async methods. You can proceed with what is written below.

    What is your Spring version?

    I had the same problem when I switched from Spring Boot 2.7.4 to Spring Boot 3.x.x.

    You're relying on the request to still be available after it's processed, which may not work reliably. You got rid of this problem with Spring Boot 2.7 and Tomcat 9.x as Tomcat 9.x is less aggressive about flushing when a request completes. You will experience the same failure with Tomcat 9.x if you set the org.apache.catalina.connector.RECYCLE_FACADES system property to true

    Alternatively, and this approach is not recommended, you can configure Tomcat 10.x to not discard the facade:

    @Bean
    TomcatConnectorCustomizer disabledFacadeDiscard() {
        return (binding) -> binding.setDiscardFacades(false);
    }
    

    Source: https://github.com/spring-projects/spring-boot/issues/36763