Non-thread-based request scoping

281 views
Skip to first unread message

Greg Methvin

unread,
Dec 20, 2016, 2:36:19 AM12/20/16
to google-guice
Hi everyone,

I currently one of the maintainers of Play Framework, a Java/Scala web framework that integrates with Guice. Play uses non-blocking I/O, and since one thread may handle multiple requests at once, we can't use thread locals to scope dependencies.

So I'm wondering if there is a recommended way to bind instances only in the context of a particular object (like a request) without using any global or thread-local state.

I can basically do what I want by creating a child injector for each request. The child injector also binds the request and a module with request-scoped dependencies. It then uses that injector to obtain an instance of the relevant controller to handle the request, which may have dependencies bound that use the request instance. Of course that means you need to create a new child injector per request, which doesn't seem like a good idea.

Does it make sense to use Guice to solve this problem? Is there a better way to do it than what I've tried?

Thanks,
Greg

Paweł Cesar Sanjuan Szklarz

unread,
Dec 20, 2016, 2:57:32 AM12/20/16
to google...@googlegroups.com
Hi.

If the request dependencies are created in a specific moment on time in a single thread, then You can make a custom scope to setup the correct request context.

The basic scope idea is that You create a specialized provider from a more general one:

Just like on the example of custom scope:

You can trigger/setup the scope implementation to provide the correct provider.

A example of scope use in a single thread for multiple request may be:
for (int i = 0; i < workAndData.length; i++) {
workAndData = workAndData[i];
scope.enterRequest(workAndData.getRequest());
workAndData.runJob();
scope.exitRequest();
}

Of course You need to setup the scope in every thread where the request scope will be used.

Pawel.

--
You received this message because you are subscribed to the Google Groups "google-guice" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-guice+unsubscribe@googlegroups.com.
To post to this group, send email to google...@googlegroups.com.
Visit this group at https://groups.google.com/group/google-guice.
To view this discussion on the web visit https://groups.google.com/d/msgid/google-guice/7494eddd-a7ea-4351-965a-2b3d96c8f14f%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Stephan Classen

unread,
Dec 20, 2016, 4:52:13 AM12/20/16
to google...@googlegroups.com

This does not solve the problem. The custom scope example uses a ThreadLocal<Map<Key<?>, Object>> to store the scoped values. I am currently also looking for a "RequestScope" in an environment where a request may be handled by more than one thread.

To unsubscribe from this group and stop receiving emails from it, send an email to google-guice...@googlegroups.com.

To post to this group, send email to google...@googlegroups.com.
Visit this group at https://groups.google.com/group/google-guice.

Paweł Cesar Sanjuan Szklarz

unread,
Dec 20, 2016, 5:00:43 AM12/20/16
to google...@googlegroups.com
Hi.

Yes, the custom scope example on the page do not solve the problem. To be more explicit on my proposition, the enterRequest method on a different scope may look like this:
class CustumMultiRequestPerThreadScope extends Scope {

private volatile ThreadLocal<Map<Key<?>, Object>> currentRequestInjectionResults = null;

public void enterRequest(CustomFrameworkRequestInfo requestData) {
  currentRequestInjectionResults = requestData.getInjectionResults();
}

}

Here currentRequestInjectionResults is ThreadLocal only to allow a single instance of the scope implementation, but in each thread You need to enter and exit the request data "During the time that the thread calculations are working on a specific request" - this is the key.

--
You received this message because you are subscribed to the Google Groups "google-guice" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-guice+unsubscribe@googlegroups.com.
To post to this group, send email to google...@googlegroups.com.
Visit this group at https://groups.google.com/group/google-guice.

Luke Sandberg

unread,
Dec 20, 2016, 11:21:24 AM12/20/16
to google...@googlegroups.com
would it not work to just transfer the scope from thread to thread?  isn't it usually the case that some thread will handle a request for a while and then return to a pool/event loop?  if that is the case, then you could just use the transferRequest method (https://github.com/google/guice/blob/master/extensions/servlet/src/com/google/inject/servlet/ServletScopes.java#L266) when a thread starts handling an in flight request.

at google we use this mechanism to implement non-blocking io and still use the request scope.

To unsubscribe from this group and stop receiving emails from it, send an email to google-guice...@googlegroups.com.

To post to this group, send email to google...@googlegroups.com.
Visit this group at https://groups.google.com/group/google-guice.
To view this discussion on the web visit https://groups.google.com/d/msgid/google-guice/7494eddd-a7ea-4351-965a-2b3d96c8f14f%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
--
You received this message because you are subscribed to the Google Groups "google-guice" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-guice...@googlegroups.com.

