0

I am facing an issue regarding @Async task with HttpSession. I want to save the data in session in an async method. What happens is the response is returned correctly but I get an error while saving data in session. Please help.

My controller file:

@RequestScope
@Controller
public class SomeController {

    @Autowired
    private ISomeService iSomeService;

    @ResponseBody
    @GetMapping("/some-func")
    public String someFunc() {
        iSomeService.myAsyncFunction("hello");
        return "ok";
    }
}

My Service file:

@RequestScope
@Service
public class SomeServiceImpl implements ISomeService {

    @Autowired
    private HttpSession session;

    @Async("threadPoolTaskExecutor")
    @Override
    public void myAsyncFunction(String s) {

        session.setAttribute("key", s);
    }
}

My configuration file:

@EnableAsync
@Configuration
public class ThreadConfig {

    @Bean(name = "threadPoolTaskExecutor")
    public Executor threadPoolTaskExecutor() {
        return new ThreadPoolTaskExecutor();
    }
}

My Stacktrace:

2019-03-07 17:23:16.065 ERROR 29179 --- [lTaskExecutor-1] .a.i.SimpleAsyncUncaughtExceptionHandler : Unexpected error occurred invoking async method: public void com.example.demo.SomeServiceImpl.myAsyncFunction(java.lang.String)

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.
    at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131) ~[spring-web-4.3.22.RELEASE.jar:4.3.22.RELEASE]
    at org.springframework.web.context.support.WebApplicationContextUtils.currentRequestAttributes(WebApplicationContextUtils.java:309) ~[spring-web-4.3.22.RELEASE.jar:4.3.22.RELEASE]
    at org.springframework.web.context.support.WebApplicationContextUtils.access$400(WebApplicationContextUtils.java:64) ~[spring-web-4.3.22.RELEASE.jar:4.3.22.RELEASE]
    at org.springframework.web.context.support.WebApplicationContextUtils$SessionObjectFactory.getObject(WebApplicationContextUtils.java:366) ~[spring-web-4.3.22.RELEASE.jar:4.3.22.RELEASE]
    at org.springframework.web.context.support.WebApplicationContextUtils$SessionObjectFactory.getObject(WebApplicationContextUtils.java:361) ~[spring-web-4.3.22.RELEASE.jar:4.3.22.RELEASE]
    at org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler.invoke(AutowireUtils.java:307) ~[spring-beans-4.3.22.RELEASE.jar:4.3.22.RELEASE]
    at com.sun.proxy.$Proxy93.setAttribute(Unknown Source) ~[na:na]
    at com.example.demo.SomeServiceImpl.myAsyncFunction(SomeServiceImpl.java:21) ~[classes/:na]
    at com.example.demo.SomeServiceImpl$$FastClassBySpringCGLIB$$9dbf1607.invoke(<generated>) ~[classes/:na]
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) ~[spring-core-4.3.22.RELEASE.jar:4.3.22.RELEASE]
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:736) ~[spring-aop-4.3.22.RELEASE.jar:4.3.22.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) ~[spring-aop-4.3.22.RELEASE.jar:4.3.22.RELEASE]
    at org.springframework.aop.interceptor.AsyncExecutionInterceptor$1.call(AsyncExecutionInterceptor.java:115) ~[spring-aop-4.3.22.RELEASE.jar:4.3.22.RELEASE]
    at java.util.concurrent.FutureTask.run(FutureTask.java:266) [na:1.8.0_191]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_191]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_191]
    at java.lang.Thread.run(Thread.java:748) [na:1.8.0_191]
sam
  • 1,800
  • 1
  • 25
  • 47
  • You cannot access the `session` from another thread. As the request has ended and the session is not available anymore after that. – M. Deinum Mar 07 '19 at 13:34
  • I was able to save and retrive data from session but when i swithced to Async i was not. Initially @dkb 's solution solved the exception problem, but i could get the data while i issue getAttribute from session – sam Mar 07 '19 at 13:37
  • because that won't work with a `Session`... When using `@Async` the request handling thread stops and hands it over and the response is rendered. After that the attribute will be set. You should not mess around with request/session in backend processes like this. If you want to use async request handling then do that but that is something else as calling an `@ASync` method from a controller. – M. Deinum Mar 07 '19 at 13:39

