zoukankan      html  css  js  c++  java
  • 低版本spring-cloud-sleuth的TraceableExecutorService一个坑,注意绕行

    我们知道Java 线程池提交任务有两种:

    1,submit 会抛出异常,会有异常日志

    2,execute 没有抛出异常,需要执行返回值Future的get方法触发抛出异常

    execute在执行中没有做任何包装,在线程池源码中会执行到runWorker方法,这个方法中可以看到会把异常抛出,我们也可以通过覆盖afterExecute方法对异常进行自定义操作。这也就是execute会有异常信息打印的原因。

    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {
                w.lock();
                // If pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        task.run();
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }
    

    submit在提交过程中会把任务包装成FutureTask,FutureTaskrun方法:

    public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable<v> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }
    

    看代码就知道,它不会直接把在执行任务的时候把异常直接抛出来,而是执行了 setException(ex);方法把异常存了起来,等到get方法请求时触发异常。

    话要从前几天一个问题讲起,我们应用使用Spring Cloud,我对应用使用的线程池进行统一管理,研发在使用execute方法提交任务后,没有看到执行任务的代码,也没有任何报错信息。

    根据前面的分析,execute有异常是会抛出异常也是有日志信息的,我调试了一下异常情况,居然没有异常抛出,发现我们的线程池都被TraceableExecutorService代理了,打开这个类,我就惊呆了,execute方法被篡改成submit提交了。

    Spring Cloud 里会使用spring-cloud-sleuth来做日志链路跟踪,一种常见的场景就是在应用中线程池使用的线程,如何把跟踪的用的traceId 传递过去,Spring的做法是把所有ExecutorService类型的Bean都用 TraceableExecutorService 进行代理,这个代理类生产代码实现在ExecutorBeanPostProcessor里的postProcessAfterInitialization方法中,具体代码:

    if (bean instanceof ExecutorService
          && !(bean instanceof TraceableExecutorService)) {
       if (isProxyNeeded(beanName)) {
          return wrapExecutorService(bean);
       }
       else {
          log.info("Not instrumenting bean " + beanName);
       }
    }
    

    注意,我使用的是Spring Cloud 的版本是Greenwich.SR1,它依赖的spring-cloud-starter-sleuth2.1.1.RELEASE而这个版本的TraceableExecutorService实现了execute代理方法,把一个execute篡改成submit提交任务,带来的后果就是预期的异常抛出却没有抛出。

    public void execute(Runnable command) {
    	this.delegate.submit(ContextUtil.isContextInCreation(this.beanFactory) ? command
    			: new TraceRunnable(tracing(), spanNamer(), command, this.spanName));
    }
    

    这个是bug产生时的改动:

    https://github.com/spring-cloud/spring-cloud-sleuth/commit/17d8165c5e1433cf7b2943ade53f06bdd12a86de#diff-7b796c619250cf07f1d1f80d53c9e32d140e268fdfcea3bee20460fc731c0105

    这个是修复bug的描述:

    https://github.com/spring-cloud/spring-cloud-sleuth/commit/e2e1cdb336ec89b26fcd22116510c2d5eee6924e

    在我看来这是个低级错误,复制粘贴代码出的错,带来的影响虽然不大,但在一些场景没有异常信息其实是很致命的,甚至会导致一个bug隐藏很久或者一个bug要查很久。

  • 相关阅读:
    219. Contains Duplicate II
    189. Rotate Array
    169. Majority Element
    122. Best Time to Buy and Sell Stock II
    121. Best Time to Buy and Sell Stock
    119. Pascal's Triangle II
    118. Pascal's Triangle
    88. Merge Sorted Array
    53. Maximum Subarray
    CodeForces 359D Pair of Numbers (暴力)
  • 原文地址:https://www.cnblogs.com/killbug/p/15345301.html
Copyright © 2011-2022 走看看