History | Log In     View a printable version of the current page.  
Issue Details (XML | Word | Printable)

Key: QUARTZ-601
Type: Bug Bug
Status: Closed Closed
Resolution: Fixed
Priority: Major Major
Assignee: Henri Yandell
Reporter: Kay Huber
Votes: 0
Watchers: 1
Operations

If you were logged in you would be able to see more operations.
Quartz Scheduler

CronExpression over Midnight causes exception

Created: 17/Jul/07 01:38 AM   Updated: 02/Feb/08 10:58 PM
Component/s: Core
Affects Version/s: 1.6
Fix Version/s: 1.6.1

File Attachments: 1. Text File QUARTZ-601-fix-and-test.patch (7 kb)
2. Text File QUARTZ-601-fix-and-test.patch (6 kb)
3. Text File QUARTZ-601.patch (2 kb)
4. Java Source File Quartz601.java (2 kb)
5. Java Source File Quartz601Test.java (2 kb)

Environment:
java version "1.5.0_08"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_08-b03)
Java HotSpot(TM) 64-Bit Server VM (build 1.5.0_08-b03, mixed mode)

Spring Framework 2.0.5


 Description  « Hide
I have the following Cron Expression in use:

cronExpression=0 0/5 21-3 ? * MON-FRI

I started the scheduler at 00:14 am

java.util.NoSuchElementException
        at java.util.TreeMap.key(TreeMap.java:433)
        at java.util.TreeMap.firstKey(TreeMap.java:287)
        at java.util.TreeSet.first(TreeSet.java:407)
        at org.quartz.CronExpression.getTimeAfter(CronExpression.java:1099)
        at org.quartz.CronTrigger.getTimeAfter(CronTrigger.java:903)
        at org.quartz.CronTrigger.getFireTimeAfter(CronTrigger.java:612)
        at org.quartz.CronTrigger.computeFirstFireTime(CronTrigger.java:882)
        at org.quartz.core.QuartzScheduler.scheduleJob(QuartzScheduler.java:742)
        at org.quartz.impl.StdScheduler.scheduleJob(StdScheduler.java:266)
        at ch.local.common.util.GenericMain.start(GenericMain.java:160)
        at ch.local.common.util.GenericMain.startupApplication(GenericMain.java:106)
        at ch.local.dataimport.ImporterMain.main(ImporterMain.java:25)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:585)
        at com.simontuffs.onejar.Boot.run(Boot.java:287)
        at com.simontuffs.onejar.Boot.main(Boot.java:137)

I corrected my cronExpression to

cronExpression=0 0/5 21-23 ? * MON-FRI

