Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

getOrElseUpdate and http context #130

Closed
tzimisce012 opened this issue Nov 9, 2017 · 14 comments
Closed

getOrElseUpdate and http context #130

tzimisce012 opened this issue Nov 9, 2017 · 14 comments
Labels
Milestone

Comments

@tzimisce012
Copy link

I am having the following issue. When I use this java method I am loosing the http context.

Reporting a bug?

  1. Version of play-redis: 1.6.0
  2. Version of Scala 2.12
  3. Version of Play 2.6.6
  4. Describe the environment if applies (usually in cloud etc.)
  5. Describe the expected behavior: it should return a CompletionStage with the http.context
@KarelCemus
Copy link
Owner

Hi, I am sorry, I am not much familiar with Java API, I use only Scala version. Could you give me more details about the context and how it is stored? Some link would be great to know what I am looking for.

Thanks

@tzimisce012
Copy link
Author

tzimisce012 commented Nov 9, 2017

The context is stored on the thread local. When you do something like:
CompletableFuture.supplyAsyn(()-> doSomething()) you are executing a task on the common-fork-join and leaving behind the akka dispatcher from play. That means that if you later do something like:

promise.thenApply(something -> something + request().getHeader("myheader")) you will get a RuntimeException since the method request() is trying to get the Http context and you don't have it anymore.

Play offers you a class called HttpExecutionContext that will allows you execute a asynchronous task in the same akka dispatcher and keeping your http context available as explained here. Even though you are still in the same executor, you might jump to a different thread, so you still need to move the http context from the previous thread to the new one.

In scala you don't have these problems, but if you are doing a java version you still need to be aware of this topic. In the class JavaRedis you are doing Future.sequence() and implicitly you are passing your execution context. When you do this, the CompletionStage that you return does not have the http context anymore. Still, scala play api offers you a nice utility method called fromThread that would allow you to assign the context to the new thread of the executor. My knowledge on scala ends here, so I hope you have enough information to solve the problem from there.

@johnsiu
Copy link

johnsiu commented Nov 9, 2017

Not all async APIs take Executor from the caller, so we can't expect them to do any context propagation for you.

Since you are using the Java API, the proper way to propagate context is to use the async versions of the CompletionStage API and pass in the injected HttpExecutionContext.

Using your example:
promise.thenApplyAsync(something -> something + request().getHeader("myheader"), httpExecutionContext.current())

@tzimisce012
Copy link
Author

tzimisce012 commented Nov 9, 2017

if the promise comes from something like:
CompletionStage<Product> promise = cache.getOrElseUpdate("asdf", ()-> respository.getFuture(), 5);
then I have lost the context and I will not recover it even if I pass the injected HttpExecutionContext again

@johnsiu
Copy link

johnsiu commented Nov 9, 2017

It doesn't matter whether the first stage has the context or not.
The second stage will run with the context captured in the Executor, assuming you set up the stage in an Action/Controller (or any place where the current context is still in scope in the executing thread).

@tzimisce012
Copy link
Author

and what happens if I need the context inside of the callable in the getOrElseUpdate method? I mean,
cache.getOrElseUpdate("asdf", ()-> CompletableFuture.completedFuture(request()), 5);
will thrown an exception. What can I do there?

@johnsiu
Copy link

johnsiu commented Nov 9, 2017

In that case, you capture the context beforehand.

Executor currentExecutionContext = httpExecutionContext.current();
cache.getOrElseUpdate("asdf", ()-> CompletableFuture.supplyAsync(() -> request(), currentExecutionContext), 5);

@tzimisce012
Copy link
Author

ok... I have the following method:
CompletionStage<Car> getCar(String id)
and this method will use the http context on its implementation.
cache.getOrElseUpdate("asdf", ()-> carRespository.getCar(id), 5);
Any good solution for this?

@johnsiu
Copy link

johnsiu commented Nov 9, 2017

Yes.

Executor currentExecutionContext = httpExecutionContext.current();
cache.getOrElseUpdate("asdf", ()-> CompletableFuture.supplyAsync(() -> carRespository.getCar(id), currentExecutionContext).thenCompose(Function.identity()), 5);

@tzimisce012
Copy link
Author

Thanks!

@johnsiu
Copy link

johnsiu commented Nov 9, 2017

You are welcome.

@KarelCemus
Copy link
Owner

@tzimisce012 Thanks for the very nice and detailed explanation of the context, I understand it.

@johnsiu Thank you very much for your great work and the proposed workaround.

I'll look at this issue and see what the Play does inside its default implementation and consider reopening this issue. I believe that missing the context inside the orElse clause is a valid point.

@tzimisce012
Copy link
Author

Great. I also believe that the workaround is good but It would be cool if we can fix this topic from the implementation of RedisCache :)

@KarelCemus
Copy link
Owner

Released 1.6.1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants