Skip to content

morgwai/guice-context-scopes

Repository files navigation

Guice Context Scopes

Classes for building Guice Scopes, that get automatically transferred when dispatching work to other threads.
Copyright 2021 Piotr Morgwai Kotarbinski, Licensed under the Apache License, Version 2.0

latest release: 9.6 (javadoc)

OVERVIEW

Asynchronous servers (such as gRPC or asynchronous Servlets) often need to switch between various threads. This requires extra care to not lose a given current Guice Scope: it needs to be preserved as long as we are in the context of a given request/call/session, regardless of thread switching.

To ease this up, this lib formally introduces a notion of an InjectionContext that can be tracked using ContextTrackers when switching between threads. Trackers are in turn used by ContextScopes to obtain a Context that is current at a given moment and from which scoped objects will be obtained.

When switching threads, static helper methods ContextTracker.getActiveContexts(List<ContextTracker<?>>) and TrackableContext.executeWithinAll(List, Runnable) can be used to manually transfer all active Contexts:

class MyComponent {

    // deriving libraries should bind List<ContextTracker<?>> appropriately
    @Inject List<ContextTracker<?>> allTrackers;

    void methodThatCallsSomeAsyncMethod(/* ... */) {
        // other code here...
        final var activeCtxs = ContextTracker.getActiveContexts(allTrackers);
        someAsyncMethod(arg1, /* ... */ argN, (callbackParam) ->
            TrackableContext.executeWithinAll(activeCtxs, () -> {
                // callback code here...
            })
        );
    }
}

On top of the above, ContextBoundRunnable decorator for Runnable was introduced: it runs its wrapped Runnable task within supplied contexts. This allows to automate Context transfer when using Executors:

class MyOtherComponent {

    @Inject List<ContextTracker<?>> allTrackers;

    void methodThatUsesSomeExecutor(/* ... */) {
        Runnable myTask;
        // build myTask here...
        myExecutor.execute(
            new ContextBoundRunnable(
                ContextTracker.getActiveContexts(allTrackers),
                myTask
            )
        );
    }
}

Deriving libs should also bind ContextBinder that can be used to transfer Contexts almost fully automatically when passing callbacks to async functions that use common functional interfaces (Runnable, Callable, Consumer, BiConsumer, Function, BiFunction) as types for their callbacks:

class MyComponent {  // compare with the "manual" version above

    @Inject ContextBinder ctxBinder;

    void methodThatCallsSomeAsyncMethod(/* ... */) {
        // other code here...
        someAsyncMethod(arg1, /* ... */ argN, ctxBinder.bindToContext((callbackParam) -> {
            // callback code here...
        }));
    }
}

Deriving libs should provide implementations of ExecutorService that fully automate Context transfers:

class MyContextTrackingExecutor extends ThreadPoolExecutor {

    final ContextBinder ctxBinder;

    @Override public void execute(Runnable task) {
        super.execute(ctxBinder.bindToContext(task));
    }

    // constructors here...
}

See the package level javadoc for full code organization guidelines for deriving libs.

DERIVED LIBS

gRPC Guice Scopes
Servlet and Websocket Guice Scopes