2 Answers2

4

On top of my head, If We have @Async annotations then I think return type should be of type CompletableFuture like a promise in javascript.
So by changing the return type to service method from void to CompletableFuture<Void> solves the problem.

So I modified the code as below:

@Async("threadPoolTaskExecutor")
@Override
public CompletableFuture<Void> myAsyncFunction(String s) {

    session.setAttribute("key", s);
    return new CompletableFuture<Void>();
}

// To get session object in Async // This is bad code but check suitability

So here I am getting session object from the HttpRequest and passing it to Service, You add parameters at service and check those parameters at the controller.
Do not autowire session object at service or controller.

@RequestScope
@Controller
public class SomeController {

@Autowired
private SomeServiceImpl iSomeService;

@ResponseBody
@GetMapping("/some-func")
public String someFunc(HttpServletRequest request) throws ExecutionException, InterruptedException {
    HttpSession session = request.getSession();
    CompletableFuture<HttpSession> completableFuture= iSomeService.myAsyncFunction(session, "hello");
    HttpSession session1=completableFuture.get();
    System.out.println(session1.getAttribute("key"));
    return "ok";
}
}

@RequestScope
@Service
public class SomeServiceImpl {

@Async("threadPoolTaskExecutor")
public CompletableFuture<HttpSession> myAsyncFunction(HttpSession session, String s) {
    session.setAttribute("key", s);
    return CompletableFuture.completedFuture(session);
}
}
dkb
  • 4,389
  • 4
  • 36
  • 54
  • Glad, it helped – dkb Mar 07 '19 at 12:56
  • It wasn't showing any error but i couldn't get my data from the session using `session.getAttribute("key")` – sam Mar 07 '19 at 13:17
  • So you are trying to access singleton `session` object from multiple threads(created from your custom threadPoolTaskExecutor) and wanted to preserve the state of session for each thread?, – dkb Mar 07 '19 at 13:30
  • I didn't understand what you said, but all my beans are of request scope. I was able to store and retrieve data from session but after using Async i was not. Your solution initially solved the exception problem, but i am not able to get the value from session – sam Mar 07 '19 at 13:34
  • Yes, because without Async, entire request stack has same request context and no other threads present, so if you remove Async, you can still get the same working. – dkb Mar 07 '19 at 13:51
  • check this, hope it will help: https://stackoverflow.com/a/33337838/2987755 – dkb Mar 07 '19 at 13:52
  • I tried using `CompletableFuture` but that too didn't help – sam Mar 07 '19 at 13:55
  • the above link didn't help – sam Mar 07 '19 at 14:00
  • Thank You for your time. Seems like I shouldn't mess with async and session. but I learned new thing from your implementation. – sam Mar 08 '19 at 05:17
  • Last try, can you add above code below `// To get session` text, and check – dkb Mar 08 '19 at 05:27
  • Hell yeah. that worked. thank you so much for your time. – sam Mar 08 '19 at 05:50
  • i tried to retrieve the session from a different mapping too and it worked. – sam Mar 08 '19 at 05:51
  • The solution is concise and efficient and it cannot be found in any Stackoverflow thread. An upvote to the question will make the question appear more in searches, for the users facing such an issue. – sam Mar 10 '19 at 17:09
1

According to Async java doc. void is a legal return type.

In terms of target method signatures, any parameter types are supported. However, the return type is constrained to either {@code void} or {@link java.util.concurrent.Future}. In the latter case, you may declare the more specific {@link org.springframework.util.concurrent.ListenableFuture} or {@link java.util.concurrent.CompletableFuture} types which allow for richer interaction with the asynchronous task and for immediate composition with further processing steps.

I think this link may help you.

chaos
  • 1,359
  • 2
  • 13
  • 25
  • the solution by @dkb is very concise and working efficiently and it cannot be found in any Stackoverflow thread. An upvote to the question will make the question appear more in searches, for the users facing such an issue. – sam Mar 10 '19 at 10:37