Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TTL 装饰的线程池无法执行等待任务完成插件 #990

Closed
pirme opened this issue Nov 16, 2022 · 10 comments · Fixed by #1003
Closed

TTL 装饰的线程池无法执行等待任务完成插件 #990

pirme opened this issue Nov 16, 2022 · 10 comments · Fixed by #1003
Assignees
Labels
type: bug Something isn't working
Milestone

Comments

@pirme
Copy link
Member

pirme commented Nov 16, 2022

创建线程池代码如下:

@Slf4j
@Configuration
public class DynamicThreadPoolConfig {

    @Bean
    @DynamicThreadPool
    public Executor messageConsumeTtlDynamicThreadPool() {
        String threadPoolId = MESSAGE_CONSUME;
        ThreadPoolExecutor customExecutor = ThreadPoolBuilder.builder()
                .dynamicPool()
                .threadFactory(threadPoolId)
                .threadPoolId(threadPoolId)
                .build();
        // Ali ttl adaptation use case.
        Executor ttlExecutor = TtlExecutors.getTtlExecutor(customExecutor);
        return ttlExecutor;
    }
}

该代码只是把 TTL 对应的 TtlExecutors.getTtlExecutor 创建为 SpringBean,内部的动态线程池并没有注册为 SpringBean,导致无法执行等待任务完成插件。

修改建议:

将 TTL 内部持有的动态线程池对象注册为 SpringBean。

@pirme pirme added the type: bug Something isn't working label Nov 16, 2022
@pirme pirme added this to the 1.5.0 milestone Nov 16, 2022
@Createsequence
Copy link
Collaborator

这种外部引用被 spring 管理,但是内部的引用没有被 spring 管理的情况应该都会导致关闭的时候不能自动等待任务完成。不知其他第三方适配的线程池是否也存在这个问题?

@pirme
Copy link
Member Author

pirme commented Nov 16, 2022

该功能是为了适配创建线程池 Bean时,可以在声明返回类型时使用 TTL 修饰或者 Spring 线程池,接口实现参考 :

cn.hippo4j.core.executor.support.adpter.DynamicThreadPoolAdapter

@Createsequence
Copy link
Collaborator

该功能是为了适配创建线程池 Bean时,可以在声明返回类型时使用 TTL 修饰或者 Spring 线程池,接口实现参考 :

cn.hippo4j.core.executor.support.adpter.DynamicThreadPoolAdapter

我的描述可能存在一点问题,我重新理解然后组织一下语言。

首先,这个问题的直接原因,可能是外部对象被 spring 管理,但是内部的 DynamicThreadPoolExecutor 对象没有被 spring 管理,最后导致 DynamicThreadPoolExecutor 实现的 DisposableBean 接口提供的回调方法没有触发,因此任务等待插件没有执行。

我的意思是,如果确实是因为这个原因的话,其他像 TtlExecutor 这样,通过 DynamicThreadPoolAdapter 将内部线程池引用替换为 DynamicThreadPoolExecutor 的线程池 Bean,是不是都有可能会存在这样的问题?因为我看到其他的 DynamicThreadPoolAdapter 实现好像也没有针对这种情况做特殊处理。

@pizihao
Copy link
Collaborator

pizihao commented Nov 16, 2022

将动态线程池注册为bean并不是很好的方法,因为内部的线程池并不应该被强制加入到容器中,这会导致容器中出现用户无法第一时间感知的数据

@Createsequence
Copy link
Collaborator

将动态线程池注册为bean并不是很好的方法,因为内部的线程池并不应该被强制加入到容器中,这会导致容器中出现用户无法第一时间感知的数据

我也觉得这或许不是一个好的想法,换个角度,如果只是为了销毁外部 bean 的时候仍然能保证内部对象依然能够触发 DisposableBean 的效果,是否可以直接通过为外部 Bean 额外注册 DestructionAwareBeanPostProcessor 来做到类似的效果?

比如我们现在有一个被 spring 管理的 TtlExecutor,我们通过一些标记或其他什么东西知道它内部有一个 DynamicThreadPoolExecutor ,然后让 DestructionAwareBeanPostProcessor 记住这个 TtlExecutor ,最后等到它被销毁的时候,在后处理这个阶段通过反射拿到内部的 DynamicThreadPoolExecutor 然后调用一下它 destroy 的方法。

不知道这个方案是否可行?

@magestacks
Copy link
Member

方案我是觉得没问题的,实施起来怎么样呢?尽量不要过多侵入原有流程。

@Createsequence
Copy link
Collaborator

这周有时间我提个 pr 试试吧,如果后处理器这个方案可行,应该是完全不会影响到现有流程的

@Createsequence
Copy link
Collaborator

貌似发现了另一个问题。

TaskTimeoutNotifyAlarmPlugin 现在发告警会动态从 ApplicationContextHolderThreadPoolCheckAlarm,也就是说,当我们停止容器的时候,会出现这样的问题:

  • 停止容器,然后容器销毁所有的 bean;

  • 动态线程池 bean 被销毁的时候,启动了任务等待插件;

  • 任务等待插件会在主线程等待时间内竟可能的跑完剩下的任务;

  • 这些剩余任务运行的时候出现了超时,触发了 TaskTimeoutNotifyAlarmPlugin 插件;

  • TaskTimeoutNotifyAlarmPlugin 插件从上下文里获取 ThreadPoolCheckAlarm,但是此时容器正在停止,然后就会抛出异常

    Error creating bean with name xxxx: Singleton bean creation not allowed while singletons of this factory are in destruction (Do not request a bean from a BeanFactory in a destroy method implementation!)

解决方案目前最简单粗暴的就是直接让 TaskTimeoutNotifyAlarmPlugin 在初始化的时候直接从注入一个 ThreadPoolCheckAlarm,要是没啥问题的话,我提完这个 pr 再改这个。

@magestacks
Copy link
Member

可以的。不过我提供另外一种思路,看看是否可行。

既然已经是尽可能执行任务,那么是否报警是否还有必要。如果认为没有必要,TaskTimeoutNotifyAlarmPlugin 获取 ThreadPoolCheckAlarm 如果发现指定异常,吞掉就好了。

可以看下两种方案哪种可行。

@Createsequence
Copy link
Collaborator

可以的。不过我提供另外一种思路,看看是否可行。

既然已经是尽可能执行任务,那么是否报警是否还有必要。如果认为没有必要,TaskTimeoutNotifyAlarmPlugin 获取 ThreadPoolCheckAlarm 如果发现指定异常,吞掉就好了。

可以看下两种方案哪种可行。

感觉你说的这个方案改动量比较小,前一个方案可能还得考虑一下告警那边的问题,出于保险先用直接 catch 一下吧

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment