1212 */
1313package org .openhab .core .internal .common ;
1414
15+ import java .time .Duration ;
16+ import java .time .Instant ;
17+ import java .util .Objects ;
18+ import java .util .concurrent .Callable ;
1519import java .util .concurrent .CancellationException ;
1620import java .util .concurrent .ExecutionException ;
1721import java .util .concurrent .Future ;
22+ import java .util .concurrent .ScheduledFuture ;
1823import java .util .concurrent .ScheduledThreadPoolExecutor ;
1924import java .util .concurrent .ThreadFactory ;
25+ import java .util .concurrent .TimeUnit ;
2026
2127import org .eclipse .jdt .annotation .NonNullByDefault ;
2228import org .eclipse .jdt .annotation .Nullable ;
@@ -38,10 +44,81 @@ public class WrappedScheduledExecutorService extends ScheduledThreadPoolExecutor
3844
3945 final Logger logger = LoggerFactory .getLogger (WrappedScheduledExecutorService .class );
4046
47+ private static final Duration DEFAULT_TIMEOUT = Duration .ofMillis (5000 );
48+
4149 public WrappedScheduledExecutorService (int corePoolSize , ThreadFactory threadFactory ) {
4250 super (corePoolSize , threadFactory );
4351 }
4452
53+ /**
54+ * A base class that checks the time a scheduled task takes to complete, and if it takes too long,
55+ * it outputs a log message with the stack trace from whence the task was originally scheduled.
56+ */
57+ private abstract class TimedAbstractTask {
58+ private final Exception stackTraceHolder ;
59+ private Instant timeout ;
60+
61+ protected TimedAbstractTask () {
62+ this .stackTraceHolder = new Exception ();
63+ this .timeout = Instant .MAX ;
64+ }
65+
66+ protected void clockStart () {
67+ timeout = Instant .now ().plus (DEFAULT_TIMEOUT );
68+ }
69+
70+ protected void clockStop () {
71+ if (Instant .now ().isAfter (timeout )) {
72+ logger .debug ("Scheduled task took more than {}; it was created here: " , DEFAULT_TIMEOUT ,
73+ stackTraceHolder );
74+ }
75+ }
76+ }
77+
78+ /**
79+ * A running time checker for a {@link Runnable}
80+ */
81+ private class TimedRunnable extends TimedAbstractTask implements Runnable {
82+ private final Runnable runnable ;
83+
84+ protected TimedRunnable (@ Nullable Runnable runnable ) {
85+ super ();
86+ this .runnable = Objects .requireNonNull (runnable );
87+ }
88+
89+ @ Override
90+ public void run () {
91+ try {
92+ clockStart ();
93+ runnable .run ();
94+ } finally {
95+ clockStop ();
96+ }
97+ }
98+ }
99+
100+ /**
101+ * A running time checker for a {@link Callable}
102+ */
103+ private class TimedCallable <V > extends TimedAbstractTask implements Callable <V > {
104+ private final Callable <V > callable ;
105+
106+ protected TimedCallable (@ Nullable Callable <V > callable ) {
107+ super ();
108+ this .callable = Objects .requireNonNull (callable );
109+ }
110+
111+ @ Override
112+ public V call () throws Exception {
113+ try {
114+ clockStart ();
115+ return callable .call ();
116+ } finally {
117+ clockStop ();
118+ }
119+ }
120+ }
121+
45122 @ Override
46123 protected void afterExecute (@ Nullable Runnable r , @ Nullable Throwable t ) {
47124 super .afterExecute (r , t );
@@ -68,4 +145,30 @@ protected void afterExecute(@Nullable Runnable r, @Nullable Throwable t) {
68145 logger .warn ("Scheduled runnable ended with an exception: " , actualThrowable );
69146 }
70147 }
148+
149+ @ Override
150+ public ScheduledFuture <?> schedule (@ Nullable Runnable runnable , long delay , @ Nullable TimeUnit unit ) {
151+ Runnable r = logger .isDebugEnabled () ? new TimedRunnable (runnable ) : runnable ;
152+ return super .schedule (r , delay , unit );
153+ }
154+
155+ @ Override
156+ public ScheduledFuture <?> scheduleAtFixedRate (@ Nullable Runnable runnable , long initialDelay , long period ,
157+ @ Nullable TimeUnit unit ) {
158+ Runnable r = logger .isDebugEnabled () ? new TimedRunnable (runnable ) : runnable ;
159+ return super .scheduleAtFixedRate (r , initialDelay , period , unit );
160+ }
161+
162+ @ Override
163+ public ScheduledFuture <?> scheduleWithFixedDelay (@ Nullable Runnable runnable , long initialDelay , long delay ,
164+ @ Nullable TimeUnit unit ) {
165+ Runnable r = logger .isDebugEnabled () ? new TimedRunnable (runnable ) : runnable ;
166+ return super .scheduleWithFixedDelay (r , initialDelay , delay , unit );
167+ }
168+
169+ @ Override
170+ public <V > ScheduledFuture <V > schedule (@ Nullable Callable <V > callable , long delay , @ Nullable TimeUnit unit ) {
171+ Callable <V > c = logger .isDebugEnabled () ? new TimedCallable <>(callable ) : callable ;
172+ return super .schedule (c , delay , unit );
173+ }
71174}
0 commit comments