To post to this group, send email to google...@googlegroups.com.
Visit this group at https://groups.google.com/group/google-guice.

--
You received this message because you are subscribed to the Google Groups "google-guice" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-guice...@googlegroups.com.

To post to this group, send email to google...@googlegroups.com.
Visit this group at https://groups.google.com/group/google-guice.

--
You received this message because you are subscribed to the Google Groups "google-guice" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-guice...@googlegroups.com.

To post to this group, send email to google...@googlegroups.com.
Visit this group at https://groups.google.com/group/google-guice.

Bob Lee

unread,
Dec 20, 2016, 2:16:14 PM12/20/16
to Guice Mailing List
Child injectors were designed in part for this very use case! If child injectors had been in 1.0, we probably wouldn't have scopes.

Bob

--
You received this message because you are subscribed to the Google Groups "google-guice" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-guice+unsubscribe@googlegroups.com.

Greg Methvin

unread,
Dec 20, 2016, 4:07:50 PM12/20/16
to google...@googlegroups.com
Hi Bob,

So would you say I shouldn't worry about the cost of creating child injectors on every request? I sort of like this approach. Having to transfer state between thread locals seems error-prone.

The only thing I don't like is I can't "override" bindings in the child injector from the parent injector, unless I also do it using another scope or a inject different type.

Greg

--
You received this message because you are subscribed to a topic in the Google Groups "google-guice" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/google-guice/qZqkaWPkhoo/unsubscribe.
To unsubscribe from this group and all its topics, send an email to google-guice+unsubscribe@googlegroups.com.

To post to this group, send email to google...@googlegroups.com.
Visit this group at https://groups.google.com/group/google-guice.

Bob Lee

unread,
Dec 22, 2016, 6:25:56 PM12/22/16
to Guice Mailing List
On Tue, Dec 20, 2016 at 1:07 PM, Greg Methvin <greg.m...@gmail.com> wrote:
So would you say I shouldn't worry about the cost of creating child injectors on every request? I sort of like this approach. Having to transfer state between thread locals seems error-prone.

Correct. Child injectors were designed with this very use case in mind. Of course, you'll want to avoid slow operations in your request-scoped modules. Child injectors are less error prone than Scopes because they make it impossible for an object in a larger scope to directly reference an object in a smaller scope (for example, a global singleton can't be injected with a request-scoped object).

Bob 

Luke Sandberg

unread,
Dec 22, 2016, 9:55:53 PM12/22/16
to Guice Mailing List
I think there might be some performance concerns, since the cost of creating a child injector scales wither number of bindings in the child injector. Whereas using request scope you just pay the overhead (of the threadlocal interactions) for the bindings that you actually use in a request. For large applications there might be a large difference between these two numbers.
--
You received this message because you are subscribed to the Google Groups "google-guice" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-guice...@googlegroups.com.

To post to this group, send email to google...@googlegroups.com.
Visit this group at https://groups.google.com/group/google-guice.

Tim Boudreau

unread,
Mar 2, 2017, 6:24:30 AM3/2/17
to google-guice
I did this for a Guice-based framework I built of top of Netty - https://github.com/timboudreau/acteur - as follows:

First, some base classes for custom scopes:

And basically, since my code controls thread dispatch, you simply wrap any Runnable/Callable that is going to expect to be able to inject request-specific objects in another Runnable/Callable that first re-enters the scope with the same objects it had before.  You can make this even more transparent by wrapping an ExecutorService with one which:

 - Whenever new work is scheduled *from* a thread that is already in-scope, it is wrapped in such a Runnable/Callable

Now, whether Play has the entry points you'd need to control dispatch in this way, I don't know.  But basically, when a request comes in, you fill up a bag with the objects you want to be injectable, and "enter" the scope with them.  When the request processing code schedules something, you give it a copy of that bag, and before the code to be scheduled is invoked, you re-enter the scope with its former contents.

pseudocode example:

ReentrantScope scope = ...

public void schedule(Runnable r) { // assume this is a thread pool, whatever thread mgmt mechanism you or Play uses
    super.schedule (new ScopedRunnable(r));
}
...

class ScopedRunnable implements Runnable {
     final Object[] contents;
     final Runnable delegate;
     ScopedRunnable(Runnable delegate) {
        this.contents = scope.contents();
        this delegate = delegate;
     }

    public void run() {
        scope.enter(contents);
        try {
           delegate.run();
        } finally {
            scope.exit();
        }
}

(that's fairly rough code, but hopefully you get the idea - there are some convenience methods in the scopes library to make this sort of thing simple).

-Tim

Reply all
Reply to author
Forward
0 new messages