and afterwards the tool started without complaining.
Some ideas:
- It started, because the corrected cronExpression did not fire the trigger immediately again (as I stared outside of the scope of the cronexpression)
- It started, because the cronExpression was no longer over Midnight (-> from 21 pm to 3 am - could be a problem couldn't it?)

Any idea anyone?

 All   Comments   Change History      Sort Order:
Henri Yandell - [17/Jul/07 04:16 PM ]
The following passes:

    public void testQuartz601() throws ParseException {
        CronExpression cronExpression = new CronExpression("0 0/5 21-3 ? * MON-FRI");
    }

So presumably it's not your second idea.

Kay Huber - [19/Jul/07 11:50 AM ]
Use the following code to reproduce the problem.
It's not the parsing of the CronExpression that fails, it's the "getTimeAfter" - which is called by QuartzTrigger - as you can see in my original stacktrace.

I didn't dig into the whole Quartz internals yet, but I'm still somewhat convinced that is has problems with cron expressions that go over midnight and should fire then.

import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;

import org.quartz.CronExpression;

public class QuartzCronExpressionProblem {

private class CronExpressionTest extends CronExpression {
public CronExpressionTest(String cronExpression) throws ParseException {
super(cronExpression);
}

public Date getTimeAfterPublic(Date afterTime) {
return getTimeAfter(afterTime);
}
}

public void runTest() {
CronExpressionTest cronExpression = null;
try {
cronExpression = new CronExpressionTest("0 0/5 21-3 ? * MON-FRI");
} catch (ParseException e) {
return;
}

Calendar closeToMidnight = Calendar.getInstance();
closeToMidnight.set(Calendar.HOUR, 0);
closeToMidnight.set(Calendar.MINUTE, 30);
closeToMidnight.set(Calendar.SECOND, 0);
closeToMidnight.clear(Calendar.MILLISECOND);

Date timeAfterPublic = cronExpression.getTimeAfterPublic(closeToMidnight.getTime());
}

public static void main(String[] args) {
QuartzCronExpressionProblem obj = new QuartzCronExpressionProblem();
obj.runTest();
}

}

Kay Huber - [19/Jul/07 12:19 PM ]
After tracing some more, the problem lies in the parsing after all. Having the following stack:

Thread [main] (Suspended)
QuartzCronExpressionProblem$CronExpressionTest(org.quartz.CronExpression).addToSet(int, int, int, int) line: 959
QuartzCronExpressionProblem$CronExpressionTest(org.quartz.CronExpression).checkNext(int, java.lang.String, int, int) line: 685
QuartzCronExpressionProblem$CronExpressionTest(org.quartz.CronExpression).storeExpressionVals(int, java.lang.String, int) line: 609
QuartzCronExpressionProblem$CronExpressionTest(org.quartz.CronExpression).buildExpression(java.lang.String) line: 411
QuartzCronExpressionProblem$CronExpressionTest(org.quartz.CronExpression).<init>(java.lang.String) line: 246
QuartzCronExpressionProblem$CronExpressionTest.<init>(QuartzCronExpressionProblem, java.lang.String) line: 12
QuartzCronExpressionProblem.runTest() line: 39
QuartzCronExpressionProblem.main(java.lang.String[]) line: 56

In addToSet, startAt is 21, stopAt is 3 and the for loop at the very end of addToSet will never add anything to the hours treeset, as there are no hours bigger than 21 but smaller than 3.

So I'd say, whenever you have a range where the end is "before" the start, Quartz' cron scheduler will fail.

Henri Yandell - [20/Jul/07 12:34 PM ]
Thanks for the further investigation.

I'll dig into it - might be that it's already fixed on trunk but was a bug with 1.6; or might be that my test was oversimple for some reason.

Henri Yandell - [20/Jul/07 12:56 PM ]
Definitely still a bug on trunk - your test case works very nicely.

Henri Yandell - [20/Jul/07 03:32 PM ]
Having a slow day... so the bug is, as you said, in the for loop at the end of addToSet.

A work of: "0 0/5 0-3,21-23 ? * MON-FRI" seems to be fine.

Henri Yandell - [20/Jul/07 04:38 PM ]
Experimental test file based on Kay's original example.

Henri Yandell - [20/Jul/07 04:49 PM ]
Attaching my current state of code in attempting to fix the problem.

1/
 There are some caveats. I presume that having 29,30,31 in the days of month set won't hurt the system when it's in February etc. I think that's a safe assumption, but I need to test.

2/
 The isSatisfiedBy unit test fails with this change. Need to understand why.

3/
 The following cron expression fails: "58 5 21 ? * FRI-TUE"
  with:

java.text.ParseException: Invalid Day-of-Week sequence: 6 > 3
        at org.quartz.CronExpression.storeExpressionVals(CronExpression.java:483)
        at org.quartz.CronExpression.buildExpression(CronExpression.java:411)
        at org.quartz.CronExpression.<init>(CronExpression.java:246)
        at TestCronExpression.<init>(Quartz601.java:39)
        at Quartz601.runTest(Quartz601.java:23)
        at Quartz601.main(Quartz601.java:15)

Unix cron (or rather crontab on my OS X laptop) appears quite happy with an overflow in the named day of week, so that exception should probably be removed.

Henri Yandell - [20/Jul/07 04:56 PM ]
Commenting out the ParseException seems to do the right thing; though also would need some more testing.

Henri Yandell - [22/Aug/07 11:06 AM ]
Adding a unit test to show these bugs.

Most of them cause errors, one causes an exception.

Henri Yandell - [22/Aug/07 12:08 PM ]
Final fix/test patch for this issue. The isSatisifedBy failure was because I had broken the default case. I've adjusted it to check if the max variable has been changed from the initial -1 to decide if it should do the default case or the overlap case.

Ready for review.

Kay Huber - [20/Sep/07 05:20 AM ]
Tried the patch and works fine for me now - thanks a lot.

Henri Yandell - [29/Nov/07 11:52 AM ]
I need to javadoc this strongly as per the dev@ list discussion - if you use too many ranges in crons then it all gets very confusing and we've no idea what will happen there.

Then it'll get committed.

Henri Yandell - [03/Jan/08 01:11 AM ]
New version of the patch with added javadoc note:

 * <li>Overflowing ranges is supported - that is, having a larger number on
 * the left hand side than the right. You might do 22-2 to catch 10 o'clock
 * at night until 2 o'clock in the morning, or you might have NOV-FEB. It is
 * very important to note that overuse of overflowing ranges creates ranges
 * that don't make sense and no effort has been made to determine which
 * interpretation CronExpression chooses. An example would be
 * "0 0 14-6 ? * FRI-MON". </li>

James House - [02/Feb/08 12:34 PM ]

Looks good Henri.

Henri Yandell - [02/Feb/08 10:58 PM ]
svn ci -m "Applying the last patch from Kay Huber's QUARTZ-601 - enhancing CronExpression so it can handle ranges over Midnight. It appears to work nicely for simple cases, but more complicated cases start to become semantically ambigious - so javadoc to that effect has been added. " src

Sending src/java/org/quartz/CronExpression.java
Adding src/test/java/org/quartz/Quartz601Test.java
Transmitting file data ..
Committed revision 765.