Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions src/main/java/org/knowm/sundial/InterruptingJob.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.knowm.sundial;

import org.quartz.exceptions.UnableToInterruptJobException;

/** Base class that interrupts blocking operations when the job is stopped.
*
* This class can be used as the base class for jobs that should interrupt
* blocking operations when the job is stopped, as opposed to completing them
* first.
*
* @see Thread#interrupt()
*/
public abstract class InterruptingJob extends Job {
private Thread executingThread;

public synchronized void setup() {
executingThread = Thread.currentThread();
}

public synchronized void cleanup() {
executingThread = null;
}

public void interrupt() throws UnableToInterruptJobException {
synchronized (this) {
if (executingThread != null) {
executingThread.interrupt();
}
}
super.interrupt();
}
}
10 changes: 9 additions & 1 deletion src/main/java/org/knowm/sundial/Job.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ public final void execute(JobExecutionContext jobExecutionContext) throws JobExe

initContextContainer(jobExecutionContext);

setup();
doRun();

} catch (RequiredParameterException e) {
Expand All @@ -73,7 +74,14 @@ public void interrupt() throws UnableToInterruptJobException {
logger.info("Interrupt called!");
}

/**
/**
* Override and place any code in here that should be called before doRun
*/
public void setup() {

}

/**
* Override and place any code in here that should be called no matter what after the Job runs or throws an exception.
*/
public void cleanup() {
Expand Down
13 changes: 4 additions & 9 deletions src/main/java/org/quartz/jobs/InterruptableJob.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,11 @@
* <code>execute(..)</code> signals that it has noticed the set flag.
* </p>
* <p>
* If the Job performs some form of blocking I/O or similar functions, you may want to consider having the <code>Job.execute(..)</code> method store a
* reference to the calling <code>Thread</code> as a member variable. Then the Implementation of this interfaces <code>interrupt()</code> method can
* call <code>interrupt()</code> on that Thread. Before attempting this, make sure that you fully understand what
* <code>java.lang.Thread.interrupt()</code> does and doesn't do. Also make sure that you clear the Job's member reference to the Thread when the
* execute(..) method exits (preferably in a <code>finally</code> block.
* If the Job performs some form of blocking I/O or similar functions, you may want to consider extending InterruptingJob. Its
* <code>interrupt()</code> method calls <code>interrupt()</code> on the thread that is executing the job. Before attempting this, make sure that you
* fully understand what <code>java.lang.Thread.interrupt()</code> does and doesn't do.
* </p>
* <p>
* See Example 7 (org.quartz.examples.example7.DumbInterruptableJob) for a simple implementation demonstration.
* </p>
*
*
* @see Job
* @see StatefulJob
* @see Scheduler#interrupt(JobKey)
Expand Down
74 changes: 74 additions & 0 deletions src/test/java/org/knowm/sundial/InterruptingJobTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package org.knowm.sundial;

import java.util.concurrent.Semaphore;

import org.junit.Test;
import org.quartz.core.JobExecutionContext;
import org.quartz.core.JobExecutionContextImpl;
import org.quartz.core.TriggerFiredBundle;
import org.quartz.jobs.JobDetail;
import org.quartz.jobs.JobDetailImpl;
import org.quartz.triggers.OperableTrigger;
import org.quartz.triggers.SimpleTriggerImpl;

public class InterruptingJobTest {
@Test
public void shouldInterruptThread() throws Exception {
final Semaphore sem = new Semaphore(0);
final WaitingJob job = new WaitingJob(sem);

final JobDetail detail = new JobDetailImpl();
final OperableTrigger trigger = new SimpleTriggerImpl();
final TriggerFiredBundle bundle = new TriggerFiredBundle(detail, trigger, null, true, null, null, null, null);
final JobExecutionContext context = new JobExecutionContextImpl(null, bundle, job);

final Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
job.execute(context);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
});
t.start();

synchronized (job) {
while (!job.aboutToSleep) {
job.wait();
}
}
while (!sem.hasQueuedThreads()) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}

job.interrupt();

t.join();
}

static class WaitingJob extends InterruptingJob {
public boolean aboutToSleep = false;
final Semaphore sem;
public WaitingJob(Semaphore sem) {
this.sem = sem;
}
@Override
public void doRun() {
synchronized (this) {
aboutToSleep = true;
notify();
}
try {
sem.acquire();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}