blob: f8c62f6f3cd9710819660495a7471f688499ae26 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2003 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.core.internal.jobs;
import java.util.*;
import org.eclipse.core.internal.runtime.Assert;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.jobs.*;
/**
* Implementation of API type IJobManager
*
* Implementation note: all the data structures of this class are protected
* by a single lock object held as a private field in this class. The JobManager
* instance itself is not used because this class is publicly reachable, and third
* party clients may try to sychronize on it.
*
* The WorkerPool class uses its own monitor for synchronizing its data
* structures. To avoid deadlock between the two classes, the JobManager
* must NEVER call the worker pool while its own monitor is held.
*/
public class JobManager implements IJobManager {
public static final boolean DEBUG = true;
private static JobManager instance;
protected static final long NEVER = Long.MAX_VALUE;
/**
* Flag to indicate that the system is still up and running.
*/
private boolean alive = false;
private final JobListeners jobListeners = new JobListeners();
/**
* The lock for synchronizing all activity in the job manager. To avoid deadlock,
* this lock must never be held for extended periods, and must never be
* held while third party code is being called.
*/
private final Object lock = new Object();
private final LockManager lockManager = new LockManager();
/**
* The pool of worker threads.
*/
private WorkerPool pool;
private IProgressProvider progressProvider = null;
/**
* Jobs that are currently running.
*/
private final HashSet running;
/**
* Jobs that are sleeping. Some sleeping jobs are scheduled to wake
* up at a given start time, while others will sleep indefinitely until woken.
*/
private final JobQueue sleeping;
/**
* jobs that are waiting to be run
*/
private final JobQueue waiting;
public static synchronized JobManager getInstance() {
if (instance == null) {
new JobManager();
}
return instance;
}
public static synchronized void shutdown() {
if (instance != null)
instance.doShutdown();
instance = null;
}
private JobManager() {
instance = this;
synchronized (lock) {
alive = true;
waiting = new JobQueue();
sleeping = new JobQueue();
running = new HashSet(10);
pool = new WorkerPool(this);
}
}
/* (non-Javadoc)
* @see org.eclipse.core.runtime.jobs.IJobManager#addJobListener(org.eclipse.core.runtime.jobs.IJobChangeListener)
*/
public void addJobChangeListener(IJobChangeListener listener) {
jobListeners.add(listener);
}
/**
* Cancels a job
*/
protected boolean cancel(Job job) {
boolean canceled = true;
synchronized (lock) {
switch (job.getState()) {
case Job.WAITING:
waiting.remove(job);
break;
case Job.SLEEPING:
sleeping.remove(job);
break;
case Job.RUNNING:
((InternalJob)job).getMonitor().setCanceled(true);
canceled = false;
break;
case Job.NONE:
default:
return true;
}
}
//only notify listeners if the job was waiting or sleeping
if (canceled) {
((InternalJob) job).setState(Job.NONE);
jobListeners.done(job, Status.CANCEL_STATUS);
}
return canceled;
}
/* (non-Javadoc)
* @see org.eclipse.core.runtime.jobs.IJobManager#cancel(java.lang.String)
*/
public void cancel(String family) {
synchronized (lock) {
for (Iterator it = select(family).iterator(); it.hasNext();) {
((Job)it.next()).cancel();
}
}
}
/**
* Returns a new progress monitor for this job. Never returns null.
*/
protected IProgressMonitor createMonitor(Job job) {
IProgressMonitor monitor = null;
if (progressProvider != null)
monitor = progressProvider.createMonitor(job);
if (monitor == null)
monitor = new NullProgressMonitor();
((InternalJob)job).setMonitor(monitor);
return monitor;
}
public Job currentJob() {
Thread current = Thread.currentThread();
if (current instanceof Worker)
return ((Worker) current).currentJob();
return null;
}
/**
* Returns the delay in milliseconds that a job with a given priority can
* tolerate waiting.
*/
private long delayFor(int priority) {
//these values may need to be tweaked based on machine speed
switch (priority) {
case Job.INTERACTIVE :
return 0L;
case Job.SHORT :
return 50L;
case Job.LONG :
return 100L;
case Job.BUILD :
return 500L;
case Job.DECORATE :
return 1000L;
default :
Assert.isTrue(false, "Job has invalid priority: " + priority); //$NON-NLS-1$
return 0;
}
}
/**
* Shuts down the job manager. Currently running jobs will be told
* to stop, but worker threads may still continue processing.
*/
private void doShutdown() {
synchronized (lock) {
alive = false;
//cancel all running jobs
for (Iterator it = running.iterator(); it.hasNext();) {
Job job = (Job) it.next();
job.cancel();
}
//clean up
sleeping.clear();
waiting.clear();
running.clear();
}
pool.shutdown();
}
/**
* Indicates that a job was running, and has now finished.
*/
protected void endJob(Job job, IStatus result) {
InternalJob internalJob = (InternalJob)job;
InternalJob blocked = null;
synchronized (lock) {
//if the job is finishing asynchronously, there is nothing more to do for now
if (result == Job.ASYNC_FINISH) {
internalJob.setAsyncFinish();
return;
}
internalJob.setState(Job.NONE);
internalJob.setMonitor(null);
running.remove(job);
blocked = internalJob.previous();
internalJob.setPrevious(null);
}
//add any blocked jobs back to the wait queue
while (blocked != null) {
InternalJob previous = blocked.previous();
waiting.enqueue(blocked);
pool.jobQueued(blocked);
blocked = previous;
}
//notify listeners outside sync block
jobListeners.done(job, result);
}
/* (non-Javadoc)
* @see org.eclipse.core.runtime.jobs.IJobManager#find(java.lang.String)
*/
public Job[] find(String family) {
List members = select(family);
return (Job[]) members.toArray(new Job[members.size()]);
}
/**
* Returns a running job whose scheduling rule conflicts with the scheduling rule
* of the given waiting job. Returns null if there are no conflicting jobs.
*/
private InternalJob findBlockingJob(InternalJob waiting) {
if (waiting.getRule() == null)
return null;
for (Iterator it = running.iterator(); it.hasNext();) {
Job job = (Job) it.next();
if (waiting.isConflicting(job))
return job;
}
return null;
}
/* (non-Javadoc)
* @see IJobManager#newLock(java.lang.String)
*/
public ILock newLock() {
return lockManager.newLock();
}
/**
* Removes and returns the first waiting job in the queue. Returns null if there
* are no items waiting in the queue. If an item is removed from the queue,
* it is moved to the running jobs list.
*/
private Job nextJob() {
synchronized (lock) {
//tickle the sleep queue to see if anyone wakes up
long now = System.currentTimeMillis();
InternalJob job = (InternalJob)sleeping.peek();
while (job != null && job.getStartTime() < now) {
sleeping.dequeue();
job.setState(Job.WAITING);
job.setStartTime(now + delayFor(job.getPriority()));
waiting.enqueue(job);
job = (InternalJob)sleeping.peek();
}
//process the wait queue until we find a job whose rules are satisfied.
InternalJob next;
while ((next = waiting.dequeue()) != null) {
InternalJob blocker = findBlockingJob(next);
if (blocker == null)
break;
//queue this job after the job that's blocking it
blocker.addLast(next);
}
//the job to run must be in the running list before we exit
//the sync block, otherwise two jobs with conflicting rules could start at once
if (next != null)
running.add(next);
return (Job)next;
}
}
/* (non-Javadoc)
* @see org.eclipse.core.runtime.jobs.IJobManager#removeJobListener(org.eclipse.core.runtime.jobs.IJobChangeListener)
*/
public void removeJobChangeListener(IJobChangeListener listener) {
jobListeners.remove(listener);
}
/* (non-Javadoc)
* @see org.eclipse.core.runtime.jobs.Job#schedule(long)
*/
protected void schedule(InternalJob job, long delay) {
Assert.isNotNull(job, "Job is null"); //$NON-NLS-1$
synchronized (lock) {
//can't schedule a job that is already waiting, sleeping, or running
if (job.getState() != Job.NONE)
return;
if (delay > 0) {
job.setState(Job.SLEEPING);
job.setStartTime(System.currentTimeMillis() + delay);
sleeping.enqueue(job);
} else {
job.setState(Job.WAITING);
job.setStartTime(System.currentTimeMillis() + delayFor(job.getPriority()));
waiting.enqueue(job);
}
}
//call the pool outside sync block to avoid deadlock
pool.jobQueued(job);
//notify listeners outside sync block
jobListeners.scheduled((Job)job);
}
/**
* Adds all family members in the list of jobs to the collection
*/
private void select(List members, String family, Job firstJob) {
if (firstJob == null)
return;
Job job = firstJob;
do {
if (job.belongsTo(family))
members.add(job);
job = (Job)((InternalJob)job).previous();
} while (job != null && job != firstJob);
}
/**
* Returns a list of all jobs known to the job manager that belong to the given family.
*/
private List select(String family) {
List members = new ArrayList();
synchronized (lock) {
for (Iterator it = running.iterator(); it.hasNext();) {
select(members, family, (Job)it.next());
}
select(members, family, (Job)waiting.peek());
select(members, family, (Job)sleeping.peek());
}
return members;
}
/* (non-Javadoc)
* @see IJobManager#setLockListener(ILockListener)
*/
public void setLockListener(ILockListener listener) {
lockManager.setLockListener(listener);
}
/**
* Changes a job priority.
*/
protected void setPriority(InternalJob job, int newPriority) {
synchronized (lock) {
int oldPriority = job.getPriority();
if (oldPriority == newPriority)
return;
job.internalSetPriority(newPriority);
//if the job is waiting to run, reshuffle the queue
if (job.getState() == Job.WAITING) {
long oldStart = job.getStartTime();
job.setStartTime(oldStart + (delayFor(newPriority) - delayFor(oldPriority)));
waiting.resort(job);
}
}
}
/* (non-Javadoc)
* @see IJobManager#setProgressProvider(IProgressProvider)
*/
public void setProgressProvider(IProgressProvider provider) {
progressProvider = provider;
}
/**
* Puts a job to sleep. Returns true if the job was successfully put to sleep.
*/
protected boolean sleep(InternalJob job) {
synchronized (lock) {
switch (job.getState()) {
case Job.RUNNING :
//cannot be paused if it is already running
return false;
case Job.SLEEPING :
//update the job wake time
job.setStartTime(NEVER);
return true;
case Job.NONE:
return true;
case Job.WAITING :
//put the job to sleep
waiting.remove(job);
job.setStartTime(NEVER);
job.setState(Job.SLEEPING);
sleeping.enqueue(job);
//fall through and notify listeners
}
}
jobListeners.sleeping((Job) job);
return true;
}
/* (non-Javadoc)
* @see IJobManager#sleep(String)
*/
public void sleep(String family) {
synchronized (lock) {
for (Iterator it = select(family).iterator(); it.hasNext();) {
((Job)it.next()).sleep();
}
}
}
/**
* Returns the estimated time in milliseconds before the next job is scheduled
* to wake up. The result may be negative. Returns JobManager.NEVER if
* there are no sleeping or waiting jobs.
*/
protected long sleepHint() {
synchronized (lock) {
if (waiting.peek() != null)
return 0L;
InternalJob next = (InternalJob)sleeping.peek();
return next == null ? NEVER : next.getStartTime() - System.currentTimeMillis();
}
}
/**
* Returns the next job to be run, or null if no jobs are waiting to run.
* The worker must call endJob when the job is finished running.
*/
protected Job startJob() {
while (true) {
Job job = nextJob();
if (job == null)
return null;
//must perform this outside sync block because it is third party code
if (job.shouldRun()) {
//check for listener veto
jobListeners.aboutToRun(job);
//listeners may have canceled or put the job to sleep
if (job.getState() == Job.WAITING) {
((InternalJob)job).setState(Job.RUNNING);
jobListeners.running(job);
return job;
}
}
if (job.getState() != Job.SLEEPING) {
//job has been vetoed or canceled, so mark it as done
endJob(job, Status.CANCEL_STATUS);
continue;
}
}
}
/* (non-Javadoc)
* @see org.eclipse.core.runtime.jobs.IJobManager#wait(org.eclipse.core.runtime.jobs.Job, org.eclipse.core.runtime.IProgressMonitor)
*/
public void wait(Job job, IProgressMonitor monitor) {
synchronized (lock) {
//compute set of all jobs that must run before this one
//add a listener that removes jobs from the blocking set when they finish
}
//wait until listener notifies this thread.
}
/* (non-Javadoc)
* @see IJobManager#wait(String, IProgressMonitor)
*/
public void wait(String family, IProgressMonitor monitor) {
}
/**
* Implementation of wakeUp()
*/
protected void wakeUp(InternalJob job) {
synchronized (lock) {
//cannot wake up if it is not sleeping
if (job.getState() != Job.SLEEPING)
return;
sleeping.remove(job);
job.setState(Job.WAITING);
job.setStartTime(System.currentTimeMillis() + delayFor(job.getPriority()));
waiting.enqueue(job);
}
//call the pool outside sync block to avoid deadlock
pool.jobQueued(job);
jobListeners.awake((Job) job);
}
/* (non-Javadoc)
* @see IJobFamily#wakeUp(String)
*/
public void wakeUp(String family) {
synchronized (lock) {
for (Iterator it = select(family).iterator(); it.hasNext();) {
((Job)it.next()).wakeUp();
}
}
}
}