mirror of https://github.com/apache/activemq.git
git-svn-id: https://svn.apache.org/repos/asf/activemq/trunk@1516561 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
92860ae622
commit
85bb229aa7
|
@ -32,17 +32,17 @@ import org.apache.activemq.broker.scheduler.CronParser;
|
||||||
import org.apache.activemq.broker.scheduler.Job;
|
import org.apache.activemq.broker.scheduler.Job;
|
||||||
import org.apache.activemq.broker.scheduler.JobListener;
|
import org.apache.activemq.broker.scheduler.JobListener;
|
||||||
import org.apache.activemq.broker.scheduler.JobScheduler;
|
import org.apache.activemq.broker.scheduler.JobScheduler;
|
||||||
|
import org.apache.activemq.store.kahadb.disk.index.BTreeIndex;
|
||||||
|
import org.apache.activemq.store.kahadb.disk.journal.Location;
|
||||||
|
import org.apache.activemq.store.kahadb.disk.page.Transaction;
|
||||||
|
import org.apache.activemq.store.kahadb.disk.util.LongMarshaller;
|
||||||
|
import org.apache.activemq.store.kahadb.disk.util.VariableMarshaller;
|
||||||
|
import org.apache.activemq.util.ByteSequence;
|
||||||
import org.apache.activemq.util.IdGenerator;
|
import org.apache.activemq.util.IdGenerator;
|
||||||
import org.apache.activemq.util.ServiceStopper;
|
import org.apache.activemq.util.ServiceStopper;
|
||||||
import org.apache.activemq.util.ServiceSupport;
|
import org.apache.activemq.util.ServiceSupport;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.apache.activemq.store.kahadb.disk.index.BTreeIndex;
|
|
||||||
import org.apache.activemq.store.kahadb.disk.journal.Location;
|
|
||||||
import org.apache.activemq.store.kahadb.disk.page.Transaction;
|
|
||||||
import org.apache.activemq.util.ByteSequence;
|
|
||||||
import org.apache.activemq.store.kahadb.disk.util.LongMarshaller;
|
|
||||||
import org.apache.activemq.store.kahadb.disk.util.VariableMarshaller;
|
|
||||||
|
|
||||||
class JobSchedulerImpl extends ServiceSupport implements Runnable, JobScheduler {
|
class JobSchedulerImpl extends ServiceSupport implements Runnable, JobScheduler {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(JobSchedulerImpl.class);
|
private static final Logger LOG = LoggerFactory.getLogger(JobSchedulerImpl.class);
|
||||||
|
@ -51,12 +51,12 @@ class JobSchedulerImpl extends ServiceSupport implements Runnable, JobScheduler
|
||||||
private String name;
|
private String name;
|
||||||
BTreeIndex<Long, List<JobLocation>> index;
|
BTreeIndex<Long, List<JobLocation>> index;
|
||||||
private Thread thread;
|
private Thread thread;
|
||||||
|
private final Object listenerLock = new Object();
|
||||||
private final List<JobListener> jobListeners = new CopyOnWriteArrayList<JobListener>();
|
private final List<JobListener> jobListeners = new CopyOnWriteArrayList<JobListener>();
|
||||||
private static final IdGenerator ID_GENERATOR = new IdGenerator();
|
private static final IdGenerator ID_GENERATOR = new IdGenerator();
|
||||||
private final ScheduleTime scheduleTime = new ScheduleTime();
|
private final ScheduleTime scheduleTime = new ScheduleTime();
|
||||||
|
|
||||||
JobSchedulerImpl(JobSchedulerStoreImpl store) {
|
JobSchedulerImpl(JobSchedulerStoreImpl store) {
|
||||||
|
|
||||||
this.store = store;
|
this.store = store;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,65 +66,77 @@ class JobSchedulerImpl extends ServiceSupport implements Runnable, JobScheduler
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
|
*
|
||||||
* @see org.apache.activemq.beanstalk.JobScheduler#getName()
|
* @see org.apache.activemq.beanstalk.JobScheduler#getName()
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return this.name;
|
return this.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
* @see
|
*
|
||||||
* org.apache.activemq.beanstalk.JobScheduler#addListener(org.apache.activemq
|
* @see org.apache.activemq.beanstalk.JobScheduler#addListener(org.apache.activemq .beanstalk.JobListener)
|
||||||
* .beanstalk.JobListener)
|
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public void addListener(JobListener l) {
|
public void addListener(JobListener l) {
|
||||||
this.jobListeners.add(l);
|
this.jobListeners.add(l);
|
||||||
|
synchronized (this.listenerLock) {
|
||||||
|
this.listenerLock.notify();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
* @see
|
*
|
||||||
* org.apache.activemq.beanstalk.JobScheduler#removeListener(org.apache.
|
* @see org.apache.activemq.beanstalk.JobScheduler#removeListener(org.apache. activemq.beanstalk.JobListener)
|
||||||
* activemq.beanstalk.JobListener)
|
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public void removeListener(JobListener l) {
|
public void removeListener(JobListener l) {
|
||||||
this.jobListeners.remove(l);
|
this.jobListeners.remove(l);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public synchronized void schedule(final String jobId, final ByteSequence payload, final long delay) throws IOException {
|
public synchronized void schedule(final String jobId, final ByteSequence payload, final long delay) throws IOException {
|
||||||
this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
|
this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
|
||||||
|
@Override
|
||||||
public void execute(Transaction tx) throws IOException {
|
public void execute(Transaction tx) throws IOException {
|
||||||
schedule(tx, jobId, payload, "", 0, delay, 0);
|
schedule(tx, jobId, payload, "", 0, delay, 0);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public synchronized void schedule(final String jobId, final ByteSequence payload, final String cronEntry) throws Exception {
|
public synchronized void schedule(final String jobId, final ByteSequence payload, final String cronEntry) throws Exception {
|
||||||
this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
|
this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
|
||||||
|
@Override
|
||||||
public void execute(Transaction tx) throws IOException {
|
public void execute(Transaction tx) throws IOException {
|
||||||
schedule(tx, jobId, payload, cronEntry, 0, 0, 0);
|
schedule(tx, jobId, payload, cronEntry, 0, 0, 0);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void schedule(final String jobId, final ByteSequence payload, final String cronEntry, final long delay,
|
@Override
|
||||||
final long period, final int repeat) throws IOException {
|
public synchronized void schedule(final String jobId, final ByteSequence payload, final String cronEntry, final long delay, final long period,
|
||||||
|
final int repeat) throws IOException {
|
||||||
this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
|
this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
|
||||||
|
@Override
|
||||||
public void execute(Transaction tx) throws IOException {
|
public void execute(Transaction tx) throws IOException {
|
||||||
schedule(tx, jobId, payload, cronEntry, delay, period, repeat);
|
schedule(tx, jobId, payload, cronEntry, delay, period, repeat);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
|
*
|
||||||
* @see org.apache.activemq.beanstalk.JobScheduler#remove(long)
|
* @see org.apache.activemq.beanstalk.JobScheduler#remove(long)
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public synchronized void remove(final long time) throws IOException {
|
public synchronized void remove(final long time) throws IOException {
|
||||||
this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
|
this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
|
||||||
|
@Override
|
||||||
public void execute(Transaction tx) throws IOException {
|
public void execute(Transaction tx) throws IOException {
|
||||||
remove(tx, time);
|
remove(tx, time);
|
||||||
}
|
}
|
||||||
|
@ -133,6 +145,7 @@ class JobSchedulerImpl extends ServiceSupport implements Runnable, JobScheduler
|
||||||
|
|
||||||
synchronized void removeFromIndex(final long time, final String jobId) throws IOException {
|
synchronized void removeFromIndex(final long time, final String jobId) throws IOException {
|
||||||
this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
|
this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
|
||||||
|
@Override
|
||||||
public void execute(Transaction tx) throws IOException {
|
public void execute(Transaction tx) throws IOException {
|
||||||
removeFromIndex(tx, time, jobId);
|
removeFromIndex(tx, time, jobId);
|
||||||
}
|
}
|
||||||
|
@ -141,11 +154,12 @@ class JobSchedulerImpl extends ServiceSupport implements Runnable, JobScheduler
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
* @see org.apache.activemq.beanstalk.JobScheduler#remove(long,
|
*
|
||||||
* java.lang.String)
|
* @see org.apache.activemq.beanstalk.JobScheduler#remove(long, java.lang.String)
|
||||||
*/
|
*/
|
||||||
public synchronized void remove(final long time, final String jobId) throws IOException {
|
public synchronized void remove(final long time, final String jobId) throws IOException {
|
||||||
this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
|
this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
|
||||||
|
@Override
|
||||||
public void execute(Transaction tx) throws IOException {
|
public void execute(Transaction tx) throws IOException {
|
||||||
remove(tx, time, jobId);
|
remove(tx, time, jobId);
|
||||||
}
|
}
|
||||||
|
@ -154,16 +168,20 @@ class JobSchedulerImpl extends ServiceSupport implements Runnable, JobScheduler
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
|
*
|
||||||
* @see org.apache.activemq.beanstalk.JobScheduler#remove(java.lang.String)
|
* @see org.apache.activemq.beanstalk.JobScheduler#remove(java.lang.String)
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public synchronized void remove(final String jobId) throws IOException {
|
public synchronized void remove(final String jobId) throws IOException {
|
||||||
this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
|
this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
|
||||||
|
@Override
|
||||||
public void execute(Transaction tx) throws IOException {
|
public void execute(Transaction tx) throws IOException {
|
||||||
remove(tx, jobId);
|
remove(tx, jobId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public synchronized long getNextScheduleTime() throws IOException {
|
public synchronized long getNextScheduleTime() throws IOException {
|
||||||
Map.Entry<Long, List<JobLocation>> first = this.index.getFirst(this.store.getPageFile().tx());
|
Map.Entry<Long, List<JobLocation>> first = this.index.getFirst(this.store.getPageFile().tx());
|
||||||
return first != null ? first.getKey() : -1l;
|
return first != null ? first.getKey() : -1l;
|
||||||
|
@ -171,12 +189,15 @@ class JobSchedulerImpl extends ServiceSupport implements Runnable, JobScheduler
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
|
*
|
||||||
* @see org.apache.activemq.beanstalk.JobScheduler#getNextScheduleJobs()
|
* @see org.apache.activemq.beanstalk.JobScheduler#getNextScheduleJobs()
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public synchronized List<Job> getNextScheduleJobs() throws IOException {
|
public synchronized List<Job> getNextScheduleJobs() throws IOException {
|
||||||
final List<Job> result = new ArrayList<Job>();
|
final List<Job> result = new ArrayList<Job>();
|
||||||
|
|
||||||
this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
|
this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
|
||||||
|
@Override
|
||||||
public void execute(Transaction tx) throws IOException {
|
public void execute(Transaction tx) throws IOException {
|
||||||
Map.Entry<Long, List<JobLocation>> first = index.getFirst(store.getPageFile().tx());
|
Map.Entry<Long, List<JobLocation>> first = index.getFirst(store.getPageFile().tx());
|
||||||
if (first != null) {
|
if (first != null) {
|
||||||
|
@ -191,9 +212,11 @@ class JobSchedulerImpl extends ServiceSupport implements Runnable, JobScheduler
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public synchronized List<Job> getAllJobs() throws IOException {
|
public synchronized List<Job> getAllJobs() throws IOException {
|
||||||
final List<Job> result = new ArrayList<Job>();
|
final List<Job> result = new ArrayList<Job>();
|
||||||
this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
|
this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
|
||||||
|
@Override
|
||||||
public void execute(Transaction tx) throws IOException {
|
public void execute(Transaction tx) throws IOException {
|
||||||
Iterator<Map.Entry<Long, List<JobLocation>>> iter = index.iterator(store.getPageFile().tx());
|
Iterator<Map.Entry<Long, List<JobLocation>>> iter = index.iterator(store.getPageFile().tx());
|
||||||
while (iter.hasNext()) {
|
while (iter.hasNext()) {
|
||||||
|
@ -208,15 +231,16 @@ class JobSchedulerImpl extends ServiceSupport implements Runnable, JobScheduler
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public synchronized List<Job> getAllJobs(final long start, final long finish) throws IOException {
|
public synchronized List<Job> getAllJobs(final long start, final long finish) throws IOException {
|
||||||
final List<Job> result = new ArrayList<Job>();
|
final List<Job> result = new ArrayList<Job>();
|
||||||
this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
|
this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
|
||||||
|
@Override
|
||||||
public void execute(Transaction tx) throws IOException {
|
public void execute(Transaction tx) throws IOException {
|
||||||
Iterator<Map.Entry<Long, List<JobLocation>>> iter = index.iterator(store.getPageFile().tx(), start);
|
Iterator<Map.Entry<Long, List<JobLocation>>> iter = index.iterator(store.getPageFile().tx(), start);
|
||||||
while (iter.hasNext()) {
|
while (iter.hasNext()) {
|
||||||
|
@ -231,35 +255,36 @@ class JobSchedulerImpl extends ServiceSupport implements Runnable, JobScheduler
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public synchronized void removeAllJobs() throws IOException {
|
public synchronized void removeAllJobs() throws IOException {
|
||||||
this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
|
this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
|
||||||
|
@Override
|
||||||
public void execute(Transaction tx) throws IOException {
|
public void execute(Transaction tx) throws IOException {
|
||||||
destroy(tx);
|
destroy(tx);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public synchronized void removeAllJobs(final long start, final long finish) throws IOException {
|
public synchronized void removeAllJobs(final long start, final long finish) throws IOException {
|
||||||
this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
|
this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
|
||||||
|
@Override
|
||||||
public void execute(Transaction tx) throws IOException {
|
public void execute(Transaction tx) throws IOException {
|
||||||
destroy(tx, start, finish);
|
destroy(tx, start, finish);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ByteSequence getPayload(Location location) throws IllegalStateException, IOException {
|
ByteSequence getPayload(Location location) throws IllegalStateException, IOException {
|
||||||
return this.store.getPayload(location);
|
return this.store.getPayload(location);
|
||||||
}
|
}
|
||||||
|
|
||||||
void schedule(Transaction tx, String jobId, ByteSequence payload, String cronEntry, long delay, long period,
|
void schedule(Transaction tx, String jobId, ByteSequence payload, String cronEntry, long delay, long period, int repeat) throws IOException {
|
||||||
int repeat) throws IOException {
|
|
||||||
long startTime = System.currentTimeMillis();
|
long startTime = System.currentTimeMillis();
|
||||||
// round startTime - so we can schedule more jobs
|
// round startTime - so we can schedule more jobs
|
||||||
// at the same time
|
// at the same time
|
||||||
|
@ -301,6 +326,7 @@ class JobSchedulerImpl extends ServiceSupport implements Runnable, JobScheduler
|
||||||
|
|
||||||
synchronized void storeJob(final JobLocation jobLocation, final long nextExecutionTime) throws IOException {
|
synchronized void storeJob(final JobLocation jobLocation, final long nextExecutionTime) throws IOException {
|
||||||
this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
|
this.store.getPageFile().tx().execute(new Transaction.Closure<IOException>() {
|
||||||
|
@Override
|
||||||
public void execute(Transaction tx) throws IOException {
|
public void execute(Transaction tx) throws IOException {
|
||||||
storeJob(tx, jobLocation, nextExecutionTime);
|
storeJob(tx, jobLocation, nextExecutionTime);
|
||||||
}
|
}
|
||||||
|
@ -318,7 +344,6 @@ class JobSchedulerImpl extends ServiceSupport implements Runnable, JobScheduler
|
||||||
}
|
}
|
||||||
values.add(jobLocation);
|
values.add(jobLocation);
|
||||||
this.index.put(tx, nextExecutionTime, values);
|
this.index.put(tx, nextExecutionTime, values);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void remove(Transaction tx, long time, String jobId) throws IOException {
|
void remove(Transaction tx, long time, String jobId) throws IOException {
|
||||||
|
@ -383,6 +408,7 @@ class JobSchedulerImpl extends ServiceSupport implements Runnable, JobScheduler
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Long l : keys) {
|
for (Long l : keys) {
|
||||||
this.index.remove(tx, l);
|
this.index.remove(tx, l);
|
||||||
}
|
}
|
||||||
|
@ -404,6 +430,7 @@ class JobSchedulerImpl extends ServiceSupport implements Runnable, JobScheduler
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Long l : keys) {
|
for (Long l : keys) {
|
||||||
this.index.remove(tx, l);
|
this.index.remove(tx, l);
|
||||||
}
|
}
|
||||||
|
@ -415,7 +442,6 @@ class JobSchedulerImpl extends ServiceSupport implements Runnable, JobScheduler
|
||||||
return first;
|
return first;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void fireJob(JobLocation job) throws IllegalStateException, IOException {
|
void fireJob(JobLocation job) throws IllegalStateException, IOException {
|
||||||
|
@ -428,6 +454,7 @@ class JobSchedulerImpl extends ServiceSupport implements Runnable, JobScheduler
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
mainLoop();
|
mainLoop();
|
||||||
|
@ -453,6 +480,19 @@ class JobSchedulerImpl extends ServiceSupport implements Runnable, JobScheduler
|
||||||
|
|
||||||
protected void mainLoop() {
|
protected void mainLoop() {
|
||||||
while (this.running.get()) {
|
while (this.running.get()) {
|
||||||
|
|
||||||
|
// Can't start pumping messages until a listener is added otherwise we'd discard messages
|
||||||
|
// without any warning.
|
||||||
|
synchronized (listenerLock) {
|
||||||
|
while (this.running.get() && this.jobListeners.isEmpty()) {
|
||||||
|
try {
|
||||||
|
LOG.debug("Scheduled Message dispatch paused while awaiting a Job Listener");
|
||||||
|
this.listenerLock.wait();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.scheduleTime.clearNewJob();
|
this.scheduleTime.clearNewJob();
|
||||||
try {
|
try {
|
||||||
// peek the next job
|
// peek the next job
|
||||||
|
@ -478,8 +518,7 @@ class JobSchedulerImpl extends ServiceSupport implements Runnable, JobScheduler
|
||||||
if (repeat != 0) {
|
if (repeat != 0) {
|
||||||
repeat--;
|
repeat--;
|
||||||
job.setRepeat(repeat);
|
job.setRepeat(repeat);
|
||||||
// remove this job from the index - so it
|
// remove this job from the index so it doesn't get destroyed
|
||||||
// doesn't get destroyed
|
|
||||||
removeFromIndex(executionTime, job.getJobId());
|
removeFromIndex(executionTime, job.getJobId());
|
||||||
// and re-store it
|
// and re-store it
|
||||||
storeJob(job, nextExecutionTime);
|
storeJob(job, nextExecutionTime);
|
||||||
|
@ -487,28 +526,21 @@ class JobSchedulerImpl extends ServiceSupport implements Runnable, JobScheduler
|
||||||
} else {
|
} else {
|
||||||
// cron job
|
// cron job
|
||||||
if (repeat == 0) {
|
if (repeat == 0) {
|
||||||
// we haven't got a separate scheduler to
|
// we haven't got a separate scheduler to execute at
|
||||||
// execute at
|
|
||||||
// this time - just a cron job - so fire it
|
// this time - just a cron job - so fire it
|
||||||
fireJob(job);
|
fireJob(job);
|
||||||
//this.scheduleTime.setWaitTime(this.scheduleTime.DEFAULT_WAIT);
|
|
||||||
}
|
}
|
||||||
if (nextExecutionTime > currentTime) {
|
if (nextExecutionTime > currentTime) {
|
||||||
// we will run again ...
|
// we will run again ...
|
||||||
// remove this job from the index - so it
|
// remove this job from the index - so it doesn't get destroyed
|
||||||
// doesn't get destroyed
|
|
||||||
removeFromIndex(executionTime, job.getJobId());
|
removeFromIndex(executionTime, job.getJobId());
|
||||||
// and re-store it
|
// and re-store it
|
||||||
storeJob(job, nextExecutionTime);
|
storeJob(job, nextExecutionTime);
|
||||||
if (repeat != 0) {
|
if (repeat != 0) {
|
||||||
// we have a separate schedule to run at
|
// we have a separate schedule to run at this time
|
||||||
// this time
|
// so the cron job is used to set of a separate schedule
|
||||||
// so the cron job is used to set of a
|
// hence we won't fire the original cron job to the
|
||||||
// seperate scheule
|
// listeners but we do need to start a separate schedule
|
||||||
// hence we won't fire the original cron
|
|
||||||
// job to the listeners
|
|
||||||
// but we do need to start a separate
|
|
||||||
// schedule
|
|
||||||
String jobId = ID_GENERATOR.generateId();
|
String jobId = ID_GENERATOR.generateId();
|
||||||
ByteSequence payload = getPayload(job.getLocation());
|
ByteSequence payload = getPayload(job.getLocation());
|
||||||
schedule(jobId, payload, "", job.getDelay(), job.getPeriod(), job.getRepeat());
|
schedule(jobId, payload, "", job.getDelay(), job.getPeriod(), job.getRepeat());
|
||||||
|
@ -526,12 +558,11 @@ class JobSchedulerImpl extends ServiceSupport implements Runnable, JobScheduler
|
||||||
// we need to reset wait time otherwise we'll miss it.
|
// we need to reset wait time otherwise we'll miss it.
|
||||||
Map.Entry<Long, List<JobLocation>> nextUp = getNextToSchedule();
|
Map.Entry<Long, List<JobLocation>> nextUp = getNextToSchedule();
|
||||||
if (nextUp != null) {
|
if (nextUp != null) {
|
||||||
final long timeUntilNextScheduled = nextUp.getKey() - currentTime;
|
final long timeUntilNextScheduled = nextUp.getKey() - currentTime;
|
||||||
if (timeUntilNextScheduled < this.scheduleTime.getWaitTime()) {
|
if (timeUntilNextScheduled < this.scheduleTime.getWaitTime()) {
|
||||||
this.scheduleTime.setWaitTime(timeUntilNextScheduled);
|
this.scheduleTime.setWaitTime(timeUntilNextScheduled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (LOG.isDebugEnabled()) {
|
if (LOG.isDebugEnabled()) {
|
||||||
LOG.debug("Not yet time to execute the job, waiting " + (executionTime - currentTime) + " ms");
|
LOG.debug("Not yet time to execute the job, waiting " + (executionTime - currentTime) + " ms");
|
||||||
|
@ -541,7 +572,6 @@ class JobSchedulerImpl extends ServiceSupport implements Runnable, JobScheduler
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.scheduleTime.pause();
|
this.scheduleTime.pause();
|
||||||
|
|
||||||
} catch (Exception ioe) {
|
} catch (Exception ioe) {
|
||||||
LOG.error(this.name + " Failed to schedule job", ioe);
|
LOG.error(this.name + " Failed to schedule job", ioe);
|
||||||
try {
|
try {
|
||||||
|
@ -556,10 +586,12 @@ class JobSchedulerImpl extends ServiceSupport implements Runnable, JobScheduler
|
||||||
@Override
|
@Override
|
||||||
protected void doStart() throws Exception {
|
protected void doStart() throws Exception {
|
||||||
this.running.set(true);
|
this.running.set(true);
|
||||||
|
synchronized (this.listenerLock) {
|
||||||
|
this.listenerLock.notify();
|
||||||
|
}
|
||||||
this.thread = new Thread(this, "JobScheduler:" + this.name);
|
this.thread = new Thread(this, "JobScheduler:" + this.name);
|
||||||
this.thread.setDaemon(true);
|
this.thread.setDaemon(true);
|
||||||
this.thread.start();
|
this.thread.start();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -570,7 +602,6 @@ class JobSchedulerImpl extends ServiceSupport implements Runnable, JobScheduler
|
||||||
if (t != null) {
|
if (t != null) {
|
||||||
t.join(1000);
|
t.join(1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
long calculateNextExecutionTime(final JobLocation job, long currentTime, int repeat) throws MessageFormatException {
|
long calculateNextExecutionTime(final JobLocation job, long currentTime, int repeat) throws MessageFormatException {
|
||||||
|
@ -608,6 +639,8 @@ class JobSchedulerImpl extends ServiceSupport implements Runnable, JobScheduler
|
||||||
|
|
||||||
static class ValueMarshaller extends VariableMarshaller<List<JobLocation>> {
|
static class ValueMarshaller extends VariableMarshaller<List<JobLocation>> {
|
||||||
static ValueMarshaller INSTANCE = new ValueMarshaller();
|
static ValueMarshaller INSTANCE = new ValueMarshaller();
|
||||||
|
|
||||||
|
@Override
|
||||||
public List<JobLocation> readPayload(DataInput dataIn) throws IOException {
|
public List<JobLocation> readPayload(DataInput dataIn) throws IOException {
|
||||||
List<JobLocation> result = new ArrayList<JobLocation>();
|
List<JobLocation> result = new ArrayList<JobLocation>();
|
||||||
int size = dataIn.readInt();
|
int size = dataIn.readInt();
|
||||||
|
@ -619,6 +652,7 @@ class JobSchedulerImpl extends ServiceSupport implements Runnable, JobScheduler
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void writePayload(List<JobLocation> value, DataOutput dataOut) throws IOException {
|
public void writePayload(List<JobLocation> value, DataOutput dataOut) throws IOException {
|
||||||
dataOut.writeInt(value.size());
|
dataOut.writeInt(value.size());
|
||||||
for (JobLocation jobLocation : value) {
|
for (JobLocation jobLocation : value) {
|
||||||
|
@ -640,6 +674,7 @@ class JobSchedulerImpl extends ServiceSupport implements Runnable, JobScheduler
|
||||||
long getWaitTime() {
|
long getWaitTime() {
|
||||||
return this.waitTime;
|
return this.waitTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param waitTime
|
* @param waitTime
|
||||||
* the waitTime to set
|
* the waitTime to set
|
||||||
|
@ -674,6 +709,5 @@ class JobSchedulerImpl extends ServiceSupport implements Runnable, JobScheduler
|
||||||
mutex.notifyAll();
|
mutex.notifyAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,7 +65,6 @@ public class JobSchedulerStoreImpl extends ServiceSupport implements JobSchedule
|
||||||
private int journalMaxFileLength = Journal.DEFAULT_MAX_FILE_LENGTH;
|
private int journalMaxFileLength = Journal.DEFAULT_MAX_FILE_LENGTH;
|
||||||
private int journalMaxWriteBatchSize = Journal.DEFAULT_MAX_WRITE_BATCH_SIZE;
|
private int journalMaxWriteBatchSize = Journal.DEFAULT_MAX_WRITE_BATCH_SIZE;
|
||||||
private boolean enableIndexWriteAsync = false;
|
private boolean enableIndexWriteAsync = false;
|
||||||
// private int indexWriteBatchSize = PageFile.DEFAULT_WRITE_BATCH_SIZE;
|
|
||||||
MetaData metaData = new MetaData(this);
|
MetaData metaData = new MetaData(this);
|
||||||
final MetaDataMarshaller metaDataMarshaller = new MetaDataMarshaller(this);
|
final MetaDataMarshaller metaDataMarshaller = new MetaDataMarshaller(this);
|
||||||
Map<String, JobSchedulerImpl> schedulers = new HashMap<String, JobSchedulerImpl>();
|
Map<String, JobSchedulerImpl> schedulers = new HashMap<String, JobSchedulerImpl>();
|
||||||
|
|
|
@ -0,0 +1,153 @@
|
||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.activemq.broker.scheduler;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
import javax.jms.Connection;
|
||||||
|
import javax.jms.ConnectionFactory;
|
||||||
|
import javax.jms.Message;
|
||||||
|
import javax.jms.MessageConsumer;
|
||||||
|
import javax.jms.MessageListener;
|
||||||
|
import javax.jms.MessageProducer;
|
||||||
|
import javax.jms.Session;
|
||||||
|
|
||||||
|
import org.apache.activemq.ActiveMQConnectionFactory;
|
||||||
|
import org.apache.activemq.ScheduledMessage;
|
||||||
|
import org.apache.activemq.broker.BrokerService;
|
||||||
|
import org.apache.activemq.util.IOHelper;
|
||||||
|
import org.apache.log4j.BasicConfigurator;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class LostScheduledMessagesTest {
|
||||||
|
|
||||||
|
private BrokerService broker;
|
||||||
|
|
||||||
|
private static final File schedulerDirectory = new File("target/test/ScheduledDB");
|
||||||
|
private static final File messageDirectory = new File("target/test/MessageDB");
|
||||||
|
private static final String QUEUE_NAME = "test";
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() throws Exception {
|
||||||
|
IOHelper.mkdirs(schedulerDirectory);
|
||||||
|
IOHelper.deleteChildren(schedulerDirectory);
|
||||||
|
|
||||||
|
IOHelper.mkdirs(messageDirectory);
|
||||||
|
IOHelper.deleteChildren(messageDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startBroker() throws Exception {
|
||||||
|
broker = new BrokerService();
|
||||||
|
broker.setSchedulerSupport(true);
|
||||||
|
broker.setPersistent(true);
|
||||||
|
broker.setDeleteAllMessagesOnStartup(false);
|
||||||
|
broker.setDataDirectory("target");
|
||||||
|
broker.setSchedulerDirectoryFile(schedulerDirectory);
|
||||||
|
broker.setDataDirectoryFile(messageDirectory);
|
||||||
|
broker.setUseJmx(false);
|
||||||
|
broker.addConnector("vm://localhost");
|
||||||
|
broker.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() throws Exception {
|
||||||
|
broker.stop();
|
||||||
|
BasicConfigurator.resetConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void MessagePassedNotUsingScheduling() throws Exception {
|
||||||
|
doTest(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void MessageLostWhenUsingScheduling() throws Exception {
|
||||||
|
doTest(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doTest(boolean useScheduling) throws Exception {
|
||||||
|
|
||||||
|
int DELIVERY_DELAY_MS = 5000;
|
||||||
|
|
||||||
|
startBroker();
|
||||||
|
|
||||||
|
long startTime = System.currentTimeMillis();
|
||||||
|
|
||||||
|
// Send a message scheduled for delivery in 5 seconds
|
||||||
|
ConnectionFactory cf = new ActiveMQConnectionFactory("vm://localhost");
|
||||||
|
Connection connection = cf.createConnection();
|
||||||
|
connection.start();
|
||||||
|
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
|
||||||
|
MessageProducer producer = session.createProducer(session.createQueue(QUEUE_NAME));
|
||||||
|
Message message = session.createTextMessage("test");
|
||||||
|
if (useScheduling) {
|
||||||
|
message.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_DELAY, DELIVERY_DELAY_MS);
|
||||||
|
}
|
||||||
|
producer.send(message);
|
||||||
|
|
||||||
|
session.close();
|
||||||
|
connection.close();
|
||||||
|
|
||||||
|
broker.getServices();
|
||||||
|
|
||||||
|
// shut down broker
|
||||||
|
broker.stop();
|
||||||
|
broker.waitUntilStopped();
|
||||||
|
|
||||||
|
// Make sure that broker have stopped within delivery delay
|
||||||
|
long shutdownTime = System.currentTimeMillis();
|
||||||
|
assertTrue("Failed to shut down broker in expected time. Test results inconclusive", shutdownTime - startTime < DELIVERY_DELAY_MS);
|
||||||
|
|
||||||
|
// make sure that delivery falls into down time window
|
||||||
|
TimeUnit.MILLISECONDS.sleep(DELIVERY_DELAY_MS);
|
||||||
|
|
||||||
|
// Start new broker instance
|
||||||
|
startBroker();
|
||||||
|
|
||||||
|
final AtomicLong receiveCounter = new AtomicLong();
|
||||||
|
|
||||||
|
cf = new ActiveMQConnectionFactory("vm://localhost");
|
||||||
|
connection = cf.createConnection();
|
||||||
|
connection.start();
|
||||||
|
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
|
||||||
|
|
||||||
|
MessageConsumer consumer = session.createConsumer(session.createQueue(QUEUE_NAME));
|
||||||
|
consumer.setMessageListener(new MessageListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessage(Message message) {
|
||||||
|
receiveCounter.incrementAndGet();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wait for a while to let MQ process the message
|
||||||
|
TimeUnit.MILLISECONDS.sleep(DELIVERY_DELAY_MS * 2);
|
||||||
|
|
||||||
|
session.close();
|
||||||
|
connection.close();
|
||||||
|
|
||||||
|
assertEquals(1, receiveCounter.get());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue