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

ForkJoinPool提交的任务是一个runnable类型的,请问这种可以直接把runnable转成ttlRunnable来达到开启ttl的目的吗? #384

Closed DockerFan closed 2 years ago

DockerFan commented 2 years ago

debug看业务代码ForkJoinPool的execute方法里的传参是个runnable类型,那可以把一个ForkJoinTask的任务强转成一个runnable,然后处理成ttlRunnable类型这样来开启ttl吗?

oldratlee commented 2 years ago

@DockerFan

  1. 你可以测试一下,验证到效果(以及是不是有问题)。
  2. 要给一下完整可运行的代码(以确认具体的使用方式) 并 说明你期望的运行效果(以及不预期的问题)。
    • 比如『ForkJoinTask的任务强转成一个runnable』这个说法不能准确理解

PS:ForkJoinPool有不风格的用法;这些不同用法本身的区别,可以先查资料了解。

DockerFan commented 2 years ago

先介绍下背景:

目前解决方案:

一、将ttl-agent绑定到被测服务,这样对于ForkJoinPool所有线程池方法都进行了增强,理论上是不是就可以通过sandbox-repeaterTransmittableThreadLocal拿到正确的上下文拷贝结果呢?

二、通过反射机制,将ForkJoinPoolexecute(Runnable task)如下方法的runnable转成ttlRunnable

public void execute(Runnable task) {
    if (task == null) throw new NullPointerException();

    ForkJoinTask<?> job;
    if (task instanceof ForkJoinTask<?>) // avoid re-wrap
        job = (ForkJoinTask<?>) task;
    else
        job = new ForkJoinTask.RunnableExecuteAction(task);

    externalPush(job);
}

目前看两种方式都没成功,不知道是哪块理解和使用不到位,期待您的回复。

oldratlee commented 2 years ago

先介绍下背景:

……

  • 然后sandbox-repeater绑定到被测服务以后需要把每个主请求下面的所有子请求根据traceId进行关联 (所谓主请求就类似是浏览器发送的http请求,子请求就是处理这个http请求所涉及到的中间件的交互)
  • 所以会涉及到线程池上下文拷贝的问题,但是sandbox-repeater处理的是加载到jvm后的处理

一、将ttl-agent绑定到被测服务,这样对于ForkJoinPool所有线程池方法都进行了增强, 理论上是不是就可以通过sandbox-repeaterTransmittableThreadLocal拿到正确的上下文拷贝结果呢?

二、通过反射机制,将ForkJoinPoolexecute(Runnable task)如下方法的runnable转成ttlRunnable

上面『通过反射机制 转换』,你的说明和代码少 & 问题相对复杂,不能清楚理解。 比如runnableForkJoinPool#execute方法参数,在不修改/增强这个方法的情况下,用反射修改如何能生效?在你上面的评论说明中,不能看出如何修改/增强这个方法的逻辑。

下面假设在ForkJoinPool#execute方法增强加入了

runnable = TtlRunnable.get(runnable);

作为方法第一行,以在方法入口将参数runnable转成ttlRunnable

简单看来(具体要展开细挖),在入口转成ttlRunnable,影响了ForkJoinPool#execute(Runnable task)方法原有的执行逻辑。 比如,导致ForkJoinPool#execute后续原有逻辑总是会调用new ForkJoinTask.RunnableExecuteAction(task) wrap 成ForkJoinTask<?>

即,这样的转换修改可能会影响 ForkJoinPool本身的逻辑正确性,看起来并不是一个安全可靠的修改方式。

@DockerFan

ForkJoinPool的实现有些复杂,运行时增强修改的有些问题我也没有找到可靠可行的解决方法。推荐先了解一下已有的讨论:

oldratlee commented 2 years ago

性能优化那些事(3) https://zhuanlan.zhihu.com/p/503149395

这篇文章提到:

@DockerFan 可以看看:具体解决到什么程度、是不是有可以参考借鉴的。

关于TTL的首要场景与实现要求

即,像测试的便利性 在优先级上要低于 生产在线的可靠性。 当然,如果能找到 两者兼顾的解决方法,就不需要做这样的取舍了。

oldratlee commented 2 years ago

先 close 了;有进一步信息或问题可以继续讨论。

JasonMing commented 2 years ago

@DockerFan 之前我自己做了一个方案,分两步

  1. ForkJoinTask 里面插入字段 private final Object captured = capture();
  2. doExec() 中插入包装 Object backup; try { backup = replay(captured); <...原有代码...>} finally { restore(backup); }

这步由于涉及到添加字段,如果类已加载则会失效,必须保障在类初始化之前进行增强。