ScheduledThreadPoolExecutor does not perform cause analysis

Posted by wendu on Tue, 08 Mar 2022 07:13:05 +0100

preface

Recently, when debugging a monitoring application indicator, it was found that the timer did not execute after the service was started and executed once. The timer used here is the scheduling thread pool ScheduledThreadPoolExecutor of Java. Later, after troubleshooting, it was found that if the processing task of the ScheduledThreadPoolExecutor thread pool throws an exception, the thread pool will not be scheduled; The following is a simple analysis of why the exception causes the ScheduledThreadPoolExecutor not to execute through an example.

ScheduledThreadPoolExecutor analysis

Sample program

In the example program, we can see that when the count in the counter reaches 5, an exception will be thrown actively. After the exception is thrown, the ScheduledThreadPoolExecutor will not schedule.

public class ScheduledTask {

    private static final AtomicInteger count = new AtomicInteger(0);

    private static final ScheduledThreadPoolExecutor SCHEDULED_TASK = new ScheduledThreadPoolExecutor(
            1, new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(Thread.currentThread().getThreadGroup(), r, "sc-task");
            t.setDaemon(true);
            return t;
        }
    });

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(1);
        SCHEDULED_TASK.scheduleWithFixedDelay(() -> {
            System.out.println(111);
            if (count.get() == 5) {
                throw new IllegalArgumentException("my exception");
            }
            count.incrementAndGet();
        }, 0, 5, TimeUnit.SECONDS);
        latch.await();
    }
}

Source code analysis

  • ScheduledThreadPoolExecutor#run

Task periodicity is the first method to judge whether the task is periodic or not through etrunedusk super. run(); Perform tasks; If the status is running or shutdown, cancel the task execution; For periodic tasks, use scheduledfuturetask super. Runandreset() executes the task and resets the status. If it succeeds, it will execute setNextRunTime to set the next scheduling time. The problem is that it occurs in scheduledfuturetask super. Runandreset(), there is an exception in the execution of the task, resulting in the result of false, so the next scheduling time will not be set

        public void run() {
            boolean periodic = isPeriodic();
            if (!canRunInCurrentRunState(periodic))
                cancel(false);
            else if (!periodic)
                ScheduledFutureTask.super.run();
            else if (ScheduledFutureTask.super.runAndReset()) {
                setNextRunTime();
                reExecutePeriodic(outerTask);
            }
        }
  • *FutureTask#runAndReset

An exception is thrown during the execution of the thread task, and then the exception is caught, which eventually causes the method to return false. Then the ScheduledThreadPoolExecutor#run will not set the next execution time. The code is c.call(); Throw an exception, skip ran = true; Code, and finally runAndReset returns false.

    protected boolean runAndReset() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return false;
        boolean ran = false;
        int s = state;
        try {
            Callable<V> c = callable;
            if (c != null && s == NEW) {
                try {
                    c.call(); // don't set result
                    ran = true;
                } catch (Throwable ex) {
                    setException(ex);
                }
            }
        } 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
            s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
        return ran && s == NEW;
    }

summary

If an exception is thrown in the task scheduled by the scheduled task pool of Java's ScheduledThreadPoolExecutor, and the exception is not captured and directly thrown into the framework, the scheduled task of ScheduledThreadPoolExecutor will not be scheduled. Specifically, when the exception is thrown into the ScheduledThreadPoolExecutor framework, the next scheduling time will not be set, As a result, the scheduled task of ScheduledThreadPoolExecutor is not scheduled.

Topics: Java