Classes for building Guice Scope
s, 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)
Asynchronous servers (such as gRPC or asynchronous Servlet
s) 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 Context
s:
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 Executor
s:
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 Context
s 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.