alibaba / transmittable-thread-local

📌 a missing Java std lib(simple & 0-dependency) for framework/middleware, provide an enhanced InheritableThreadLocal that transmits values between threads even using thread pooling components.
https://github.com/alibaba/transmittable-thread-local
Apache License 2.0
7.59k stars 1.69k forks source link

“业务项目”在通过bytebuddy增强后,TTL+CompletableFuture无法再正常地进行上下文传递。 #519

Closed Nietzschecat closed 1 year ago

Nietzschecat commented 1 year ago

1、问题简述

在开发「流量回放工具」时,「流量回放工具」内部通过使用bytebuddy,在不侵入“业务项目”的情况下,对“业务项目”的各种外部调用进行数据Mock

在“业务项目”中通过Maven引入「流量回放工具」的jar包,且在“业务项目”启动时对特定的类做字节码增强(未增强CompletableFuture等并发类)后,TTL+CompletableFuture无法再正常进行上下文数据传递。

简言之:“业务项目”在通过bytebuddy增强后,TTL+CompletableFuture无法再正常地进行上下文传递。

2、测试代码

public static ThreadLocal<String> ttlA = new TransmittableThreadLocal<>();
public static ThreadLocal<String> ttlB = new TransmittableThreadLocal<>();
public static ThreadLocal<String> ttlC = new TransmittableThreadLocal<>();
// 使用单个线程进行测试
public static ExecutorService executorService = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(1));

public void testCompletableFutureTTL() {
    ttlA.set("111");
    ttlB.set("222");
    ttlC.set("333");

    CompletableFuture.supplyAsync(() -> {
        System.out.println(ttlA.get());
        System.out.println(ttlB.get());
        System.out.println(ttlC.get());
        return null;
    }, executorService);

    // 变更了ttlA的值为“888”,理论上子线程也会变更
    ttlA.set("888");
    // 删除了ttlB,理论上子线程ttlB会变为null
    ttlB.remove();

    CompletableFuture.supplyAsync(() -> {
        System.out.println(ttlA.get());
        System.out.println(ttlB.get());
        System.out.println(ttlC.get());
        return null;
    }, executorService);
}

3、问题复现

transmittable-thread-local版本:2.12.2

情况一:未通过bytebuddy增强

(1)执行结果(符合预期):

fffbab7a649e4cd79b55357c572c8e19

(2)debug过程(符合预期):

0d9136484d10601990958c270ef31f88

c6943cf2db0b4595fe72466c102f624f

情况二:使用bytebuddy进行增强

(1)执行结果(不符合预期):

image

(2)debug过程:

image

最奇怪的地方:执行到supplyAsync时,仍是主线程,但是TransmittableThreadLocal却生成了新的holder,仿佛有切面逻辑,但是我在TTL源码中没找到切面逻辑。

image

主线程的ttlA.set("888"); ttlB.remove();操作没生效。子线程内的数据仿佛快照一样,定格在了第一次执行时的上下文,数据不再发生任何变化。

image

oldratlee commented 1 year ago

在开发「流量回放工具」时,「流量回放工具」内部通过使用bytebuddy,在不侵入“业务项目”的情况下,对“业务项目”的各种外部调用进行数据Mock

你提到的「流量回放工具」也是基于Java Agent实现,应用中有2个Agent吧?

如果是,下面是 继续的内容。

在“业务项目”中通过Maven引入「流量回放工具」的jar包,且在“业务项目”启动时对特定的类做字节码增强(未增强CompletableFuture等并发类)后,TTL+CompletableFuture无法再正常进行上下文数据传递。

「流量回放工具」与 TTL 2个Java Agent之间的影响, 要具体了解2者代码实现与设计才能确定; 仅通过上面的运行结果无法排查。 @Nietzschecat

TTL是开源的,有完整的排查信息(如源码); 可以让「流量回放工具」的实现同学排查一下。