diff --git a/common/util/src/main/java/org/thingsboard/common/util/ThingsBoardExecutors.java b/common/util/src/main/java/org/thingsboard/common/util/ThingsBoardExecutors.java index 1111a2aa55..9bd1abb437 100644 --- a/common/util/src/main/java/org/thingsboard/common/util/ThingsBoardExecutors.java +++ b/common/util/src/main/java/org/thingsboard/common/util/ThingsBoardExecutors.java @@ -25,8 +25,11 @@ import java.util.concurrent.TimeUnit; public class ThingsBoardExecutors { + /** Cannot instantiate. */ + private ThingsBoardExecutors(){} + /** - * Method forked from ExecutorService to provide thread poll name + * Method forked from ExecutorService to provide thread pool name * * Creates a thread pool that maintains enough threads to support * the given parallelism level, and may use multiple queues to diff --git a/common/util/src/main/java/org/thingsboard/common/util/ThingsBoardScheduledThreadPoolExecutor.java b/common/util/src/main/java/org/thingsboard/common/util/ThingsBoardScheduledThreadPoolExecutor.java index 8b4d9bf511..fd3ecb085c 100644 --- a/common/util/src/main/java/org/thingsboard/common/util/ThingsBoardScheduledThreadPoolExecutor.java +++ b/common/util/src/main/java/org/thingsboard/common/util/ThingsBoardScheduledThreadPoolExecutor.java @@ -1,12 +1,12 @@ /** * Copyright © 2016-2024 The Thingsboard Authors - * + *

* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -17,9 +17,6 @@ package org.thingsboard.common.util; import lombok.extern.slf4j.Slf4j; -import java.util.concurrent.CancellationException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadFactory; @@ -32,67 +29,35 @@ final class ThingsBoardScheduledThreadPoolExecutor extends ScheduledThreadPoolEx super(corePoolSize, threadFactory); } - @Override - protected void afterExecute(Runnable r, Throwable t) { - super.afterExecute(r, t); - logExceptionsAfterExecute(r, t); - } - - private static void logExceptionsAfterExecute(Runnable r, Throwable directThrowable) { - Throwable wrappedThrowable = extractThrowableWrappedInFuture(r); - if (wrappedThrowable != null) { - if (wrappedThrowable instanceof CancellationException) { - log.debug("Task was cancelled.", wrappedThrowable); - } else { - log.error("Uncaught error occurred during task execution!", wrappedThrowable); - } - } - - if (directThrowable != null) { // should never be true since ScheduledThreadPoolExecutor wraps tasks in Future's - log.error("Caught direct throwable from task submitted to the scheduled thread pool executor!", directThrowable); - } - } - - private static Throwable extractThrowableWrappedInFuture(Runnable runnable) { - if (runnable instanceof Future future && future.isDone()) { - try { - future.get(); - } catch (InterruptedException e) { // should not happen due to isDone() check - throw new AssertionError("InterruptedException caught after isDone() check on a future", e); - } catch (CancellationException e) { - return e; - } catch (ExecutionException e) { - return e.getCause(); - } - } - return null; - } - @Override public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { if (command == null) { // preserve the original NPE behavior of ScheduledThreadPoolExecutor with a more helpful message - throw new NullPointerException("command is null"); + throw new NullPointerException("Command is null"); } - return super.scheduleAtFixedRate(new SafePeriodicRunnable(command), initialDelay, period, unit); + return super.scheduleAtFixedRate(new PeriodicRunnable(command), initialDelay, period, unit); } @Override public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { if (command == null) { // preserve the original NPE behavior of ScheduledThreadPoolExecutor with a more helpful message - throw new NullPointerException("command is null"); + throw new NullPointerException("Command is null"); } - return super.scheduleWithFixedDelay(new SafePeriodicRunnable(command), initialDelay, delay, unit); + return super.scheduleWithFixedDelay(new PeriodicRunnable(command), initialDelay, delay, unit); } - private record SafePeriodicRunnable(Runnable runnable) implements Runnable { + private record PeriodicRunnable(Runnable runnable) implements Runnable { public void run() { try { runnable.run(); - } catch (Exception ex) { - log.error("Uncaught exception occurred during periodic task execution!", ex); + } catch (Exception e) { + // Log exceptions but do not propagate it. This ensures that subsequent scheduled tasks will still run. + log.error("Uncaught exception occurred during periodic task execution!", e); + } catch (Throwable th) { + // Log and rethrow other serious issues that are not regular Exceptions. + log.error("Critical exception occurred during periodic task execution!", th); + throw th; } - // Intentionally, no catch block for Throwable; uncaught Throwables will be handled in afterExecute() } }