Search code examples
multithreadingguice

Guice and RequestScoped behaviour in multiple threads


I am using Guice's RequestScoped and Provider in order to get instances of some classes during a user request. This works fine currently. Now I want to do some job in a background thread, using the same instances created during request. However, when I call Provider.get(), guice returns an error:

Error in custom provider, com.google.inject.OutOfScopeException: Cannot 
access scoped object. Either we are not currently inside an HTTP Servlet 
request, or you may have forgotten to apply     
com.google.inject.servlet.GuiceFilter as a servlet 
filter for this request.

afaik, this is due to the fact that Guice uses thread local variables in order to keep track of the current request instances, so it is not possible to call Provider.get() from a thread different from the thread that is handling the request.

How can I get the same instances inside new threads using Provider? It is possible to achieve this writing a custom scope?


Solution

  • I recently solved this exact problem. There are a few things you can do. First, read up on ServletScopes.continueRequest(), which wraps a callable so it will execute as if it is within the current request. However, that's not a complete solution because it won't forward @RequestScoped objects, only basic things like the HttpServletResponse. That's because @RequestScoped objects are not expected to be thread safe. You have some options:

    • If your entire @RequestScoped hierarchy is computable from just the HTTP response, you're done! You will get new instances of these objects in the other thread though.

    • You can use the code snippet below to explicitly forward all RequestScoped objects, with the caveat that they will all be eagerly instantiated.

    • Some of my @RequestScoped objects couldn't handle being eagerly instantiated because they only work for certain requests. I extended the below solution with my own scope, @ThreadSafeRequestScoped, and only forwarded those ones.

    Code sample:

    public class RequestScopePropagator {
        private final Map<Key<?>, Provider<?>> requestScopedValues = new HashMap<>();
    
        @Inject
        RequestScopePropagator(Injector injector) {
            for (Map.Entry<Key<?>, Binding<?>> entry : injector.getAllBindings().entrySet()) {
                Key<?> key = entry.getKey();
                Binding<?> binding = entry.getValue();
                // This is like Scopes.isSingleton() but we don't have to follow linked bindings
                if (binding.acceptScopingVisitor(IS_REQUEST_SCOPED)) {
                    requestScopedValues.put(key, binding.getProvider());
                }
            }
        }
    
        private final BindingScopingVisitor<Boolean> IS_REQUEST_SCOPED = new BindingScopingVisitor<Boolean>() {
            @Override
            public Boolean visitScopeAnnotation(Class<? extends Annotation> scopeAnnotation) {
                return scopeAnnotation == RequestScoped.class;
            }
    
            @Override
            public Boolean visitScope(Scope scope) {
                return scope == ServletScopes.REQUEST;
            }
    
            @Override
            public Boolean visitNoScoping() {
                return false;
            }
    
            @Override
            public Boolean visitEagerSingleton() {
                return false;
            }
        };
    
        public <T> Callable<T> continueRequest(Callable<T> callable) {
            Map<Key<?>, Object> seedMap = new HashMap<>();
            for (Map.Entry<Key<?>, Provider<?>> entry : requestScopedValues.entrySet()) {
                // This instantiates objects eagerly
                seedMap.put(entry.getKey(), entry.getValue().get());
            }
    
            return ServletScopes.continueRequest(callable, seedMap);
        }
    }