PL-6818 Support fixed-delay in Scheduled Tasks mechanism

This commit is contained in:
Andrey Subbotin 2016-06-24 13:57:36 +04:00
parent 5049caa263
commit b982798bfe
19 changed files with 173 additions and 104 deletions

View File

@ -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)^
------------------------------------------------------------------------------------------------------------

View File

@ -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)^

View File

@ -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)^
/**********************************************************************************************/

View File

@ -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,

View File

@ -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)^
------------------------------------------------------------------------------------------------------------

View File

@ -0,0 +1 @@
create index IDX_SYS_SCHEDULED_EXECUTION_TASK_FINISH_TIME on SYS_SCHEDULED_EXECUTION (TASK_ID, FINISH_TIME)^

View File

@ -0,0 +1 @@
create index IDX_SYS_SCHEDULED_EXECUTION_TASK_FINISH_TIME on SYS_SCHEDULED_EXECUTION (TASK_ID, FINISH_TIME)^

View File

@ -0,0 +1 @@
create index IDX_SYS_SCHEDULED_EXECUTION_TASK_FINISH_TIME on SYS_SCHEDULED_EXECUTION (TASK_ID, FINISH_TIME)^

View File

@ -0,0 +1 @@
create index IDX_SYS_SCH_EXE_TAS_FI_TIM on SYS_SCHEDULED_EXECUTION (TASK_ID, FINISH_TIME)

View File

@ -0,0 +1 @@
create index IDX_SYS_SCHEDULED_EXECUTION_TASK_FINISH_TIME on SYS_SCHEDULED_EXECUTION (TASK_ID, FINISH_TIME)^

View File

@ -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);
}

View File

@ -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.
* <p>This implementation should not be used if the database is overloaded.</p>
*
*/
@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<ScheduledTask> getTasks() {
log.trace("Read all active tasks from DB and lock them");
EntityManager em = persistence.getEntityManager();

View File

@ -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<MethodParameterInfo> methodParams = task.getMethodParameters();

View File

@ -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<ScheduledTask, Long> runningTasks = new ConcurrentHashMap<>();
protected Map<ScheduledTask, Long> lastStartCache = new HashMap<>();
protected Map<ScheduledTask, Long> lastStartCache = new ConcurrentHashMap<>();
protected Map<ScheduledTask, Long> 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() {

View File

@ -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
*/

View File

@ -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));
}

View File

@ -20,7 +20,8 @@ import com.haulmont.chile.core.datatypes.impl.EnumClass;
public enum SchedulingType implements EnumClass<String> {
CRON("C"),
PERIOD("P");
PERIOD("P"),
FIXED_DELAY("D");
private final String id;

View File

@ -132,6 +132,7 @@ ScheduledTask.methodParamsXml = XML
SchedulingType.CRON=Cron
SchedulingType.PERIOD=Period
SchedulingType.FIXED_DELAY=Fixed Delay
ScheduledExecution = Scheduled Execution
ScheduledExecution.task = Task

View File

@ -135,6 +135,7 @@ ScheduledTask.methodParamsXml = XML
SchedulingType.CRON=Cron
SchedulingType.PERIOD=Period
SchedulingType.FIXED_DELAY=Fixed Delay
ScheduledExecution = Выполнение задания
ScheduledExecution.task = Task