2

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.

Example 1.1

public CompletableFuture<String> longTask(String param) {
    CompletableFuture<String> future = startLongTask(param);
    future.thenApply(sessionService::transform);
    return future;
}

Example 1.2

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.

Example 2.1

public CompletableFuture<String> longTask(String param) {
    return startTaskLongTask(param).thenApply(sessionService::transform);
}

Example 2.2

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?

Additonal info

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).

akop
  • 5,981
  • 6
  • 24
  • 51
  • 1
    In Ex 1, you have wriiten sessionService::transform and in Ex 2 session::transform . Are you sure? – Shubham Kadlag Jun 12 '18 at 10:10
  • Was a mistake, question edited. – akop Jun 12 '18 at 10:11
  • 1
    What is return type of startLongTask() ? Can you add its code? – Shubham Kadlag Jun 12 '18 at 10:12
  • 1
    Seems like in the first case it is executing the session scope method in the same thread that started the request. You still need to deal with the fact that calling request and session scoped beans async like that is not going to work because the scope is not available in a different thread. You might need a solution like this: https://stackoverflow.com/questions/23732089/how-to-enable-request-scope-in-async-task-executor – Strelok Jun 12 '18 at 10:56
  • Same reaction, question is edited. – akop Jun 12 '18 at 11:15
  • 1
    Try this and let me know the results. public CompletableFuture longTask(String param) { CompletableFuture future = startLongTask(param); return future.thenApply(sessionService::transform); } – Shubham Kadlag Jun 13 '18 at 06:51
  • What is the method signature and return type of sessionService.transform() method? – Shubham Kadlag Jun 13 '18 at 08:53
  • Same result like in Example 2.1 and 2.2. I will edit my question with the signature. – akop Jun 13 '18 at 10:45
  • an upvote to answer wouldn't be minded :) – Shubham Kadlag Jun 13 '18 at 13:15

2 Answers2

2

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. :)

Community
  • 1
  • 1
Shubham Kadlag
  • 2,248
  • 1
  • 13
  • 32
0

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));
}
akop
  • 5,981
  • 6
  • 24
  • 51