Our company has integrated Play with existing Java infrastructure code that makes heavy use of ThreadLocals to propagate context throughout a request's execution. Unfortunately Play isn't very ThreadLocal friendly, and while we have done a number of hacks to propagate the values across different execution branching points we continue to run into cases that we missed which lead to some serious bugs. We're now looking for a holistic solution at the framework level.James Roper was kind enough to look into this initially and suggested creating a custom ExecutorServiceConfigurator for configuring the default Akka ForkJoinExecutor and propagating things using a custom ExecutorService. This seems to work for a majority of the execution as long as things stay within the Play/Akka world. We've found one instance where it breaks though; when using WS.* to make remote requests. Any work done within a map/recover/etc loses the context. It looks like to get around this issue we need to modify Play to properly propagate things; WS uses the play.core.Execution.Implicits.internalContext instead of the play.api.libs.concurrent.Execution.Implicits.defaultContext, and the internalContext isn't configurable as far as I can tell. I'm guessing that customizing the internalContext with a ExecutorService that does propagation will work, but if so I wonder why Play didn't do this to copy the Java Http.Context around. I've also prototyped a solution similar to what is done in F.Promise when the Java WS is used which basically seems to grab the current context whenever map/flatMap/recover/etc are invoked. This seems like a messy solution and would need to be implemented on a case by case basis wherever Futures/Promises are being used outside of the defaultContext.I guess at this point I'm looking for any comments/answers for the following:1. Will modifying the internalContext to copy things around work? This seems to be the cleanest approach, but I wonder why this isn't done to handle the Java Context.
2. Aside from WS are there other areas we need to be aware of that may be executing outside of the defaultContext and calling back in to user code?
3. What is the proper way to make this configurable? Add methods to Global to build ExecutionContexts/ExecutorServices? Specify FQCNs in config? Perhaps something more fine grained such as a class that defines what it means to capture context, restore context, and clear context? Eventually we'd like to contribute this work back as a pull request.
Thanks,Bryan
The internal context is for use by Play to execute Play code. If it is ever used to execute application code, that's a bug in Play. Report it to us, and we'll fix it.
No, this should never happen. If Play is executing your code on anything but the default context, or a context that you have explicitly passed, then that's a bug. Of course, if you use an external library that executes asynchronous code, then that's a different matter, how you manage thread locals then will depend on that particular library.
There is some interesting discussion happening around this - there is a SIP in the works that may change the way thread local state is transferred in ExecutionContexts. So at this stage, it's hard to comment. I think the approach you're taking now of creating a custom ExecutorServiceConfigurator is the way to go - it may make sense to contribute some of this back into Play and document how you've achieved what you've done.
Another thing to note is that we're looking to get rid of Play's global state in Play 3.0. This will most likely mean getting rid of the default execution context as it currently stands, and instead having it injected into components that need it (including Play components). Play 3.0 will give you the necessary control you need over how all this is setup, which will probably make it a lot easier for you to provide a custom execution context. If you're interested in following this work, then I'd suggest joining the dev mailing list, in particular this thread is where a lot of the discussion has been happening:But there are a few other threads there as well.
On Wednesday, July 30, 2014 8:39:18 PM UTC-7, James Roper wrote:
The internal context is for use by Play to execute Play code. If it is ever used to execute application code, that's a bug in Play. Report it to us, and we'll fix it.Is there any situation where it would be used to bridge application code though? For example something executing on the default context calls out to Play code which uses the internal context and then executes additional work back in the default context. The context propagation would break down when it hits the internal context execution.
No, this should never happen. If Play is executing your code on anything but the default context, or a context that you have explicitly passed, then that's a bug. Of course, if you use an external library that executes asynchronous code, then that's a different matter, how you manage thread locals then will depend on that particular library.Understood, but Play can execute code on the default context that doesn’t originate from another thread in the default context making TL propagation from one thread to another impossible. This is the problem I’ve hit using WS in Scala. For example:WS.url(“http://www.google.com”).get.map(..context lost here..) // this is called from a netty thread so propagation using just the ExecutorServiceConfigurator doesn’t work
There is some interesting discussion happening around this - there is a SIP in the works that may change the way thread local state is transferred in ExecutionContexts. So at this stage, it's hard to comment. I think the approach you're taking now of creating a custom ExecutorServiceConfigurator is the way to go - it may make sense to contribute some of this back into Play and document how you've achieved what you've done.The approach using the ExecutorServiceDecorator doesn’t fully work as mentioned in the WS example above. At this point we’re having to modify the WS code to achieve correct propagation. Thus far I’ve only tested the Scala side of things and haven’t evaluated any gaps that decorating the executor service has with Java’s async APIs. I’ve also bumped into an issue where dev-time hot reload breaks down with a fairly straightforward ExecutorServiceDecorator implementation; I’m guessing I’ll need to add some classloader propagating code similar to what HttpExecutionContext is doing.
Another thing to note is that we're looking to get rid of Play's global state in Play 3.0. This will most likely mean getting rid of the default execution context as it currently stands, and instead having it injected into components that need it (including Play components). Play 3.0 will give you the necessary control you need over how all this is setup, which will probably make it a lot easier for you to provide a custom execution context. If you're interested in following this work, then I'd suggest joining the dev mailing list, in particular this thread is where a lot of the discussion has been happening:
But there are a few other threads there as well.Thanks for the reply and the pointers on future work.
--
You received this message because you are subscribed to the Google Groups "play-framework" group.
To unsubscribe from this group and stop receiving emails from it, send an email to play-framewor...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
The WS API uses plain futures - it does not use the default context, every call to a plain future, including the map call you have above there, takes an implicit execution context to execute on. You need to ensure that the context that you pass there (ie, the implicit execution context that is in scope) is your thread local propagating context. If the callbacks are executing on the Netty thread, then my guess is that that means you have imported a trampoline execution context. Never use a trampoline execution context for mapping a future from a WS call, this is dangerous especially if you're doing blocking in the callbacks. The trampoline execution context won't have any significant performance improvement on your code unless you're deep in iteratee code.So, when you have this code:WS.url(“http://www.google.com”).get.map(..context lost here..)you need to somewhere before that:import my.Contexts.myImplicitThreadLocalPropgatingContextand not:import play.api.libs.iteratee.Execution.Implicits.trampoline
--
You received this message because you are subscribed to the Google Groups "play-framework" group.
To unsubscribe from this group and stop receiving emails from it, send an email to play-framewor...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Yes, this looks like it’s working correctly and doesn’t require the WS hack I resorted to earlier. It also works nicely for our needs as some of the context we’re propagating has very specific visibility rules from parent to child tasks (it’s not a shared instance). The ability to access the state during the handoff from one task to another via execute but also fall back to what was captured at the call site during prepare fits the requirements. The only change from the code you outlined I made was to have ContextPropagatingDispatcher extend Dispatcher instead of MessageDispatcher since that’s what the DefaultDispatcher seems to do.
Out of curiosity is there a reason why the Java http context propagation is not implemented this way?
--
You received this message because you are subscribed to the Google Groups "play-framework" group.
To unsubscribe from this group and stop receiving emails from it, send an email to play-framewor...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.