-
Notifications
You must be signed in to change notification settings - Fork 682
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[#2060] drop exceptions thrown by @Every jobs so that they get rescheduled #1160
base: master
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
import java.util.concurrent.atomic.AtomicLong; | ||
|
||
import org.junit.Test; | ||
|
||
import play.jobs.Every; | ||
import play.jobs.Job; | ||
import play.test.UnitTest; | ||
|
||
/** | ||
* Unit tests for the {@link Job} class. | ||
*/ | ||
public class JobTest extends UnitTest { | ||
|
||
/** | ||
* A Job class that is annotated with {@link Every} to run every second. | ||
*/ | ||
public static class TestJob extends Job { | ||
private final AtomicLong totalRuns = new AtomicLong(0); | ||
private volatile boolean throwException = false; | ||
|
||
@Override | ||
public void doJob() throws Exception { | ||
totalRuns.getAndIncrement(); | ||
|
||
// To avoid logging an error every second, we only throw an | ||
// exception when the relevant test case is running. | ||
if (throwException) { | ||
throwException = false; | ||
throw new Exception("Throwing an exception"); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* A Job class that is annotated with {@link Every} to run every second. | ||
*/ | ||
@Every("1s") | ||
public static class RunEverySecond extends Job { | ||
private static final AtomicLong totalRuns = new AtomicLong(0); | ||
private static volatile boolean throwException = false; | ||
|
||
@Override | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. override is easy! why not just override There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand the recommendation. This unit test is designed to test what happens when someone doesn't override onException (the common case, and the case cited in [#2060]). If it overrides |
||
public void doJob() throws Exception { | ||
totalRuns.getAndIncrement(); | ||
|
||
// To avoid logging an error every second, we only throw an | ||
// exception when the relevant test case is running. | ||
if (throwException) { | ||
throwException = false; | ||
throw new Exception("Throwing an exception"); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* A Job class that is annotated with {@link Every} to never run. | ||
*/ | ||
@Every("never") | ||
public static class RunNever extends Job { | ||
private static final AtomicLong totalRuns = new AtomicLong(0); | ||
|
||
@Override | ||
public void doJob() throws Exception { | ||
totalRuns.getAndIncrement(); | ||
} | ||
} | ||
|
||
/** | ||
* A Job class that is annotated with {@link Every} to never run (using mixed case) | ||
*/ | ||
@Every("NeveR") | ||
public static class RunNeverMixedCase extends Job { | ||
private static final AtomicLong totalRuns = new AtomicLong(0); | ||
|
||
@Override | ||
public void doJob() throws Exception { | ||
totalRuns.getAndIncrement(); | ||
} | ||
} | ||
|
||
/** | ||
* Tests that a job which is annotated to run every second does, indeed, run every second. | ||
*/ | ||
@Test | ||
public void testEverySecond() { | ||
long beforeRuns = RunEverySecond.totalRuns.get(); | ||
pause(1500); // wait long enough for the job to have run at least once | ||
long afterRuns = RunEverySecond.totalRuns.get(); | ||
assertTrue("RunEverySecond job was never run", beforeRuns < afterRuns); | ||
assertTrue("RunEverySecond job was run too many times", afterRuns - beforeRuns <= 2); | ||
} | ||
|
||
/** | ||
* Tests that jobs which are annotated to never run have never been run. | ||
*/ | ||
@Test | ||
public void testEveryNever() { | ||
assertEquals(0, RunNever.totalRuns.get()); | ||
assertEquals(0, RunNeverMixedCase.totalRuns.get()); | ||
} | ||
|
||
/** | ||
* Tests that throwing an exception does not halt the periodic scheduling of an {@code @Every} annotation. | ||
* This is a regression test for Lighthouse [#2060] | ||
*/ | ||
@Test | ||
public void testExceptionDoesNotHaltReschedulingWithEveryAnnotation() { | ||
|
||
// Configure RunEverySecond to throw an exception. | ||
RunEverySecond.throwException = true; | ||
long beforeRuns = RunEverySecond.totalRuns.get(); | ||
|
||
pause(2500); // wait long enough for the job to have run at least twice | ||
|
||
// Make sure it threw an exception and ran multiple times. | ||
long afterRuns = RunEverySecond.totalRuns.get(); | ||
assertFalse("RunEverySecond job never ran", RunEverySecond.throwException); | ||
assertTrue("RunEverySecond job was not run after throwing an exception", 2 <= afterRuns - beforeRuns); | ||
} | ||
|
||
/** | ||
* Tests that throwing an exception does not halt the periodic scheduling of the {@link Job#every(int)} method. | ||
* This is a regression test for Lighthouse [#2060] | ||
*/ | ||
@Test | ||
public void testExceptionDoesNotHaltReschedulingEveryMethod() { | ||
|
||
// Schedule a job to run every second. | ||
TestJob job = new TestJob(); | ||
job.throwException = true; | ||
job.every("1s"); | ||
|
||
pause(2500); // wait long enough for the job to have run at least twice | ||
|
||
// Make sure it threw an exception and ran multiple times. | ||
assertFalse("TestJob job never ran", job.throwException); | ||
assertTrue("TestJob job was not run after throwing an exception", 2 <= job.totalRuns.get()); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
first try to avoid anonymous class wrapper creation if you can just use a flag: https://gist.github.com/flybyray/a7d61c66cd666c8aa391b3a0fb92425a#file-job-java-L168
https://gist.github.com/flybyray/a7d61c66cd666c8aa391b3a0fb92425a#file-job-java-L182
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also dislike anonymous class wrappers. The reason I didn't add a new flag to the Job class is that it's not a property of the job, but how the job was scheduled. I didn't want to introduce hard-to-understand failures when a Job is scheduled multiple times by the application. For example, if someone wanted to run a Job now and every 24 hours afterward, they could write something like:
If
Job.every()
sets a flag in the Job object, then puttingj.every()
before thej.now()
would interfere with the exception propagation, but the reverse wouldn't. To me, that seemed counter-intuitive. The current Job implementation uses the anonymous wrapper class created inJob.getJobCallingCallable()
to manage the exceptions for jobs scheduled byJob.now()
andJob.in()
without reading/writing state in the Job, so I followed suit.That said, the applications written by my organization never the above pattern, so we wouldn't be negatively impacted by keeping a "scheduled by every" flag within the Job object. So if that's the way you want me to go, that's the way I'll go. Please confirm.
I suppose you recommended making this behavior controllable by a new configuration option so that someone could disable it if they wanted an unhandled exception to halt the "every" execution. Are there additional documentation requirements for introducing a new configuration option, or would it be acceptable to keep it as a hidden option, available to anyone who reads the source code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
my suggestion is:
now
andin
too)onException
should be sufficientOr am I missing something? Overriding
onException
is the way to go if you want to ignore errors or special error handling.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the extra clarification. I'll rework my pull request to do this, following the pattern of your Gist, but applied to
now()
andin()
instead ofevery()
. It may take me a while to put the update together.