Skip to content

talsma-ict/context-propagation

Repository files navigation

Maven Version Javadoc Quality Gate Status Coverage Status

Context propagation library

Library to capture a snapshot of one or more ThreadLocal values and reactivate them in another thread.

The library automatically detects supported ThreadLocal values to be captured. It uses a ContextManager Service Provider Interface (SPI) for this using the Java ServiceLoader.

The core module provides several utility classes to safely capture a context snapshot in the calling thread and reactivating it in another thread and ensuring proper cleanup after its execution finishes. This reduces the chance of 'leaking' thread-local values.

API concepts

A brief explanation of the core concepts from the api:

ContextSnapshot

Captures active ThreadLocal values from all detected ContextManager implementations.

These values can be reactivated in another thread.
Reactivated snapshots must be closed to avoid leaking context.

All context aware utility classes in this library are tested to make sure they reactivate and close snapshots in a safe way.

ContextManager

Manages a context by providing a standard way of interacting with ThreadLocal values.

Thread-local values can be accessed via a ContextManager by:

  • Calling getActiveContextValue() which gets the current thread-local value.
  • Calling activate(value) which sets the given value until close() is called on the resulting Context.
  • Calling clear() which removes the thread-local value.

Context

Abstraction for an activated thread-local value.

When the context manager activates a value, a new Context is returned. Closing this context will undo this activated value again.

Important

It is the responsibility of the one activating a new Context to also close it from the same thread. Using every activated context in a 'try-with-resources' block of code is a recommended and safe way to do this.

Utility classes

The context-propagation-core module contains various utility classes that make it easier to capture context snapshots and reactivate them safely in other threads.

Examples include:

  • ContextAwareExecutorService, wrapping any existing ExecutorService, automatically capturing and reactivating context snapshots.
  • ContextAwareCompletableFuture, propagating context snapshots into each successive CompletionStage.
  • Variants of java standard java.util.function implementations, executing within a context snapshot.
  • Base class AbstractThreadLocalContext that features nesting active values and predictable behaviour for out-of-order closing.

How to use this library

Threadpools and ExecutorService

If your background threads are managed by an ExecutorService, you can use our context aware ExecutorService to wrap your usual threadpool.

The ContextAwareExcutorService automatically captures context snapshots before submitting work. This snapshot is then reactivated (and closed) in the submitted background thread.

It can wrap any ExecutorService for the actual thread execution:

private static final ExecutorService THREADPOOL =
        ContextAwareExecutorService.wrap(Executors.newCachedThreadpool());

Manually capture and reactivate a context snapshot

Just before creating a new thread, capture a snapshot of all ThreadLocal context values:

final ContextSnapshot snapshot = ContextSnapshot.capture();

In the code of your background thread, reactivate the snapshot to have all ThreadLocal context values set as they were captured:

try (ContextSnapshot.Reactivation reactivation = snapshot.reactivate()) {
    // All ThreadLocal values from the snapshot are available within this block
}

// or, using a Runnable lambda:
snapshot.wrap(() -> {
    // All ThreadLocal values from the snapshot are available within this block
}).run();

Supported contexts

The following ThreadLocal-based contexts are currently supported out of the box by this context-propagation library:

Custom contexts

Adding your own Context type is possible by creating your own context manager.

Building jars with dependencies

When using a build tool or plugin to create an 'uber-jar', i.e. a jar file with all the classes of its dependencies included, you have to make sure that the service provider configuration files under META-INF/services are either preserved or merged. Otherwise Java's ServiceLoader will not be able to find the context implementations of this library.

In case you are using the Maven Shade Plugin, you can use the ServicesResourceTransformer for this task.

Performance metrics

No library is 'free' with regards to performance. Capturing a context snapshot and reactivating it in another thread is no different. For insight, the library tracks the overall time used creating and reactivating context snapshots along with time spent in each individual ContextManager.

Logging performance

On a development machine, you can get timing for each snapshot by turning on logging for nl.talsmasoftware.context.api.ContextTimer at FINEST or TRACE level (depending on your logger of choice). Please do not turn this on in production as the logging overhead will most likely have a noticeable impact on your application.

Metrics reporting

The context propagation metrics module uses the excellent dropwizard metrics library to instrument Timers for context propagation.

Similarly, the context propagation Micrometer module adds Micrometer instrumentation Timers for the context propagation.

Adding either of these modules to your classpath will automatically configure various timers in the global default metric registry of your application.

New in version 2

Purpose of 'v2' of this library has been simplification of both the API and the structure of the repository.

  • Minimum Java version bumped to 8.
  • Repository module restructuring.
    • Separate API module containing only the minimum API.
    • Separate core module for all utility classes.
    • All provided manager implementations moved to managers subdirectory.
    • All context timer implementations moved to timers subdirectory.
  • API simplification.
    • Static ContextSnapshot.capture() captures a new snapshot and ContextSnapshot.reactivate() reactivates it.
    • ContextManager.initializeNewContext(value) was renamed to activate(value).
    • ContextManager.getActiveContext() was replaced by getActiveContextValue().
    • ContextManager.clear() must be implemented, but is allowed to be a 'no-op' empty implementation. The Clearable interface was removed.
  • All @Deprecated(forRemoval=true) items from v1 were removed.

License

Apache 2.0 license

About

API to propagate one or more ThreadLocal values to another thread

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 9

Languages