Reputation: 202
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
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
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