simpleCoder
simpleCoder

Reputation: 202

Multithreading within Spring Controller "No thread-bound request found"

I am trying to use parallel streams in my Spring application but am getting a "No thread-bound request found" exception.

My code looks similar to the following:

@Controller
@RequiredArgsConstructor(onConstructor = @__(@Inject))  // yes, I'm using lombok
public class controllerClass {
   private final someOtherComponent;

   @RequestMapping(value = "/test", method = RequestMethod.Get)
   public Map<String, String> doParallelStream() {
       List<String> testStrings = Arrays.asList("one", "two", "three");

       return testStrings.parallelStream()
           .map(testStrings -> someOtherComponent.someCall(testStrings))
           .collect(Collectors.toConcurrentMap(
                    returnedString, returnedString, (p1, p2) -> p1
            ));
   }
}

My guess is that because I'm using someOtherComponent within the map inside the parallelstream that the threads spun up no longer have the context to access it.

The full error I'm getting is:

Error executing a controller { java.lang.IllegalStateException: java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.

Any suggestions on how I can get around this?

Upvotes: 2

Views: 10917

Answers (2)

Nanor
Nanor

Reputation: 2560

I faced the same error but under a different set of circumstances. I had:

public ClientSettingsDTO getClientSettings(String clientId) {

    CompletableFuture<Boolean> blacklistStatus = CompletableFuture.supplyAsync( () -> {
            return getBlacklistStatus(clientId);
        }); 
    }

    private Boolean getBlacklistStatus(String clientId) {
            return mmmBlacklistRestClient.getBlacklistClientById(clientId); // IllegalStateException
    }

The issue was resolved by configuring my own Executor bean and specifying a task decorator as so:

   @Bean(name = "executorAsyncThread")
   public TaskExecutor getAccountAsyncExecutor() {
      ThreadPoolTaskExecutor poolExecutor = new ThreadPoolTaskExecutor();
      poolExecutor.setTaskDecorator(new ContextCopyingDecorator());
      poolExecutor.setCorePoolSize(10);
      poolExecutor.setMaxPoolSize(20);
      poolExecutor.setQueueCapacity(80000);
      poolExecutor.setThreadNamePrefix("Async-Executor-");
      poolExecutor.initialize();
      return poolExecutor;
   }
public class ContextCopyingDecorator implements TaskDecorator {
   @Nonnull
   @Override
   public Runnable decorate(@Nonnull Runnable runnable) {
      RequestAttributes context = RequestContextHolder.currentRequestAttributes();
      Map<String, String> contextMap = MDC.getCopyOfContextMap();
      return () -> {
         try {
            RequestContextHolder.setRequestAttributes(context);
            MDC.setContextMap(contextMap);
            runnable.run();
         } finally {
            MDC.clear();
            RequestContextHolder.resetRequestAttributes();
         }
      };
   }
}

And passing it as the second argument in supplyAsync:

        CompletableFuture<Boolean> blacklistStatus = CompletableFuture.supplyAsync( () -> {
            return getBlacklistStatus(clientId);
        }, executor);

This allowed the request to be performed.

Upvotes: 4

Mikita Harbacheuski
Mikita Harbacheuski

Reputation: 2253

I believe your assumption is correct. Seems that component with request or session scope is used in a thread other than controller thread. And exception is thrown because of underlying usage of ThreadLocal in RequestContextHolder which is used as storage for request or session scoped beans. To make it work InheritableThreadLocal should be used. You can enabled it by setting threadContextInheritable property to true in DispatcherServlet or RequestContextFilter.

Upvotes: 4

Related Questions