001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.util.thread; 018 019import java.time.Duration; 020import java.time.Instant; 021 022import org.slf4j.Logger; 023import org.slf4j.LoggerFactory; 024 025 026/** 027 * Runs a block of code periodically. A <code>Task</code> can be started at a given time in the 028 * future and can be a daemon. The block of code will be passed a <code>Log</code> object each time 029 * it is run through its <code>ICode</code> interface. 030 * <p> 031 * If the code block takes longer than the period to run, the next task invocation will occur 032 * immediately. In this case, tasks will not occur at precise multiples of the period. For example, 033 * if you run a task every 30 seconds, and the first run takes 40 seconds but the second takes 20 034 * seconds, your task will be invoked at 0 seconds, 40 seconds and 70 seconds (40 seconds + 30 035 * seconds), which is not an even multiple of 30 seconds. 036 * <p> 037 * In general, this is a simple task class designed for polling activities. If you need precise 038 * guarantees, you probably should be using a different task class. 039 * 040 * @author Jonathan Locke 041 * @since 1.2.6 042 */ 043public final class Task 044{ 045 /** <code>true</code> if the task's thread should be a daemon */ 046 private boolean isDaemon = true; 047 048 /** <code>true</code> if the task's thread has already started executing */ 049 private boolean isStarted = false; 050 051 /** the <code>log</code> to give to the user's code */ 052 private Logger log = null; 053 054 /** the name of this <code>Task</code> */ 055 private final String name; 056 057 /** the <code>Instant</code> at which the task should start */ 058 private Instant startTime = Instant.now(); 059 060 /** When set the task will stop as soon as possible */ 061 private boolean stop; 062 063 /** each <code>Task</code> has an associated <code>Thread</code> */ 064 private Thread thread; 065 066 /** 067 * Constructor. 068 * 069 * @param name 070 * the name of this <code>Task</code> 071 */ 072 public Task(final String name) 073 { 074 this.name = name; 075 } 076 077 /** 078 * Runs this <code>Task</code> at the given frequency. You may only call this method if the task 079 * has not yet been started. If the task is already running, an 080 * <code>IllegalStateException</code> will be thrown. 081 * 082 * @param frequency 083 * the frequency at which to run the code 084 * @param code 085 * the code to run 086 * @throws IllegalStateException 087 * thrown if task is already running 088 */ 089 public synchronized final void run(final Duration frequency, final ICode code) 090 { 091 if (!isStarted) 092 { 093 final Runnable runnable = new Runnable() 094 { 095 @Override 096 public void run() 097 { 098 // Sleep until start time 099 Duration untilStart = Duration.between(startTime, Instant.now()); 100 101 final Logger log = getLog(); 102 103 if (!untilStart.isNegative()) 104 { 105 try 106 { 107 Thread.sleep(untilStart.toMillis()); 108 } 109 catch (InterruptedException e) 110 { 111 log.error("An error occurred during sleeping phase.", e); 112 } 113 } 114 115 try 116 { 117 while (!stop) 118 { 119 // Get the start of the current period 120 final Instant startOfPeriod = Instant.now(); 121 122 if (log.isTraceEnabled()) 123 { 124 log.trace("Run the job: '{}'", code); 125 } 126 127 try 128 { 129 // Run the user's code 130 code.run(getLog()); 131 } 132 catch (Exception e) 133 { 134 log.error( 135 "Unhandled exception thrown by user code in task " + name, e); 136 } 137 138 if (log.isTraceEnabled()) 139 { 140 log.trace("Finished with job: '{}'", code); 141 } 142 143 // Sleep until the period is over (or not at all if it's 144 // already passed) 145 Instant nextExecution = startOfPeriod.plus(frequency); 146 147 Duration timeToNextExecution = Duration.between(Instant.now(), nextExecution); 148 149 if (!timeToNextExecution.isNegative()) 150 { 151 try { 152 Thread.sleep(timeToNextExecution.toMillis()); 153 } 154 catch (InterruptedException e) { 155 Thread.currentThread().interrupt(); 156 } 157 } 158 } 159 log.trace("Task '{}' stopped", name); 160 } 161 catch (Exception x) 162 { 163 log.error("Task '{}' terminated", name, x); 164 } 165 finally 166 { 167 isStarted = false; 168 } 169 } 170 }; 171 172 // Start the thread 173 thread = new Thread(runnable, name + " Task"); 174 thread.setDaemon(isDaemon); 175 thread.start(); 176 177 // We're started all right! 178 isStarted = true; 179 } 180 else 181 { 182 throw new IllegalStateException("Attempt to start task that has already been started"); 183 } 184 } 185 186 /** 187 * Sets daemon or not. For obvious reasons, this value can only be set before the task starts 188 * running. If you attempt to set this value after the task starts running, an 189 * <code>IllegalStateException</code> will be thrown. 190 * 191 * @param daemon 192 * <code>true</code> if this <code>Task</code>'s <code>Thread</code> should be a 193 * daemon 194 * @throws IllegalStateException 195 * thrown if task is already running 196 */ 197 public synchronized void setDaemon(final boolean daemon) 198 { 199 if (isStarted) 200 { 201 throw new IllegalStateException( 202 "Attempt to set daemon state of a task that has already been started"); 203 } 204 205 isDaemon = daemon; 206 } 207 208 /** 209 * Sets log for user code to log to when task runs. 210 * 211 * @param log 212 * the log 213 */ 214 public synchronized void setLog(final Logger log) 215 { 216 this.log = log; 217 } 218 219 /** 220 * Sets start time for this task. You cannot set the start time for a task which is already 221 * running. If you attempt to, an IllegalStateException will be thrown. 222 * 223 * @param startTime 224 * The time this task should start running 225 * @throws IllegalStateException 226 * Thrown if task is already running 227 */ 228 public synchronized void setStartTime(final Instant startTime) 229 { 230 if (isStarted) 231 { 232 throw new IllegalStateException( 233 "Attempt to set start time of task that has already been started"); 234 } 235 236 this.startTime = startTime; 237 } 238 239 /** 240 * @see java.lang.Object#toString() 241 */ 242 @Override 243 public String toString() 244 { 245 return "[name=" + name + ", startTime=" + startTime + ", isDaemon=" + isDaemon + 246 ", isStarted=" + isStarted + ", codeListener=" + log + "]"; 247 } 248 249 /** 250 * Gets the log for this <code>Task</code>. 251 * 252 * @return the log 253 */ 254 protected synchronized Logger getLog() 255 { 256 if (log == null) 257 { 258 log = LoggerFactory.getLogger(Task.class); 259 } 260 return log; 261 } 262 263 /** 264 * Stops this <code>Task</code> as soon as it has the opportunity. 265 */ 266 public void stop() 267 { 268 stop = true; 269 } 270 271 /** 272 * Interrupts the <code>Task</code> as soon as it has the opportunity. 273 */ 274 public void interrupt() 275 { 276 stop(); 277 if (thread != null) 278 { 279 thread.interrupt(); 280 } 281 } 282 283 /** 284 * Sets the priority of the thread 285 * 286 * @param prio 287 */ 288 public void setPriority(int prio) 289 { 290 if (prio < Thread.MIN_PRIORITY) 291 { 292 prio = Thread.MIN_PRIORITY; 293 } 294 else if (prio > Thread.MAX_PRIORITY) 295 { 296 prio = Thread.MAX_PRIORITY; 297 } 298 thread.setPriority(prio); 299 } 300 301 /** 302 * Gets the thread priority 303 * 304 * @return priority 305 */ 306 public int getPriority() 307 { 308 return thread.getPriority(); 309 } 310}