blob: e414c98399bd9932b31eec255070be56cc52d7a9 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. The ASF licenses this file to You
* 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
*
* 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.
* See the License for the specific language governing permissions and
* limitations under the License. For additional information regarding
* copyright in this work, please see the NOTICE file in the top level
* directory of this distribution.
*/
package org.apache.roller.weblogger.business.runnable;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.roller.util.DateUtil;
import org.apache.roller.util.RollerConstants;
import org.apache.roller.weblogger.business.WebloggerFactory;
import org.apache.roller.weblogger.pojos.TaskLock;
import static org.apache.roller.util.RollerConstants.GRACEFUL_SHUTDOWN_WAIT_IN_SECONDS;
/**
* Manages scheduling of periodic tasks.
*
* This scheduler is meant to be run on a single thread and once started it will
* run continuously until the thread is interrupted. The basic logic of the
* scheduler is to accept some number of tasks to be run and once per minute
* the scheduler will launch any tasks that need to be executed.
*
* Tasks are executed each on their own thread, so this scheduler does not run
* serially like a TimerTask. The threads used for running tasks are managed
* by an instance of a ThreadPoolExecutor.
*/
public class TaskScheduler implements Runnable {
private static Log log = LogFactory.getLog(TaskScheduler.class);
private final ExecutorService pool;
private final List<RollerTask> tasks;
public TaskScheduler(List<RollerTask> webloggerTasks) {
// store list of tasks available to run
tasks = webloggerTasks;
// use an expanding thread executor pool
pool = Executors.newCachedThreadPool();
}
public void run() {
boolean firstRun = true;
// run forever, or until we get interrupted
while (!Thread.currentThread().isInterrupted()) {
try {
// get current time
Date now = new Date();
log.debug("Current time = "+now);
// run tasks, skip run on first pass
if(firstRun) {
// add a slight delay to scheduler start
Calendar cal = Calendar.getInstance();
cal.setTime(now);
cal.add(Calendar.MINUTE, 1);
cal.set(Calendar.SECOND, cal.getMinimum(Calendar.SECOND));
cal.set(Calendar.MILLISECOND, cal.getMinimum(Calendar.MILLISECOND));
now = cal.getTime();
log.debug("Start time = "+now);
firstRun = false;
} else {
try {
runTasks(now);
} finally {
// always release session after each pass
WebloggerFactory.getWeblogger().release();
}
}
// wait 'til next minute
// NOTE: we add 50ms of adjustment time to make sure we awaken
// during the next minute, and not before. awakening at
// exactly the .000ms is not of any concern to us
Date endOfMinute = DateUtil.getEndOfMinute(now);
long sleepTime = (endOfMinute.getTime() + 50) - System.currentTimeMillis();
if(sleepTime > 0) {
log.debug("sleeping - "+sleepTime);
Thread.sleep(sleepTime);
} else {
// it's taken us more than 1 minute for the last loop
// so recalculate to sleep 'til the end of the current minute
endOfMinute = DateUtil.getEndOfMinute(new Date());
sleepTime = (endOfMinute.getTime() + 50) - System.currentTimeMillis();
log.debug("sleeping - "+sleepTime);
Thread.sleep(sleepTime);
}
} catch (InterruptedException ex) {
log.debug("Thread interrupted, scheduler is stopping");
break;
}
}
// thread interrupted
pool.shutdownNow();
try {
pool.awaitTermination(GRACEFUL_SHUTDOWN_WAIT_IN_SECONDS, TimeUnit.SECONDS);
log.debug("TaskScheduler executor was terminated successfully");
} catch (InterruptedException e) {
log.debug(e.getMessage(), e);
}
}
/**
* Run the necessary tasks given a specific currentTime to work from.
*/
private void runTasks(Date currentTime) {
log.debug("Started - "+currentTime);
ThreadManager tmgr = WebloggerFactory.getWeblogger().getThreadManager();
for (RollerTask task : tasks) {
try {
// get tasklock for the task
TaskLock tasklock = tmgr.getTaskLockByName(task.getName());
// TODO: check if task is enabled, otherwise skip
if (tasklock == null) {
continue;
}
// first, calculate the next allowed run time for the task
// based on when the task was last run
Date nextRunTime = tasklock.getNextAllowedRun(task.getInterval());
log.debug(task.getName()+": next allowed run time = "+nextRunTime);
// if we missed the last scheduled run time then see when the
// most appropriate next run time should be and wait 'til then
boolean needToWait = false;
if(currentTime.getTime() > (nextRunTime.getTime() + RollerConstants.MIN_IN_MS)) {
log.debug("MISSED last run, checking if waiting is necessary");
// add delays if task is non-immediate
if ("startOfDay".equals(task.getStartTimeDesc())) {
// for daily tasks we only run during the first
// couple minutes of the day
Date startOfDay = DateUtil.getStartOfDay(currentTime);
if(currentTime.getTime() > startOfDay.getTime() + (2 * RollerConstants.MIN_IN_MS)) {
needToWait = true;
log.debug("WAITING for next reasonable run time");
}
} else if("startOfHour".equals(task.getStartTimeDesc())) {
// for hourly tasks we only run during the first
// couple minutes of the hour
Date startOfHour = DateUtil.getStartOfHour(currentTime);
if(currentTime.getTime() > startOfHour.getTime() + (2 * RollerConstants.MIN_IN_MS)) {
needToWait = true;
log.debug("WAITING for next reasonable run time");
}
}
}
// if we are within 1 minute of run time then execute,
// otherwise we do nothing
long differential = currentTime.getTime() - nextRunTime.getTime();
if (differential >= 0 && !needToWait) {
log.debug(task.getName()+": LAUNCHING task");
pool.submit(task);
}
} catch (ThreadDeath t) {
throw t;
} catch (Throwable t) {
log.warn(task.getName() + ": Unhandled exception caught", t);
}
}
log.debug("Finished");
}
}