Yongqin Xu opened SPR-15580 and commented

I am using SpringBoot 1.5.2 release. I have an asynchronous REST call that spawns separate java thread to finish a long running job. i use Postman to send POST request to async REST api. I set DispatcherServlet's setThreadContextInheritable() to true, so that my ServletRequestAttributes instance can be passed to child thread for its task. Debugging shows the ServletRequestAttributes instance is set inactive status and cached in RequestContextHolder when child thread in async call and that stops child thread to get some values from the attributes object. Debugging shows at line 989 of FrameworkServlet.java, it calls requestCompleted() right after parent thread process the request. Here is my Application.java snippets:

 @SpringBootApplication(exclude = DispatcherServletAutoConfiguration.class)
 @EnableAsync 
 public class Application extends AsyncConfigurerSupport {
  @Override
  public Executor getAsyncExecutor() {
    SimpleAsyncTaskExecutor executor = new 
    SimpleAsyncTaskExecutor(appProperties.threadNamePrefix);
    return executor;
  }
  @Bean
  public ServletRegistrationBean registerAirportDispatchServlet() {
    DispatcherServlet dispatcherServlet = new DispatcherServlet();
    AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
    dispatcherServlet.setApplicationContext(applicationContext);
    dispatcherServlet.setThreadContextInheritable(true);
    ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(dispatcherServlet, "/*");
    servletRegistrationBean.setName("MyDispacherServlet");
    return servletRegistrationBean;
  }
}

My REST Controller async snippets:

@RestController
public class AsyncController {
  @RequestMapping(path = "/async", method = RequestMethod.POST)
  @Async
  public DeferredResult<ResponseEntity<String>> doAsync(
    DeferredResult<ResponseEntity<String>> result = new DeferredResult<>();
    ServletRequestAttributes attributes =  
(ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
    result.setResult(doLongTask());
    return result;
  }
}

You can easily verify the attributes object returned from RequestContextHolder is in inactive. This will cost issue down the path especially when child thread enables JPA auditing and updates DB data where child thread need to access current security token stored in ServletRequestAttributes instance from RequestContextHolder. i got error message: "Cannot ask for request attribute - request is not active anymore!"

I think for async REST calls, if inheritable flag is set to true, FrameworkServlet shouldn't mark the request attributes to inactive especially child thread hasn't finish its job yet. Should leave the attributes instance as it is in RequestContextHolder for child thread.


Affects: 4.3.6

Reference URL: https://stackoverflow.com/questions/44121654/inherited-servletrquestattributes-is-marked-completed-before-child-thread-finish

Comment From: bclozel

Sorry for the late reply here.

I think this is working as designed. The RequestContextHolder is only meant to provide attributes and allow child threads to inherit those values, but only during the lifetime of the request. We cannot delay the request lifecycle as you've explicitly spawned a new thread to decouple this processing from the HTTP exchange. You could indeed reuse this thread for other requests and we need to consistently clear the request attributes when the request has been processed.

I would suggest copying the relevant request information to a custom context object and ensure to carefully manage how that thread can be reused.

Thanks!