package com.malk.core;

//import com.mcli.filter.ExceptionNotice;

import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * 线程池配置 [函数式编程：详见YDClient/YDService使用说明]
 * -
 * 前提: 1. 必须仅适用于 public 实例方法; 2.在同一个类中调用异步方法将无法正常工作(self-invocation); 3. 访问对象必须通过 @Autowired 进行注入, 否则无效
 * 返回: 1. 一般为 void, 若需要返回值, 使用Future. 2. 不建议使用回调函数: 回调函数没有状态, 若执行过程中出现数据增删, 简单的通过数量判断不可取 [YDService]
 * 备注: 开启异步在配置类添加 @EnableAsync 类注解, 无需在启动类添加 [非必要]
 * 扩展:
 * - 1. 可以搭配 lombok 的 @Synchronized, 实现异步情况下的线程安全. 会等到前一次执行成功后执行后一次线程调用 [若是不使用 @Async, 可能会超时失败, 宜搭不稳定]
 * - 2. 指定线程 @Async("aliworkConcurrence") 配置, 统一配置调用上限避免超并发 [宜搭, YDClient内并发公共一个@Async配置]
 */
@Slf4j
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {

    // 指定类输出日志到指定文件夹
    private static final Logger logger = LoggerFactory.getLogger("point");

//    @Autowired
//    private ExceptionNotice notice;

    /**
     * 异步执行配置
     *
     * @async 更建议是为了实现异步, 若有并发等待也可满足
     * Delayed 函数回调存在线程死锁, 导致内存问题, 已弃用
     */
    @Override
    public Executor getAsyncExecutor() {
        logger.info("asyncExecutor ▷ 多线程执行");
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();  // 定义线程池
        taskExecutor.setCorePoolSize(3);  // 设置核心线程
        taskExecutor.setMaxPoolSize(10);  // 设置最大线程
        taskExecutor.setQueueCapacity(200);  // 设置线程队列最大线程数
        taskExecutor.setKeepAliveSeconds(10);  // 允许线程的空闲时间 [勿动]
        taskExecutor.setThreadNamePrefix("async-executor-");  // 线程池名的前缀
        taskExecutor.initialize();  // 初始化
        return taskExecutor;
    }

    /**
     * 并发线程配置: 宜搭 [统一配置调用上限避免超过并发, YDClient内并发公共一个@Async配置]
     *
     * @Async("aliworkConcurrence") 指定线程
     */
    @Bean("aliworkConcurrence")
    public ThreadPoolTaskExecutor aliworkTaskExecutor() {
        logger.info("aliworkConcurrence ▷ 多线程初始化");
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心线程数：线程池创建时候初始化的线程数 ==> 宜搭并发200次/s, 限流配置 [4核偶尔出现, 3核极其偶尔]
        executor.setCorePoolSize(2);
        // 最大线程数：线程池最大的线程数，只有在缓冲队列满了之后才会申请超过核心线程数的线程
        executor.setMaxPoolSize(2);
        // 缓冲队列：用来缓冲执行任务的队列
        executor.setQueueCapacity(200);
        // 允许线程的空闲时间60秒：当超过了核心线程之外的线程在空闲时间到达之后会被销毁
        executor.setKeepAliveSeconds(60);
        // 线程池名的前缀：设置好了之后可以方便我们定位处理任务所在的线程池
        executor.setThreadNamePrefix("aliwork-concurrence-");
        // 缓冲队列满了之后的拒绝策略：由调用线程处理（一般是主线程）
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
        executor.initialize();
        return executor;
    }

    /**
     * 处理异步方法中未捕获的异常
     * -
     * ppExt 重要声明
     * 1. @Async 内若调用了不能并发的方法, 如查询限流, 需要在调用的方法上添加 @Synchronized, 避免异步情况下执行触发限流导致抛出错误到多线程
     * 2. @Async 方法上建议要添加 @Synchronized, 否则就在多次连续调用的时候, 入参需要做拷贝, 若同一个入参对象连续调用 @Async 会读取最后一次赋值
     */
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (ex, method, params) -> {
            log.error("Async反馈异常 {}. {}. {}", ex, method, params);  // 记录错误日志
            try {
                throw ex;
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
        };
    }
}
