TransmittableThreadLocal (TTL)

repository·master·Indexed 27 days ago

https://github.com/alibaba/transmittable-thread-local

A lightweight Java library designed for context propagation in multi-threaded environments. TTL ensures that ThreadLocal values are correctly passed when tasks are submitted to thread pools, overcoming the limitations of InheritableThreadLocal in pooled environments. It supports Java 6 to 21 and provides multiple integration methods, including manual wrapping via TtlRunnable/TtlCallable, executor wrapping via TtlExecutors, and a non-intrusive Java Agent for transparent decoration of JDK thread pools.

Tokens
8.3K
Snippets
18
Records
46
Agent score
93%

What's inside TransmittableThreadLocal

  1. Overview of TransmittableThreadLocal (TTL)

    master

    TransmittableThreadLocal (TTL) provides the ability to pass ThreadLocal values across thread pools and other execution components that reuse threads.

    While the standard JDK InheritableThreadLocal handles value passing from a parent thread to a newly created child thread, it fails in pooled environments where threads are reused. TTL solves this by ensuring that the ThreadLocal value present when a task is submitted to a thread pool is correctly passed to the task's execution context.

    Key features:

    • Zero dependencies.
    • Supports Java 6 to 21 (Note: v2.13+ requires Java 8; use v2.12.x for Java 6 support).
    • Extremely lightweight (~1000 SLOC).
  2. Integrate TTL with Log4j2 MDC

    master

    To ensure that Mapped Diagnostic Context (MDC) values are correctly passed to worker threads in thread pools (like Executors), use the log4j2-ttl-thread-context-map integration. This solves the issue where Log4j2's default ThreadLocal implementation fails to propagate context when threads are reused.

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>log4j2-ttl-thread-context-map</artifactId>
        <version>1.3.0</version>
    </dependency>
  3. Implement a TTL Agent Transformlet extension

    master

    To extend the TTL Agent using a Transformlet, you must provide two components:

    1. An implementation class: A class that implements the Transformlet interface to define how classes or methods should be modified during transformation.
    2. A configuration file: A file located at META-INF/ttl.agent.transformlets within your classpath. This file must contain the fully qualified name of your implementation class.

    TTL Agent scans the classpath for this specific file and automatically enables the extensions, similar to how JDK's ServiceLoader works. Simply adding the JAR containing your implementation to the application's classpath will activate it.

  4. Ensure TTL values are passed on Vert.x EventBus

    master

    To ensure TransmittableThreadLocal (TTL) values are passed through the Vert.x EventBus, use the following methods:

    Decorate java.lang.Runnable

    Use TtlRunnable to decorate a Runnable before passing it to the EventBus.

    Decorate io.netty.util.concurrent.SingleThreadEventExecutor

    TTL support for io.netty.util.concurrent.SingleThreadEventExecutor is provided via the NettySingleThreadEventExecutorTtlTransformlet implementation.

  5. Integrate TTL with Logback MDC

    master

    To propagate MDC context across threads when using Logback, use the logback-mdc-ttl library. This integration is suitable for production environments where thread-local context must persist through thread pool execution.

    <dependency>
        <groupId>com.ofpay</groupId>
        <artifactId>logback-mdc-ttl</artifactId>
        <version>1.0.2</version>
    </dependency>
  6. Run TPS and pressure tests for TransmittableThreadLocal

    master

    You can perform throughput (TPS) and pressure testing to compare the performance of TransmittableThreadLocal against standard ThreadLocal. These tests involve concurrent threads continuously creating new instances without manual cleanup.

    Use the following scripts to execute the tests:

    • For ThreadLocal: Run tps-ThreadLocal.sh (uses test class CreateThreadLocalInstanceTps).
    • For TransmittableThreadLocal: Run tps-TransmittableThreadLocal.sh (uses test class CreateTransmittableThreadLocalInstanceTps).
  7. Pass TTL values in Vert.x 4 EventBus

    master

    To ensure TransmittableThreadLocal (TTL) values are propagated through the Vert.x EventBus, use the following approaches:

    Decorate java.lang.Runnable

    Use TtlRunnable to wrap Runnable tasks that are passed through the EventBus.

    Decorate io.netty.util.concurrent.SingleThreadEventExecutor

    TTL support for io.netty.util.concurrent.SingleThreadEventExecutor is provided via the NettySingleThreadEventExecutorTtlTransformlet agent transformlet.

  8. Use TTL for Cross-layer Context Propagation (Container to SDK)

    master
    In multi-tenant environments (e.g., PaaS/SaaS), TTL can be used to pass tenant context (like SAAS_USER_ID) from an application container through user application code (which may use thread pools) down to a low-level SDK. This ensures the SDK can validate that data access is restricted to the correct tenant context, even when the execution thread has changed.
  9. Wrap Executor and ExecutorService with TtlExecutors

    master

    Instead of wrapping every individual task, you can wrap the entire thread pool using the TtlExecutors utility class. This automatically handles context propagation for all tasks submitted to that executor.

    Available methods in TtlExecutors:

    • getTtlExecutor(Executor executor): Wraps an Executor.
    • getTtlExecutorService(ExecutorService executorService): Wraps an ExecutorService.
    • getTtlScheduledExecutorService(ScheduledExecutorService scheduledExecutorService): Wraps a ScheduledExecutorService.
    ExecutorService executorService = ...
    // Wrap the executor service once
    executorService = TtlExecutors.getTtlExecutorService(executorService);
    
    TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();
    context.set("value-set-in-parent");
    
    Runnable task = new RunnableTask();
    // No manual wrapping of 'task' needed here
    executorService.submit(task);