From b982798bfeaccd40a7f5591c629ccbc93932ebce Mon Sep 17 00:00:00 2001 From: Andrey Subbotin Date: Fri, 24 Jun 2016 13:57:36 +0400 Subject: [PATCH] PL-6818 Support fixed-delay in Scheduled Tasks mechanism --- modules/core/db/init/hsql/create-db.sql | 1 + modules/core/db/init/mssql/create-db.sql | 1 + modules/core/db/init/mysql/create-db.sql | 1 + modules/core/db/init/oracle/create-db.sql | 1 + modules/core/db/init/postgres/create-db.sql | 1 + ...ateIndexOnFinishTimeScheduledExecution.sql | 1 + ...ateIndexOnFinishTimeScheduledExecution.sql | 1 + ...ateIndexOnFinishTimeScheduledExecution.sql | 1 + ...ateIndexOnFinishTimeScheduledExecution.sql | 1 + ...ateIndexOnFinishTimeScheduledExecution.sql | 1 + .../cuba/core/app/scheduling/Coordinator.java | 3 + .../app/scheduling/DbBasedCoordinator.java | 13 +- .../cuba/core/app/scheduling/RunnerBean.java | 56 +++--- .../cuba/core/app/scheduling/Scheduling.java | 163 +++++++++++------- .../core/app/scheduling/SchedulingAPI.java | 9 +- .../core/app/scheduling/SchedulingTest.java | 18 +- .../cuba/core/entity/SchedulingType.java | 3 +- .../cuba/core/entity/messages.properties | 1 + .../cuba/core/entity/messages_ru.properties | 1 + 19 files changed, 173 insertions(+), 104 deletions(-) create mode 100644 modules/core/db/update/hsql/16/160626-createIndexOnFinishTimeScheduledExecution.sql create mode 100644 modules/core/db/update/mssql/16/160626-createIndexOnFinishTimeScheduledExecution.sql create mode 100644 modules/core/db/update/mysql/16/160626-createIndexOnFinishTimeScheduledExecution.sql create mode 100644 modules/core/db/update/oracle/16/160626-createIndexOnFinishTimeScheduledExecution.sql create mode 100644 modules/core/db/update/postgres/16/160626-createIndexOnFinishTimeScheduledExecution.sql diff --git a/modules/core/db/init/hsql/create-db.sql b/modules/core/db/init/hsql/create-db.sql index a70ad03920..6d2699e516 100644 --- a/modules/core/db/init/hsql/create-db.sql +++ b/modules/core/db/init/hsql/create-db.sql @@ -144,6 +144,7 @@ create table SYS_SCHEDULED_EXECUTION ( )^ create index IDX_SYS_SCHEDULED_EXECUTION_TASK_START_TIME on SYS_SCHEDULED_EXECUTION (TASK_ID, START_TIME)^ +create index IDX_SYS_SCHEDULED_EXECUTION_TASK_FINISH_TIME on SYS_SCHEDULED_EXECUTION (TASK_ID, FINISH_TIME)^ ------------------------------------------------------------------------------------------------------------ diff --git a/modules/core/db/init/mssql/create-db.sql b/modules/core/db/init/mssql/create-db.sql index 86cd72ec74..aa701688d6 100644 --- a/modules/core/db/init/mssql/create-db.sql +++ b/modules/core/db/init/mssql/create-db.sql @@ -142,6 +142,7 @@ create table SYS_SCHEDULED_EXECUTION ( )^ create index IDX_SYS_SCHEDULED_EXECUTION_TASK_START_TIME on SYS_SCHEDULED_EXECUTION (TASK_ID, START_TIME)^ +create index IDX_SYS_SCHEDULED_EXECUTION_TASK_FINISH_TIME on SYS_SCHEDULED_EXECUTION (TASK_ID, FINISH_TIME)^ create clustered index IDX_SYS_SCHEDULED_EXECUTION_CREATE_TS on SYS_SCHEDULED_EXECUTION (CREATE_TS)^ diff --git a/modules/core/db/init/mysql/create-db.sql b/modules/core/db/init/mysql/create-db.sql index c5d9d9d226..d910f4800f 100644 --- a/modules/core/db/init/mysql/create-db.sql +++ b/modules/core/db/init/mysql/create-db.sql @@ -141,6 +141,7 @@ create table SYS_SCHEDULED_EXECUTION ( )^ create index IDX_SYS_SCHEDULED_EXECUTION_TASK_START_TIME on SYS_SCHEDULED_EXECUTION (TASK_ID, START_TIME)^ +create index IDX_SYS_SCHEDULED_EXECUTION_TASK_FINISH_TIME on SYS_SCHEDULED_EXECUTION (TASK_ID, FINISH_TIME)^ /**********************************************************************************************/ diff --git a/modules/core/db/init/oracle/create-db.sql b/modules/core/db/init/oracle/create-db.sql index 8cdd24b791..7345ff12de 100644 --- a/modules/core/db/init/oracle/create-db.sql +++ b/modules/core/db/init/oracle/create-db.sql @@ -233,6 +233,7 @@ create table SYS_SCHEDULED_EXECUTION ( primary key(ID) )^ create index IDX_SYS_SCH_EXE_TAS_STA_TIM on SYS_SCHEDULED_EXECUTION(TASK_ID, START_TIME)^ +create index IDX_SYS_SCH_EXE_TAS_FI_TIM on SYS_SCHEDULED_EXECUTION(TASK_ID, FINISH_TIME)^ create table SYS_SCHEDULED_TASK ( ID varchar2(32) not null, diff --git a/modules/core/db/init/postgres/create-db.sql b/modules/core/db/init/postgres/create-db.sql index 86cb38cfb4..cc2ff81e5d 100644 --- a/modules/core/db/init/postgres/create-db.sql +++ b/modules/core/db/init/postgres/create-db.sql @@ -141,6 +141,7 @@ create table SYS_SCHEDULED_EXECUTION ( )^ create index IDX_SYS_SCHEDULED_EXECUTION_TASK_START_TIME on SYS_SCHEDULED_EXECUTION (TASK_ID, START_TIME)^ +create index IDX_SYS_SCHEDULED_EXECUTION_TASK_FINISH_TIME on SYS_SCHEDULED_EXECUTION (TASK_ID, FINISH_TIME)^ ------------------------------------------------------------------------------------------------------------ diff --git a/modules/core/db/update/hsql/16/160626-createIndexOnFinishTimeScheduledExecution.sql b/modules/core/db/update/hsql/16/160626-createIndexOnFinishTimeScheduledExecution.sql new file mode 100644 index 0000000000..ecb8df1b69 --- /dev/null +++ b/modules/core/db/update/hsql/16/160626-createIndexOnFinishTimeScheduledExecution.sql @@ -0,0 +1 @@ +create index IDX_SYS_SCHEDULED_EXECUTION_TASK_FINISH_TIME on SYS_SCHEDULED_EXECUTION (TASK_ID, FINISH_TIME)^ \ No newline at end of file diff --git a/modules/core/db/update/mssql/16/160626-createIndexOnFinishTimeScheduledExecution.sql b/modules/core/db/update/mssql/16/160626-createIndexOnFinishTimeScheduledExecution.sql new file mode 100644 index 0000000000..ecb8df1b69 --- /dev/null +++ b/modules/core/db/update/mssql/16/160626-createIndexOnFinishTimeScheduledExecution.sql @@ -0,0 +1 @@ +create index IDX_SYS_SCHEDULED_EXECUTION_TASK_FINISH_TIME on SYS_SCHEDULED_EXECUTION (TASK_ID, FINISH_TIME)^ \ No newline at end of file diff --git a/modules/core/db/update/mysql/16/160626-createIndexOnFinishTimeScheduledExecution.sql b/modules/core/db/update/mysql/16/160626-createIndexOnFinishTimeScheduledExecution.sql new file mode 100644 index 0000000000..ecb8df1b69 --- /dev/null +++ b/modules/core/db/update/mysql/16/160626-createIndexOnFinishTimeScheduledExecution.sql @@ -0,0 +1 @@ +create index IDX_SYS_SCHEDULED_EXECUTION_TASK_FINISH_TIME on SYS_SCHEDULED_EXECUTION (TASK_ID, FINISH_TIME)^ \ No newline at end of file diff --git a/modules/core/db/update/oracle/16/160626-createIndexOnFinishTimeScheduledExecution.sql b/modules/core/db/update/oracle/16/160626-createIndexOnFinishTimeScheduledExecution.sql new file mode 100644 index 0000000000..d4406900b4 --- /dev/null +++ b/modules/core/db/update/oracle/16/160626-createIndexOnFinishTimeScheduledExecution.sql @@ -0,0 +1 @@ +create index IDX_SYS_SCH_EXE_TAS_FI_TIM on SYS_SCHEDULED_EXECUTION (TASK_ID, FINISH_TIME) \ No newline at end of file diff --git a/modules/core/db/update/postgres/16/160626-createIndexOnFinishTimeScheduledExecution.sql b/modules/core/db/update/postgres/16/160626-createIndexOnFinishTimeScheduledExecution.sql new file mode 100644 index 0000000000..ecb8df1b69 --- /dev/null +++ b/modules/core/db/update/postgres/16/160626-createIndexOnFinishTimeScheduledExecution.sql @@ -0,0 +1 @@ +create index IDX_SYS_SCHEDULED_EXECUTION_TASK_FINISH_TIME on SYS_SCHEDULED_EXECUTION (TASK_ID, FINISH_TIME)^ \ No newline at end of file diff --git a/modules/core/src/com/haulmont/cuba/core/app/scheduling/Coordinator.java b/modules/core/src/com/haulmont/cuba/core/app/scheduling/Coordinator.java index 0594251acf..d5a0ded1e1 100644 --- a/modules/core/src/com/haulmont/cuba/core/app/scheduling/Coordinator.java +++ b/modules/core/src/com/haulmont/cuba/core/app/scheduling/Coordinator.java @@ -19,6 +19,7 @@ package com.haulmont.cuba.core.app.scheduling; import com.haulmont.cuba.core.entity.ScheduledTask; +import java.util.Date; import java.util.List; /** @@ -40,4 +41,6 @@ public interface Coordinator { void end(Context context); boolean isLastExecutionFinished(ScheduledTask task, long now); + + long getLastFinished(ScheduledTask task); } diff --git a/modules/core/src/com/haulmont/cuba/core/app/scheduling/DbBasedCoordinator.java b/modules/core/src/com/haulmont/cuba/core/app/scheduling/DbBasedCoordinator.java index 6fa366c07e..b08a51e84c 100644 --- a/modules/core/src/com/haulmont/cuba/core/app/scheduling/DbBasedCoordinator.java +++ b/modules/core/src/com/haulmont/cuba/core/app/scheduling/DbBasedCoordinator.java @@ -24,8 +24,8 @@ import com.haulmont.cuba.core.Transaction; import com.haulmont.cuba.core.entity.ScheduledTask; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import org.springframework.stereotype.Component; + import javax.inject.Inject; import javax.persistence.LockModeType; import java.util.Date; @@ -35,7 +35,6 @@ import java.util.List; * Implementation of {@link Coordinator} interface, performing synchronization of singleton schedulers on the main * database. *

This implementation should not be used if the database is overloaded.

- * */ @Component(Coordinator.NAME) public class DbBasedCoordinator implements Coordinator { @@ -108,6 +107,16 @@ public class DbBasedCoordinator implements Coordinator { } } + @Override + public long getLastFinished(ScheduledTask task) { + EntityManager em = persistence.getEntityManager(); + Query query = em.createQuery( + "select max(e.finishTime) from sys$ScheduledExecution e where e.task.id = ?1") + .setParameter(1, task.getId()); + Date date = (Date) query.getFirstResult(); + return date == null ? 0 : date.getTime(); + } + protected synchronized List getTasks() { log.trace("Read all active tasks from DB and lock them"); EntityManager em = persistence.getEntityManager(); diff --git a/modules/core/src/com/haulmont/cuba/core/app/scheduling/RunnerBean.java b/modules/core/src/com/haulmont/cuba/core/app/scheduling/RunnerBean.java index 434aef83e8..8fe5a6ded4 100644 --- a/modules/core/src/com/haulmont/cuba/core/app/scheduling/RunnerBean.java +++ b/modules/core/src/com/haulmont/cuba/core/app/scheduling/RunnerBean.java @@ -25,6 +25,7 @@ import com.haulmont.cuba.core.app.ServerInfoAPI; import com.haulmont.cuba.core.app.scheduled.MethodParameterInfo; import com.haulmont.cuba.core.entity.ScheduledExecution; import com.haulmont.cuba.core.entity.ScheduledTask; +import com.haulmont.cuba.core.entity.SchedulingType; import com.haulmont.cuba.core.global.AppBeans; import com.haulmont.cuba.core.global.Metadata; import com.haulmont.cuba.core.global.Scripting; @@ -104,33 +105,31 @@ public class RunnerBean implements Runner { // It's better not to pass an entity instance in managed state to another thread final ScheduledTask taskCopy = metadata.getTools().copy(task); - executorService.submit(new Runnable() { - @Override - public void run() { - log.debug(taskCopy + ": running"); - try { - boolean runConcurrent = scheduling.setRunning(taskCopy, true); - if (!runConcurrent) { + executorService.submit(() -> { + log.debug("{}: running", taskCopy); + try { + boolean runConcurrent = scheduling.setRunning(taskCopy, true); + if (!runConcurrent) { + try { + setSecurityContext(taskCopy, userSession); + ScheduledExecution execution = registerExecutionStart(taskCopy, now); + statisticsCounter.incCubaScheduledTasksCount(); try { - setSecurityContext(taskCopy, userSession); - ScheduledExecution execution = registerExecutionStart(taskCopy, now); - statisticsCounter.incCubaScheduledTasksCount(); - try { - Object result = executeTask(taskCopy); - registerExecutionFinish(taskCopy, execution, result); - } catch (Throwable throwable) { - registerExecutionFinish(taskCopy, execution, throwable); - throw throwable; - } - } finally { - scheduling.setRunning(taskCopy, false); + Object result = executeTask(taskCopy); + registerExecutionFinish(taskCopy, execution, result); + } catch (Throwable throwable) { + registerExecutionFinish(taskCopy, execution, throwable); + throw throwable; } - } else { - log.info("Detected concurrent task execution: {}, skip it", taskCopy); + } finally { + scheduling.setRunning(taskCopy, false); + scheduling.setFinished(task); } - } catch (Throwable throwable) { - log.error("Error running " + taskCopy, throwable); + } else { + log.info("Detected concurrent task execution: {}, skip it", taskCopy); } + } catch (Throwable throwable) { + log.error("Error running {}", taskCopy, throwable); } }); } @@ -148,10 +147,10 @@ public class RunnerBean implements Runner { } protected ScheduledExecution registerExecutionStart(ScheduledTask task, long now) { - if (!BooleanUtils.isTrue(task.getLogStart()) && !BooleanUtils.isTrue(task.getSingleton())) + if (!BooleanUtils.isTrue(task.getLogStart()) && !BooleanUtils.isTrue(task.getSingleton()) && task.getSchedulingType() != SchedulingType.FIXED_DELAY) return null; - log.trace(task + ": registering execution start"); + log.trace("{}: registering execution start", task); Transaction tx = persistence.createTransaction(); try { @@ -172,10 +171,11 @@ public class RunnerBean implements Runner { } protected void registerExecutionFinish(ScheduledTask task, ScheduledExecution execution, Object result) { - if ((!BooleanUtils.isTrue(task.getLogFinish()) && !BooleanUtils.isTrue(task.getSingleton())) || execution == null) + if ((!BooleanUtils.isTrue(task.getLogFinish()) && !BooleanUtils.isTrue(task.getSingleton()) && task.getSchedulingType() != SchedulingType.FIXED_DELAY) + || execution == null) return; - log.trace(task + ": registering execution finish"); + log.trace("{}: registering execution finish", task); Transaction tx = persistence.createTransaction(); try { EntityManager em = persistence.getEntityManager(); @@ -193,7 +193,7 @@ public class RunnerBean implements Runner { protected Object executeTask(ScheduledTask task) { switch (task.getDefinedBy()) { case BEAN: { - log.trace(task + ": invoking bean"); + log.trace("{}: invoking bean", task); Object bean = AppBeans.get(task.getBeanName()); try { List methodParams = task.getMethodParameters(); diff --git a/modules/core/src/com/haulmont/cuba/core/app/scheduling/Scheduling.java b/modules/core/src/com/haulmont/cuba/core/app/scheduling/Scheduling.java index 5bcebdf52f..800570602f 100644 --- a/modules/core/src/com/haulmont/cuba/core/app/scheduling/Scheduling.java +++ b/modules/core/src/com/haulmont/cuba/core/app/scheduling/Scheduling.java @@ -33,21 +33,23 @@ import com.haulmont.cuba.security.sys.UserSessionManager; import org.apache.commons.lang.BooleanUtils; import org.apache.commons.lang.ObjectUtils; import org.apache.commons.lang.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.perf4j.StopWatch; import org.perf4j.log4j.Log4JStopWatch; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.scheduling.support.CronSequenceGenerator; - import org.springframework.stereotype.Component; + import javax.inject.Inject; -import java.util.*; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; /** * Class that manages {@link ScheduledTask}s in distributed environment. - * */ @Component(SchedulingAPI.NAME) public class Scheduling implements SchedulingAPI { @@ -81,7 +83,9 @@ public class Scheduling implements SchedulingAPI { protected ConcurrentMap runningTasks = new ConcurrentHashMap<>(); - protected Map lastStartCache = new HashMap<>(); + protected Map lastStartCache = new ConcurrentHashMap<>(); + + protected Map lastFinishCache = new ConcurrentHashMap<>(); protected volatile long schedulingStartTime; @@ -136,6 +140,11 @@ public class Scheduling implements SchedulingAPI { } } + @Override + public void setFinished(ScheduledTask task) { + lastFinishCache.put(task, timeSource.currentTimeMillis()); + } + @Override public boolean isActive() { return configuration.getConfig(ServerConfig.class).getSchedulingActive(); @@ -168,7 +177,7 @@ public class Scheduling implements SchedulingAPI { protected void processTask(ScheduledTask task) { if (isRunning(task)) { - log.trace(task + " is running"); + log.trace("{} is running", task); return; } @@ -189,12 +198,18 @@ public class Scheduling implements SchedulingAPI { if (BooleanUtils.isTrue(task.getSingleton())) { if (task.getStartDate() != null || SchedulingType.CRON == task.getSchedulingType()) { - long currentStart = calculateCurrentStart(task, task.getLastStart(), now, period, frame); - - if (needToStartNow(now, frame, task.getLastStart(), currentStart)) { + long currentStart; + if (SchedulingType.FIXED_DELAY == task.getSchedulingType()) { + currentStart = calculateNextDelayDate(task, task.getLastStart(), coordinator.getLastFinished(task), now, frame, period); + } else if (SchedulingType.CRON == task.getSchedulingType()) { + currentStart = calculateNextCronDate(task, task.getLastStart(), now, frame); + } else { + currentStart = calculateNextPeriodDate(task, task.getLastStart(), now, frame, period); + } + if (needToStartInTimeFrame(now, frame, task.getLastStart(), currentStart)) { runSingletonTask(task, now, me); } else { - log.trace(task + "\n not in time frame to start"); + log.trace("{}\n not in time frame to start", task); } } else { Integer lastServerPriority = task.getLastStartServer() == null ? @@ -208,40 +223,56 @@ public class Scheduling implements SchedulingAPI { boolean giveChanceToPreviousHost = lastServerWasNotMe(task, me) && (lastServerPriority != null && serverPriority.compareTo(lastServerPriority) > 0); - if (log.isTraceEnabled()) - log.trace(task + "\n now=" + now + " lastStart=" + task.getLastStart() - + " lastServer=" + task.getLastStartServer() + " shouldSwitch=" + shouldSwitch - + " giveChanceToPreviousHost=" + giveChanceToPreviousHost); + log.trace("{}\n now={} lastStart={} lastServer={} shouldSwitch={} giveChanceToPreviousHost={}", + task, now, task.getLastStart(), task.getLastStartServer(), shouldSwitch, giveChanceToPreviousHost); - if (task.getLastStart() == 0 - || shouldSwitch - || (task.getLastStart() + (giveChanceToPreviousHost ? period + period / 2 : period) <= now)) { + if (task.getLastStart() == 0 || shouldSwitch) { runSingletonTask(task, now, me); } else { - log.trace(task + "\n time has not come and we shouldn't switch"); + long delay = giveChanceToPreviousHost ? period + period / 2 : period; + if (SchedulingType.FIXED_DELAY == task.getSchedulingType()) { + long lastFinish = coordinator.getLastFinished(task); + if ((task.getLastStart() < lastFinish || !lastFinishCache.containsKey(task)) && lastFinish + delay < now) { + runSingletonTask(task, now, me); + } else { + log.trace("{}\n time has not come and we shouldn't switch", task); + } + } else if (task.getLastStart() + delay <= now) { + runSingletonTask(task, now, me); + } else { + log.trace("{}\n time has not come and we shouldn't switch", task); + } } } } else { - Long lastStart = lastStartCache.get(task); - if (lastStart == null) { - lastStart = 0L; - } + Long lastStart = lastStartCache.getOrDefault(task, 0L); + Long lastFinish = lastFinishCache.getOrDefault(task, 0L); if (task.getStartDate() != null || SchedulingType.CRON == task.getSchedulingType()) { - long currentStart = calculateCurrentStart(task, lastStart, now, period, frame); - - if (needToStartNow(now, frame, lastStart, currentStart)) { + long currentStart; + if (SchedulingType.FIXED_DELAY == task.getSchedulingType()) { + currentStart = calculateNextDelayDate(task, lastStart, lastFinish, now, frame, period); + } else if (SchedulingType.CRON == task.getSchedulingType()) { + currentStart = calculateNextCronDate(task, lastStart, now, frame); + } else { + currentStart = calculateNextPeriodDate(task, lastStart, now, frame, period); + } + if (needToStartInTimeFrame(now, frame, lastStart, currentStart)) { runTask(task, now); } else { - log.trace(task + "\n not in time frame to start"); + log.trace("{}\n not in time frame to start", task); } } else { - if (log.isTraceEnabled()) - log.trace(task + "\n now=" + now + " lastStart= " + lastStart); - - if (now >= lastStart + period) { + log.trace("{}\n now={} lastStart={} lastFinish={}", task, now, lastStart, lastFinish); + if (SchedulingType.FIXED_DELAY == task.getSchedulingType()) { + if ((lastStart == 0 || lastStart < lastFinish) && now >= lastFinish + period) { + runTask(task, now); + } else { + log.trace("{}\n time has not come", task); + } + } else if (now >= lastStart + period) { runTask(task, now); } else { - log.trace(task + "\n time has not come"); + log.trace("{}\n time has not come", task); } } } @@ -250,42 +281,48 @@ public class Scheduling implements SchedulingAPI { } } - private boolean needToStartNow(long now, long frame, long lastStart, long currentStart) { + protected boolean needToStartInTimeFrame(long now, long frame, long lastStart, long currentStart) { return currentStart <= now && now < currentStart + frame && lastStart < currentStart; } - protected long calculateCurrentStart(ScheduledTask task, long lastStart, long now, long period, long frame) { - String cron = task.getCron(); - if (SchedulingType.CRON == task.getSchedulingType()) { - StopWatch sw = new Log4JStopWatch("Cron next date calculations"); - CronSequenceGenerator cronSequenceGenerator = new CronSequenceGenerator(cron, getCurrentTimeZone()); - //if last start = 0 (task never has run) or to far in the past, we use (NOW - FRAME) timestamp for pivot time - //this approach should work fine cause cron works with absolute time - long pivotPreviousTime = Math.max(lastStart, now - frame); + protected long calculateNextCronDate(ScheduledTask task, long date, long currentDate, long frame) { + StopWatch sw = new Log4JStopWatch("Cron next date calculations"); + CronSequenceGenerator cronSequenceGenerator = new CronSequenceGenerator(task.getCron(), getCurrentTimeZone()); + //if last start = 0 (task never has run) or to far in the past, we use (NOW - FRAME) timestamp for pivot time + //this approach should work fine cause cron works with absolute time + long pivotPreviousTime = Math.max(date, currentDate - frame); - Date currentStart = null; - Date nextDate = cronSequenceGenerator.next(new Date(pivotPreviousTime)); - while (nextDate.getTime() < now) {//if next date is in past try to find next date nearest to now - currentStart = nextDate; - nextDate = cronSequenceGenerator.next(nextDate); - } - - if (currentStart == null) { - currentStart = nextDate; - } - - log.trace(task + "\n now=" + now + " frame=" + frame - + " currentStart=" + currentStart + " lastStart=" + lastStart + " cron=" + cron); - sw.stop(); - return currentStart.getTime(); - } else { - long repetitions = (now - task.getStartDate().getTime()) / period; - long currentStart = task.getStartDate().getTime() + repetitions * period; - - log.trace(task + "\n now=" + now + " frame=" + frame + " repetitions=" + repetitions + - " currentStart=" + currentStart + " lastStart=" + lastStart); - return currentStart; + Date currentStart = null; + Date nextDate = cronSequenceGenerator.next(new Date(pivotPreviousTime)); + while (nextDate.getTime() < currentDate) {//if next date is in past try to find next date nearest to now + currentStart = nextDate; + nextDate = cronSequenceGenerator.next(nextDate); } + + if (currentStart == null) { + currentStart = nextDate; + } + log.trace("{}\n now={} frame={} currentStart={} lastStart={} cron={}", + task, currentDate, frame, currentStart, task.getCron()); + sw.stop(); + return currentStart.getTime(); + } + + protected long calculateNextPeriodDate(ScheduledTask task, long date, long currentDate, long frame, long period) { + long repetitions = (currentDate - task.getStartDate().getTime()) / period; + long currentStart = task.getStartDate().getTime() + repetitions * period; + log.trace("{}\n now={} frame={} repetitions={} currentStart={} lastStart={}", + task, currentDate, frame, repetitions, currentStart, date); + return currentStart; + } + + protected long calculateNextDelayDate(ScheduledTask task, long lastStart, long lastFinish, long currentDate, long frame, long period) { + long fromDate = lastFinish != 0 ? lastFinish : task.getStartDate().getTime(); + long repetitions = (currentDate - fromDate) / period; + long currentStart = fromDate + repetitions * period; + log.trace("{}\n now={} frame={} repetitions={} currentStart={} lastStart={} lastFinish={}", + task, currentDate, frame, repetitions, currentStart, lastStart, lastFinish); + return currentStart; } protected TimeZone getCurrentTimeZone() { diff --git a/modules/core/src/com/haulmont/cuba/core/app/scheduling/SchedulingAPI.java b/modules/core/src/com/haulmont/cuba/core/app/scheduling/SchedulingAPI.java index 6cccc60816..588c172600 100644 --- a/modules/core/src/com/haulmont/cuba/core/app/scheduling/SchedulingAPI.java +++ b/modules/core/src/com/haulmont/cuba/core/app/scheduling/SchedulingAPI.java @@ -55,7 +55,7 @@ public interface SchedulingAPI { void processScheduledTasks(boolean onlyIfActive); /** - * Mark the sheduled task as running/not running in the internal list. This method should not be used in the + * Mark the scheduled task as running/not running in the internal list. This method should not be used in the * application code. * @param task task instance * @param running true to mark as running, false to mark as not running @@ -63,6 +63,13 @@ public interface SchedulingAPI { */ boolean setRunning(ScheduledTask task, boolean running); + /** + * Mark the scheduled task as finished in the internal list. This method should not be used in the + * application code. + * @param task task instance + */ + void setFinished(ScheduledTask task); + /** * @return a list of active task instances in detached state */ diff --git a/modules/core/test/com/haulmont/cuba/core/app/scheduling/SchedulingTest.java b/modules/core/test/com/haulmont/cuba/core/app/scheduling/SchedulingTest.java index 66dda3b199..156e1efa37 100644 --- a/modules/core/test/com/haulmont/cuba/core/app/scheduling/SchedulingTest.java +++ b/modules/core/test/com/haulmont/cuba/core/app/scheduling/SchedulingTest.java @@ -60,42 +60,42 @@ public class SchedulingTest { scheduledTask.setCron("*/5 * * * * *"); //scheduler has failed couple of runs and now we should run it - long currentStart = scheduling.calculateCurrentStart(scheduledTask, date("2013-11-13 15:29:00").getTime(), date("2013-11-13 15:30:00").getTime(), 0, 10000l); + long currentStart = scheduling.calculateNextCronDate(scheduledTask, date("2013-11-13 15:29:00").getTime(), date("2013-11-13 15:30:00").getTime(), 10000l); assertEquals(date("2013-11-13 15:29:55"), new Date(currentStart)); //last run was year ago, so now-frame should be considered - currentStart = scheduling.calculateCurrentStart(scheduledTask, date("2012-11-13 15:29:00").getTime(), date("2013-11-13 15:30:00").getTime(), 0, 10000l); + currentStart = scheduling.calculateNextCronDate(scheduledTask, date("2012-11-13 15:29:00").getTime(), date("2013-11-13 15:30:00").getTime(), 10000l); assertEquals(date("2013-11-13 15:29:55"), new Date(currentStart)); //last run was very close to now, last start date should be considered - currentStart = scheduling.calculateCurrentStart(scheduledTask, date("2013-11-13 15:29:59").getTime(), date("2013-11-13 15:30:01").getTime(), 0, 10000l); + currentStart = scheduling.calculateNextCronDate(scheduledTask, date("2013-11-13 15:29:59").getTime(), date("2013-11-13 15:30:01").getTime(), 10000l); assertEquals(date("2013-11-13 15:30:00"), new Date(currentStart)); scheduledTask.setCron("0 0 0 * * FRI"); //task should run in next friday - currentStart = scheduling.calculateCurrentStart(scheduledTask, date("2013-11-08 01:01:01").getTime(), date("2013-11-13 15:30:00").getTime(), 0, 10000l); + currentStart = scheduling.calculateNextCronDate(scheduledTask, date("2013-11-08 01:01:01").getTime(), date("2013-11-13 15:30:00").getTime(), 10000l); assertEquals(date("2013-11-15 00:00:00"), new Date(currentStart)); - currentStart = scheduling.calculateCurrentStart(scheduledTask, date("2013-11-08 01:01:01").getTime(), date("2013-11-08 01:01:02").getTime(), 0, 600000l); + currentStart = scheduling.calculateNextCronDate(scheduledTask, date("2013-11-08 01:01:01").getTime(), date("2013-11-08 01:01:02").getTime(), 600000l); assertEquals(date("2013-11-15 00:00:00"), new Date(currentStart)); //task is late but matches frame - currentStart = scheduling.calculateCurrentStart(scheduledTask, date("2013-11-07 23:59:59").getTime(), date("2013-11-08 00:01:00").getTime(), 0, 600000l); + currentStart = scheduling.calculateNextCronDate(scheduledTask, date("2013-11-07 23:59:59").getTime(), date("2013-11-08 00:01:00").getTime(), 600000l); assertEquals(date("2013-11-8 00:00:00"), new Date(currentStart)); //task is late and does not match frame - currentStart = scheduling.calculateCurrentStart(scheduledTask, date("2013-11-07 23:59:59").getTime(), date("2013-11-08 00:11:00").getTime(), 0, 600000l); + currentStart = scheduling.calculateNextCronDate(scheduledTask, date("2013-11-07 23:59:59").getTime(), date("2013-11-08 00:11:00").getTime(), 600000l); assertEquals(date("2013-11-15 00:00:00"), new Date(currentStart)); scheduledTask.setCron("0 59 1 * * *"); //time shift forward - currentStart = scheduling.calculateCurrentStart(scheduledTask, date("2013-10-26 1:59:59").getTime(), date("2013-10-27 00:00:00").getTime(), 0, 600000l); + currentStart = scheduling.calculateNextCronDate(scheduledTask, date("2013-10-26 1:59:59").getTime(), date("2013-10-27 00:00:00").getTime(), 600000l); assertEquals(date("2013-10-27 01:59:00"), new Date(currentStart)); //time shift backward - currentStart = scheduling.calculateCurrentStart(scheduledTask, date("2013-03-30 1:59:00").getTime(), date("2013-03-31 00:00:00").getTime(), 0, 600000l); + currentStart = scheduling.calculateNextCronDate(scheduledTask, date("2013-03-30 1:59:00").getTime(), date("2013-03-31 00:00:00").getTime(), 600000l); assertEquals(date("2013-03-31 01:59:00"), new Date(currentStart)); } diff --git a/modules/global/src/com/haulmont/cuba/core/entity/SchedulingType.java b/modules/global/src/com/haulmont/cuba/core/entity/SchedulingType.java index 21e2d26e5a..33f16ed976 100644 --- a/modules/global/src/com/haulmont/cuba/core/entity/SchedulingType.java +++ b/modules/global/src/com/haulmont/cuba/core/entity/SchedulingType.java @@ -20,7 +20,8 @@ import com.haulmont.chile.core.datatypes.impl.EnumClass; public enum SchedulingType implements EnumClass { CRON("C"), - PERIOD("P"); + PERIOD("P"), + FIXED_DELAY("D"); private final String id; diff --git a/modules/global/src/com/haulmont/cuba/core/entity/messages.properties b/modules/global/src/com/haulmont/cuba/core/entity/messages.properties index f780aa7f25..fabcb40675 100644 --- a/modules/global/src/com/haulmont/cuba/core/entity/messages.properties +++ b/modules/global/src/com/haulmont/cuba/core/entity/messages.properties @@ -132,6 +132,7 @@ ScheduledTask.methodParamsXml = XML SchedulingType.CRON=Cron SchedulingType.PERIOD=Period +SchedulingType.FIXED_DELAY=Fixed Delay ScheduledExecution = Scheduled Execution ScheduledExecution.task = Task diff --git a/modules/global/src/com/haulmont/cuba/core/entity/messages_ru.properties b/modules/global/src/com/haulmont/cuba/core/entity/messages_ru.properties index cd1ae82301..024acfe2dd 100644 --- a/modules/global/src/com/haulmont/cuba/core/entity/messages_ru.properties +++ b/modules/global/src/com/haulmont/cuba/core/entity/messages_ru.properties @@ -135,6 +135,7 @@ ScheduledTask.methodParamsXml = XML SchedulingType.CRON=Cron SchedulingType.PERIOD=Period +SchedulingType.FIXED_DELAY=Fixed Delay ScheduledExecution = Выполнение задания ScheduledExecution.task = Task