Reputation: 7891
We a have very strange reaction in our application.
The application has a (singleton) service - let us call it singletonService
.
And a other service which runs in a session-scope. sessionService
.
The singletonService
has a method like this CompletableFuture<String> longTask(String param)
and after the long task, the singletonService
has to call the sessionService
(for the method String transform(String param)
).
When we write longTask()
like Example 1 then all works fine.
The transform
method runs as expected.
public CompletableFuture<String> longTask(String param) {
CompletableFuture<String> future = startLongTask(param);
future.thenApply(sessionService::transform);
return future;
}
public CompletableFuture<String> longTask(String param) {
CompletableFuture<String> future = startLongTask(param);
future.thenApplyAsync(sessionService::transform, asyncExecutor);
return future;
}
But in this way we dont wait for transform
method.
Its better to write it like Example 2.
public CompletableFuture<String> longTask(String param) {
return startTaskLongTask(param).thenApply(sessionService::transform);
}
public CompletableFuture<String> longTask(String param) {
return startTaskLongTask(param).thenApplyAsync(sessionService::transform, asyncExecutor);
}
But Example 2 will always finish exceptionally. It throws a org.springframework.beans.factory.BeanCreationException
.
The whole exception:
Scope 'session' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is 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.
Has anyone hints, where we have to search for the problem?
The signature of startLongTask()
is this: private CompletableFuture<String> startLongTask(String param)
.
To keep it simple, I wrote the method in this way.
In real, its retrofit that calls a RESTful-api.
The asyncExecutor
is a bean for spring.
/**
* Creates a context aware {@link Runnable} that wraps the original one.
*
* @param task The original {@link Runnable}
* @return The wrapper
*/
private Runnable getRunnable(final Runnable task) {
try {
final RequestAttributes attr = RequestContextHolder.currentRequestAttributes();
return () -> {
try {
RequestContextHolder.setRequestAttributes(attr);
} catch (final Exception ignored) {
}
task.run();
try {
RequestContextHolder.resetRequestAttributes();
} catch (final Exception ignored) {
}
};
} catch (final Exception ignored) {
return task;
}
}
The signature for the transform()
is String transform(String param)
. The function uses session-relevant data (the locale of current user).
Upvotes: 2
Views: 2838
Reputation: 7891
We found a workaround for the problem. Shubham Kadlag asked the right questions. :)
The sessionService
needs informations from the session -> the locale of the user.
But why we try to obtain the locale after the longTask()
(which runs in other threads)?
Now, we obtain the locale before the longTask()
and give it as parameter to the transform()
. So transform
is not longer dependent from the session and is now inside of the singletonService
.
Look this example:
public CompletableFuture<String> longTask(String param) {
Locale locale = sessionService.getLocale();
return startTaskLongTask(param).thenApply(result -> transform(result, locale));
}
Upvotes: 0
Reputation: 2318
As per your Inputs:
Example 1 (Works wells):
public CompletableFuture<String> longTask(String param) {
CompletableFuture<String> future = startLongTask(param);
future.thenApply(sessionService::transform);
return future;
}
Example 2 (Doesn't Work):
public CompletableFuture<String> longTask(String param) {
CompletableFuture<String> future = startLongTask(param);
return future.thenApply(sessionService::transform);
}
As you see the only difference in the two examples is that in Example 1 you are returning the CompletableFuture<String> which you created and in Example 2 you are returning the then.Apply() method;
Oracle documentation for thenApply method: https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html#thenApply-java.util.function.Function-
public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)
Description copied from interface: CompletionStage Returns a new CompletionStage that, when this stage completes normally, is executed with this stage's result as the argument to the supplied function. See the CompletionStage documentation for rules covering exceptional completion.
Specified by: thenApply in interface CompletionStage<T>
Type Parameters: U - the function's return type
Parameters: fn - the function to use to compute the value of the returned CompletionStage
Returns: the new CompletionStage
The Error description in the question says:
If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet.
This is probably the reason here, the CompletableFuture object created by the thenApply is outside of DispatcherServlet/DispatcherPortlet.
Hope you get it now. :)
Upvotes: 2