package org.eclipse.core.internal.jobs;
import java.util.*;
import org.eclipse.core.internal.runtime.RuntimeLog;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.jobs.*;
import org.eclipse.osgi.service.debug.*;
import org.eclipse.osgi.util.NLS;
public class JobManager implements IJobManager, DebugOptionsListener {
public static final String PI_JOBS = "org.eclipse.core.jobs";
public static final int PLUGIN_ERROR = 2;
private static final long MAX_WAIT_INTERVAL = 100;
private static final String OPTION_DEADLOCK_ERROR = PI_JOBS + "/jobs/errorondeadlock";
private static final String OPTION_DEBUG_BEGIN_END = PI_JOBS + "/jobs/beginend";
private static final String OPTION_DEBUG_YIELDING = PI_JOBS + "/jobs/yielding";
private static final String OPTION_DEBUG_YIELDING_DETAILED = PI_JOBS + "/jobs/yielding/detailed";
private static final String OPTION_DEBUG_JOBS = PI_JOBS + "/jobs";
private static final String OPTION_LOCKS = PI_JOBS + "/jobs/locks";
private static final String OPTION_SHUTDOWN = PI_JOBS + "/jobs/shutdown";
static DebugTrace DEBUG_TRACE;
static boolean DEBUG = false;
static boolean DEBUG_BEGIN_END = false;
static boolean DEBUG_YIELDING = false;
static boolean DEBUG_YIELDING_DETAILED = false;
static boolean DEBUG_DEADLOCK = false;
static boolean DEBUG_LOCKS = false;
static boolean DEBUG_SHUTDOWN = false;
private static JobManager instance;
private static final ISchedulingRule nullRule = new ISchedulingRule() {
@Override
public boolean contains(ISchedulingRule rule) {
return rule == this;
}
@Override
public boolean isConflicting(ISchedulingRule rule) {
return rule == this;
}
};
private volatile boolean active = true;
final ImplicitJobs implicitJobs = new ImplicitJobs(this);
private final JobListeners jobListeners = new JobListeners();
private final Object lock = new Object();
private final IJobChangeListener jobGroupUpdater = new JobGroupUpdater(lock);
private final LockManager lockManager = new LockManager();
private WorkerPool pool;
private ProgressProvider progressProvider = null;
private final HashSet<InternalJob> running;
private final HashSet<InternalJob> yielding;
private final JobQueue sleeping;
private boolean suspended = false;
private final JobQueue waiting;
final JobQueue waitingThreadJobs;
Counter waitQueueCounter = new Counter();
final List<Object[]> monitorStack = new ArrayList<>();
private final InternalWorker internalWorker;
public static void debug(String msg) {
DEBUG_TRACE.trace(null, msg);
}
static synchronized JobManager getInstance() {
if (instance == null)
new JobManager();
return instance;
}
private static String printJobName(Job job) {
if (job instanceof ThreadJob) {
Job realJob = ((ThreadJob) job).realJob;
if (realJob != null)
return realJob.getClass().getName();
return "ThreadJob on rule: " + job.getRule();
}
return job.getClass().getName();
}
public static String printState(Job job) {
return printState(((InternalJob) job).internalGetState());
}
public static String printState(int state) {
switch (state) {
case Job.NONE :
return "NONE";
case Job.WAITING :
return "WAITING";
case Job.SLEEPING :
return "SLEEPING";
case Job.RUNNING :
return "RUNNING";
case InternalJob.BLOCKED :
return "BLOCKED";
case InternalJob.YIELDING :
return "YIELDING";
case InternalJob.ABOUT_TO_RUN :
return "ABOUT_TO_RUN";
case InternalJob.ABOUT_TO_SCHEDULE :
return "ABOUT_TO_SCHEDULE";
}
return "UNKNOWN";
}
public static void shutdown() {
if (instance != null) {
instance.doShutdown();
instance = null;
}
}
private JobManager() {
instance = this;
synchronized (lock) {
waiting = new JobQueue(false);
waitingThreadJobs = new JobQueue(false, false);
sleeping = new JobQueue(true);
running = new HashSet<>(10);
yielding = new HashSet<>(10);
pool = new WorkerPool(this);
}
pool.setDaemon(JobOSGiUtils.getDefault().useDaemonThreads());
internalWorker = new InternalWorker(this);
internalWorker.setDaemon(JobOSGiUtils.getDefault().useDaemonThreads());
internalWorker.start();
jobListeners.add(jobGroupUpdater);
}
@Override
public void addJobChangeListener(IJobChangeListener listener) {
jobListeners.add(listener);
}
@Override
public void beginRule(ISchedulingRule rule, IProgressMonitor monitor) {
validateRule(rule);
implicitJobs.begin(rule, monitorFor(monitor), false);
}
protected boolean cancel(InternalJob job) {
IProgressMonitor monitor = null;
boolean runCanceling = false;
synchronized (lock) {
job.setAboutToRunCanceled(true);
switch (job.getState()) {
case Job.NONE :
return true;
case Job.RUNNING :
if (job.internalGetState() == Job.RUNNING) {
monitor = job.getProgressMonitor();
runCanceling = !job.isRunCanceled();
if (runCanceling)
job.setRunCanceled(true);
break;
}
return false;
default :
changeState(job, Job.NONE);
}
}
if (monitor != null) {
if (runCanceling) {
if (!monitor.isCanceled())
monitor.setCanceled(true);
job.canceling();
}
return false;
}
jobListeners.done((Job) job, Status.CANCEL_STATUS, false);
return true;
}
@Override
public void cancel(Object family) {
for (InternalJob internalJob : select(family))
cancel(internalJob);
}
void cancel(InternalJobGroup jobGroup) {
cancel(jobGroup, false);
}
void cancel(InternalJobGroup jobGroup, boolean cancelDueToError) {
Assert.isLegal(jobGroup != null, "jobGroup should not be null");
synchronized (lock) {
switch (jobGroup.getState()) {
case JobGroup.NONE :
return;
case JobGroup.CANCELING :
if (!cancelDueToError) {
jobGroup.updateCancelingReason(cancelDueToError);
}
return;
default :
jobGroup.cancelAndNotify(cancelDueToError);
}
}
}
private void changeState(InternalJob job, int newState) {
boolean blockedJobs = false;
synchronized (lock) {
int oldJobState;
synchronized (job.jobStateLock) {
job.jobStateLock.notifyAll();
oldJobState = job.getState();
int oldState = job.internalGetState();
switch (oldState) {
case InternalJob.YIELDING :
yielding.remove(job);
case Job.NONE :
case InternalJob.ABOUT_TO_SCHEDULE :
break;
case InternalJob.BLOCKED :
job.remove();
break;
case Job.WAITING :
try {
waiting.remove(job);
} catch (RuntimeException e) {
Assert.isLegal(false, "Tried to remove a job that wasn't in the queue");
}
break;
case Job.SLEEPING :
try {
sleeping.remove(job);
} catch (RuntimeException e) {
Assert.isLegal(false, "Tried to remove a job that wasn't in the queue");
}
break;
case Job.RUNNING :
case InternalJob.ABOUT_TO_RUN :
running.remove(job);
InternalJob blocked = job.previous();
job.remove();
blockedJobs = blocked != null;
while (blocked != null) {
InternalJob previous = blocked.previous();
changeState(blocked, Job.WAITING);
blocked = previous;
}
break;
default :
Assert.isLegal(false, "Invalid job state: " + job + ", state: " + oldState);
}
job.internalSetState(newState);
switch (newState) {
case Job.NONE :
job.setStartTime(InternalJob.T_NONE);
job.setWaitQueueStamp(InternalJob.T_NONE);
job.setRunCanceled(false);
case InternalJob.BLOCKED :
break;
case Job.WAITING :
waiting.enqueue(job);
break;
case Job.SLEEPING :
try {
sleeping.enqueue(job);
} catch (RuntimeException e) {
throw new RuntimeException("Error changing from state: " + oldState);
}
break;
case Job.RUNNING :
case InternalJob.ABOUT_TO_RUN :
job.setStartTime(InternalJob.T_NONE);
job.setWaitQueueStamp(InternalJob.T_NONE);
running.add(job);
break;
case InternalJob.YIELDING :
yielding.add(job);
case InternalJob.ABOUT_TO_SCHEDULE :
break;
default :
Assert.isLegal(false, "Invalid job state: " + job + ", state: " + newState);
}
}
InternalJobGroup jobGroup = job.getJobGroup();
if (jobGroup != null) {
jobGroup.jobStateChanged(job, oldJobState, job.getState());
}
}
if (blockedJobs)
pool.jobQueued();
}
protected IProgressMonitor createMonitor(InternalJob job, IProgressMonitor group, int ticks) {
synchronized (lock) {
if (job.getState() != Job.NONE)
return null;
IProgressMonitor monitor = null;
if (progressProvider != null)
monitor = progressProvider.createMonitor((Job) job, group, ticks);
if (monitor == null)
monitor = new NullProgressMonitor();
return monitor;
}
}
private IProgressMonitor createMonitor(Job job) {
IProgressMonitor monitor = null;
if (progressProvider != null)
monitor = progressProvider.createMonitor(job);
if (monitor == null)
monitor = new NullProgressMonitor();
return monitor;
}
@Override
public IProgressMonitor createProgressGroup() {
if (progressProvider != null)
return progressProvider.createProgressGroup();
return new NullProgressMonitor();
}
@Override
public Job currentJob() {
Thread current = Thread.currentThread();
if (current instanceof Worker)
return ((Worker) current).currentJob();
synchronized (lock) {
for (InternalJob internalJob : running) {
Job job = (Job) internalJob;
if (job.getThread() == current)
return job;
}
}
return null;
}
@Override
public ISchedulingRule currentRule() {
Job currentJob = implicitJobs.getThreadJob(Thread.currentThread());
if (currentJob != null)
return currentJob.getRule();
currentJob = currentJob();
if (currentJob != null)
return currentJob.getRule();
return null;
}
private long delayFor(int priority) {
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);
return 0;
}
}
private boolean doSchedule(InternalJob job, long delay) {
boolean cancelling = false;
synchronized (lock) {
int state = job.internalGetState();
if (state != InternalJob.ABOUT_TO_SCHEDULE && state != Job.SLEEPING)
return false;
if (job.isAboutToRunCanceled()) {
cancelling = true;
job.setResult(Status.CANCEL_STATUS);
job.setProgressMonitor(null);
job.setThread(null);
changeState(job, Job.NONE);
} else {
if (job.getPriority() == Job.DECORATE && job.getRule() == null) {
long minDelay = running.size() * 100;
delay = Math.max(delay, minDelay);
}
if (delay > 0) {
job.setStartTime(System.currentTimeMillis() + delay);
changeState(job, Job.SLEEPING);
} else {
job.setStartTime(System.currentTimeMillis() + delayFor(job.getPriority()));
job.setWaitQueueStamp(waitQueueCounter.increment());
changeState(job, Job.WAITING);
}
}
}
if (cancelling)
jobListeners.done((Job) job, Status.CANCEL_STATUS, false);
return !cancelling;
}
private void doShutdown() {
Job[] toCancel = null;
synchronized (lock) {
if (!active)
return;
active = false;
toCancel = running.toArray(new Job[running.size()]);
sleeping.clear();
waiting.clear();
}
if (toCancel != null && toCancel.length > 0) {
for (Job element : toCancel) {
cancel(element);
}
for (int waitAttempts = 0; waitAttempts < 3; waitAttempts++) {
Thread.yield();
synchronized (lock) {
if (running.isEmpty())
break;
}
if (DEBUG_SHUTDOWN) {
JobManager.debug("Shutdown - job wait cycle #" + (waitAttempts + 1));
Job[] stillRunning = null;
synchronized (lock) {
stillRunning = running.toArray(new Job[running.size()]);
}
if (stillRunning != null) {
for (Job element : stillRunning) {
JobManager.debug("\tJob: " + printJobName(element));
}
}
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
Thread.yield();
}
synchronized (lock) {
toCancel = running.toArray(new Job[running.size()]);
}
}
internalWorker.cancel();
if (toCancel != null) {
for (Job element : toCancel) {
String jobName = printJobName(element);
String msg = "Job found still running after platform shutdown. Jobs should be canceled by the plugin that scheduled them during shutdown: " + jobName;
RuntimeLog.log(new Status(IStatus.WARNING, JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, msg, null));
System.err.println(msg);
}
}
synchronized (lock) {
running.clear();
}
pool.shutdown();
jobListeners.remove(jobGroupUpdater);
}
protected void endJob(InternalJob job, IStatus result, boolean notify) {
long rescheduleDelay = InternalJob.T_NONE;
synchronized (lock) {
if (result == Job.ASYNC_FINISH)
return;
if (job.getState() == Job.NONE)
return;
if (JobManager.DEBUG && notify)
JobManager.debug("Ending job: " + job);
job.setResult(result);
job.setProgressMonitor(null);
job.setThread(null);
rescheduleDelay = job.getStartTime();
changeState(job, Job.NONE);
}
final boolean reschedule = active && rescheduleDelay > InternalJob.T_NONE && job.shouldSchedule();
if (notify)
jobListeners.done((Job) job, result, reschedule);
if (reschedule)
schedule(job, rescheduleDelay, reschedule);
if (job.getJobGroup() == null && result.matches(IStatus.ERROR | IStatus.WARNING))
RuntimeLog.log(result);
}
@Override
public void endRule(ISchedulingRule rule) {
implicitJobs.end(rule, false);
}
@Override
public Job[] find(Object family) {
List<InternalJob> members = select(family);
return members.toArray(new Job[members.size()]);
}
List<Job> find(InternalJobGroup jobGroup) {
Assert.isLegal(jobGroup != null, "jobGroup should not be null");
synchronized (lock) {
return jobGroup.internalGetActiveJobs();
}
}
protected InternalJob findBlockingJob(InternalJob waitingJob) {
if (waitingJob.getRule() == null)
return null;
synchronized (lock) {
if (running.isEmpty())
return null;
boolean hasBlockedJobs = false;
for (InternalJob job : running) {
if (waitingJob.isConflicting(job))
return job;
if (!hasBlockedJobs)
hasBlockedJobs = job.previous() != null;
}
if (!hasBlockedJobs)
return null;
for (InternalJob job : running) {
while (true) {
job = job.previous();
if (job == null)
break;
if (waitingJob.isConflicting(job))
return job;
}
}
}
return null;
}
InternalJob findBlockedJob(InternalJob job, Iterator jobs) {
synchronized (lock) {
while (jobs.hasNext()) {
InternalJob waitingJob = (InternalJob) jobs.next();
if (waitingJob.isConflicting(job))
return waitingJob;
}
return null;
}
}
void dequeue(JobQueue queue, InternalJob job) {
synchronized (lock) {
queue.remove(job);
}
}
void enqueue(JobQueue queue, InternalJob job) {
synchronized (lock) {
queue.enqueue(job);
}
}
public LockManager getLockManager() {
return lockManager;
}
private String getWaitMessage(int jobCount) {
String message = jobCount == 1 ? JobMessages.jobs_waitFamSubOne : JobMessages.jobs_waitFamSub;
return NLS.bind(message, Integer.toString(jobCount));
}
protected boolean isActive() {
return active;
}
protected boolean isBlocking(InternalJob runningJob) {
synchronized (lock) {
if (runningJob.getState() != Job.RUNNING)
return false;
InternalJob previous = runningJob.previous();
while (previous != null) {
if (previous.getPriority() < runningJob.getPriority()) {
if (!previous.isSystem())
return true;
if (previous instanceof ThreadJob && ((ThreadJob) previous).shouldInterrupt())
return true;
}
previous = previous.previous();
}
for (Iterator i = waitingThreadJobs.iterator(); i.hasNext();) {
ThreadJob waitingJob = (ThreadJob) i.next();
if (runningJob.isConflicting(waitingJob) && waitingJob.shouldInterrupt())
return true;
}
return false;
}
}
@Override
public boolean isIdle() {
synchronized (lock) {
return running.isEmpty() && waiting.isEmpty();
}
}
@Override
public boolean isSuspended() {
synchronized (lock) {
return suspended;
}
}
protected boolean join(InternalJob job, long timeout, IProgressMonitor monitor) throws InterruptedException {
Assert.isLegal(timeout >= 0, "timeout should not be negative");
long deadline = timeout == 0 ? 0 : System.currentTimeMillis() + timeout;
Job currentJob = currentJob();
if (currentJob != null) {
JobGroup jobGroup = currentJob.getJobGroup();
if (timeout == 0 && jobGroup != null && jobGroup.getMaxThreads() != 0 && jobGroup == job.getJobGroup())
throw new IllegalStateException("Joining on a job belonging to the same group is not allowed");
}
final IJobChangeListener listener;
final Semaphore barrier;
synchronized (lock) {
int state = job.getState();
if (state == Job.NONE)
return true;
if (suspended && state != Job.RUNNING)
return true;
if (state == Job.RUNNING && job.getThread() == Thread.currentThread())
throw new IllegalStateException("Job attempted to join itself");
barrier = new Semaphore(null);
listener = new JobChangeAdapter() {
@Override
public void done(IJobChangeEvent event) {
barrier.release();
}
};
job.addJobChangeListener(listener);
}
try {
boolean canBlock = lockManager.canBlock();
while (true) {
if (monitor != null && monitor.isCanceled())
throw new OperationCanceledException();
long remainingTime = deadline;
if (deadline != 0) {
remainingTime -= System.currentTimeMillis();
if (remainingTime <= 0) {
return false;
}
}
lockManager.aboutToWait(job.getThread());
try {
long sleepTime = remainingTime != 0 && remainingTime <= MAX_WAIT_INTERVAL ? remainingTime : MAX_WAIT_INTERVAL;
if (barrier.acquire(sleepTime))
break;
} catch (InterruptedException e) {
if (canBlock)
throw e;
}
}
} finally {
lockManager.aboutToRelease();
job.removeJobChangeListener(listener);
}
return true;
}
@Override
public void join(final Object family, IProgressMonitor monitor) throws InterruptedException, OperationCanceledException {
monitor = monitorFor(monitor);
IJobChangeListener listener = null;
final Set<InternalJob> jobs;
int jobCount;
Job blocking = null;
synchronized (lock) {
int states = suspended ? Job.RUNNING : Job.RUNNING | Job.WAITING | Job.SLEEPING;
jobs = Collections.synchronizedSet(new HashSet<>(select(family, states)));
jobCount = jobs.size();
if (jobCount > 0) {
if (jobCount == 1)
blocking = (Job) jobs.iterator().next();
listener = new JobChangeAdapter() {
@Override
public void done(IJobChangeEvent event) {
if (!((JobChangeEvent) event).reschedule)
jobs.remove(event.getJob());
}
@Override
public void running(IJobChangeEvent event) {
Job job = event.getJob();
if (family == null || job.belongsTo(family))
jobs.add(job);
}
@Override
public void scheduled(IJobChangeEvent event) {
if (((JobChangeEvent) event).reschedule)
return;
if (isSuspended())
return;
Job job = event.getJob();
if (family == null || job.belongsTo(family))
jobs.add(job);
}
};
addJobChangeListener(listener);
}
}
if (jobCount == 0) {
monitor.beginTask(JobMessages.jobs_blocked0, 1);
monitor.done();
return;
}
try {
monitor.beginTask(JobMessages.jobs_blocked0, jobCount);
monitor.subTask(getWaitMessage(jobCount));
reportBlocked(monitor, blocking);
int jobsLeft;
int reportedWorkDone = 0;
while ((jobsLeft = jobs.size()) > 0) {
int actualWorkDone = Math.max(0, jobCount - jobsLeft);
if (reportedWorkDone < actualWorkDone) {
monitor.worked(actualWorkDone - reportedWorkDone);
reportedWorkDone = actualWorkDone;
monitor.subTask(getWaitMessage(jobsLeft));
}
if (Thread.interrupted())
throw new InterruptedException();
if (monitor.isCanceled())
throw new OperationCanceledException();
lockManager.aboutToWait(null);
Thread.sleep(100);
}
} finally {
lockManager.aboutToRelease();
removeJobChangeListener(listener);
reportUnblocked(monitor);
monitor.done();
}
}
boolean join(InternalJobGroup jobGroup, long timeout, IProgressMonitor monitor) throws InterruptedException, OperationCanceledException {
Assert.isLegal(jobGroup != null, "jobGroup should not be null");
Assert.isLegal(timeout >= 0, "timeout should not be negative");
long deadline = timeout == 0 ? 0 : System.currentTimeMillis() + timeout;
int jobCount;
synchronized (lock) {
jobCount = jobGroup.getActiveJobsCount();
}
SubMonitor subMonitor = SubMonitor.convert(monitor, JobMessages.jobs_blocked0, jobCount);
try {
while (true) {
if (subMonitor.isCanceled())
throw new OperationCanceledException();
long remainingTime = deadline;
if (deadline != 0) {
remainingTime -= System.currentTimeMillis();
if (remainingTime <= 0) {
return false;
}
}
synchronized (lock) {
if ((suspended && jobGroup.getRunningJobsCount() == 0))
break;
}
if (jobGroup.doJoin(remainingTime))
break;
int jobsLeft;
synchronized (lock) {
jobsLeft = jobGroup.getActiveJobsCount();
}
if (jobsLeft < jobCount)
subMonitor.worked(jobCount - jobsLeft);
jobCount = jobsLeft;
subMonitor.setWorkRemaining(jobCount);
subMonitor.subTask(getWaitMessage(jobCount));
}
} finally {
if (monitor != null) {
monitor.done();
}
}
return true;
}
private IProgressMonitor monitorFor(IProgressMonitor monitor) {
if (monitor == null || (monitor instanceof NullProgressMonitor)) {
if (progressProvider != null) {
try {
monitor = progressProvider.getDefaultMonitor();
} catch (Exception e) {
String msg = NLS.bind(JobMessages.meta_pluginProblems, JobManager.PI_JOBS);
RuntimeLog.log(new Status(IStatus.ERROR, JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, msg, e));
}
}
}
if (monitor == null)
return new NullProgressMonitor();
return monitor;
}
@Override
public ILock newLock() {
return lockManager.newLock();
}
private Job nextJob() {
synchronized (lock) {
if (suspended)
return null;
long now = System.currentTimeMillis();
InternalJob job = sleeping.peek();
while (job != null && job.getStartTime() < now) {
job.setStartTime(now + delayFor(job.getPriority()));
job.setWaitQueueStamp(waitQueueCounter.increment());
changeState(job, Job.WAITING);
job = sleeping.peek();
}
InternalJobGroup jobGroup = null;
job = waiting.peek();
while (job != null) {
InternalJob blocker = findBlockingJob(job);
jobGroup = job.getJobGroup();
InternalJob nextWaitingJob = job.previous();
if (blocker != null) {
changeState(job, InternalJob.BLOCKED);
Assert.isTrue(job.next() == null);
Assert.isTrue(job.previous() == null);
blocker.addLast(job);
} else if (jobGroup == null || jobGroup.getMaxThreads() == 0 || (jobGroup.getState() != JobGroup.CANCELING && jobGroup.getRunningJobsCount() < jobGroup.getMaxThreads())) {
break;
}
job = nextWaitingJob == waiting.dummy ? null : nextWaitingJob;
}
if (job != null) {
changeState(job, InternalJob.ABOUT_TO_RUN);
if (JobManager.DEBUG)
JobManager.debug("Starting job: " + job);
}
return (Job) job;
}
}
@Override
public void optionsChanged(DebugOptions options) {
DEBUG_TRACE = options.newDebugTrace(PI_JOBS);
DEBUG = options.getBooleanOption(OPTION_DEBUG_JOBS, false);
DEBUG_BEGIN_END = options.getBooleanOption(OPTION_DEBUG_BEGIN_END, false);
DEBUG_YIELDING = options.getBooleanOption(OPTION_DEBUG_YIELDING, false);
DEBUG_YIELDING_DETAILED = options.getBooleanOption(OPTION_DEBUG_YIELDING_DETAILED, false);
DEBUG_DEADLOCK = options.getBooleanOption(OPTION_DEADLOCK_ERROR, false);
DEBUG_LOCKS = options.getBooleanOption(OPTION_LOCKS, false);
DEBUG_SHUTDOWN = options.getBooleanOption(OPTION_SHUTDOWN, false);
}
@Override
public void removeJobChangeListener(IJobChangeListener listener) {
jobListeners.remove(listener);
}
final void reportBlocked(IProgressMonitor monitor, InternalJob blockingJob) {
if (!(monitor instanceof IProgressMonitorWithBlocking))
return;
IStatus reason;
if (blockingJob == null || blockingJob instanceof ThreadJob || blockingJob.isSystem()) {
reason = new Status(IStatus.INFO, JobManager.PI_JOBS, 1, JobMessages.jobs_blocked0, null);
} else {
String msg = NLS.bind(JobMessages.jobs_blocked1, blockingJob.getName());
reason = new JobStatus(IStatus.INFO, (Job) blockingJob, msg);
}
((IProgressMonitorWithBlocking) monitor).setBlocked(reason);
}
final void reportUnblocked(IProgressMonitor monitor) {
if (monitor instanceof IProgressMonitorWithBlocking)
((IProgressMonitorWithBlocking) monitor).clearBlocked();
}
@Override
public final void resume() {
synchronized (lock) {
suspended = false;
pool.jobQueued();
}
}
@Deprecated
@Override
public final void resume(ISchedulingRule rule) {
implicitJobs.resume(rule);
}
protected InternalJob runNow(ThreadJob job, boolean releaseWaiting) {
if (releaseWaiting) {
synchronized (implicitJobs) {
synchronized (lock) {
return doRunNow(job, releaseWaiting);
}
}
}
synchronized (lock) {
return doRunNow(job, releaseWaiting);
}
}
private InternalJob doRunNow(ThreadJob job, boolean releaseWaiting) {
InternalJob blocking = findBlockingJob(job);
if (blocking == null) {
changeState(job, Job.RUNNING);
((InternalJob) job).setProgressMonitor(new NullProgressMonitor());
job.run(null);
if (releaseWaiting) {
implicitJobs.removeWaiting(job);
}
}
return blocking;
}
protected void schedule(InternalJob job, long delay, boolean reschedule) {
if (!active)
throw new IllegalStateException("Job manager has been shut down.");
Assert.isNotNull(job, "Job is null");
Assert.isLegal(delay >= 0, "Scheduling delay is negative");
synchronized (lock) {
if (!reschedule)
job.setAboutToRunCanceled(false);
if (job.getState() == Job.RUNNING) {
job.setStartTime(delay);
return;
}
if (job.internalGetState() != Job.NONE)
return;
if (JobManager.DEBUG)
JobManager.debug("Scheduling job: " + job);
changeState(job, InternalJob.ABOUT_TO_SCHEDULE);
}
jobListeners.scheduled((Job) job, delay, reschedule);
doSchedule(job, delay);
pool.jobQueued();
}
private void select(List<InternalJob> members, Object family, InternalJob firstJob, int stateMask) {
if (firstJob == null)
return;
InternalJob job = firstJob;
do {
if ((family == null || job.belongsTo(family)) && ((job.getState() & stateMask) != 0))
members.add(job);
job = job.previous();
} while (job != null && job != firstJob);
}
private List<InternalJob> select(Object family) {
return select(family, Job.WAITING | Job.SLEEPING | Job.RUNNING);
}
private List<InternalJob> select(Object family, int stateMask) {
List<InternalJob> members = new ArrayList<>();
synchronized (lock) {
if ((stateMask & Job.RUNNING) != 0) {
for (InternalJob internalJob : running) {
select(members, family, internalJob, stateMask);
}
}
if ((stateMask & Job.WAITING) != 0) {
select(members, family, waiting.peek(), stateMask);
for (InternalJob internalJob : yielding) {
select(members, family, internalJob, stateMask);
}
}
if ((stateMask & Job.SLEEPING) != 0)
select(members, family, sleeping.peek(), stateMask);
}
return members;
}
@Override
public void setLockListener(LockListener listener) {
lockManager.setLockListener(listener);
}
protected void setPriority(InternalJob job, int newPriority) {
synchronized (lock) {
int oldPriority = job.getPriority();
if (oldPriority == newPriority)
return;
job.internalSetPriority(newPriority);
if (job.getState() == Job.WAITING) {
long oldStart = job.getStartTime();
job.setStartTime(oldStart + (delayFor(newPriority) - delayFor(oldPriority)));
waiting.resort(job);
}
}
}
@Override
public void setProgressProvider(ProgressProvider provider) {
progressProvider = provider;
}
public void setRule(InternalJob job, ISchedulingRule rule) {
synchronized (lock) {
Assert.isLegal(job.getState() == Job.NONE);
validateRule(rule);
job.internalSetRule(rule);
}
}
protected boolean sleep(InternalJob job) {
synchronized (lock) {
switch (job.getState()) {
case Job.RUNNING :
if (job.internalGetState() == Job.RUNNING)
return false;
break;
case Job.SLEEPING :
job.setStartTime(InternalJob.T_INFINITE);
changeState(job, Job.SLEEPING);
return true;
case Job.NONE :
return true;
case Job.WAITING :
break;
}
job.setStartTime(InternalJob.T_INFINITE);
changeState(job, Job.SLEEPING);
}
jobListeners.sleeping((Job) job);
return true;
}
@Override
public void sleep(Object family) {
for (InternalJob internalJob : select(family)) {
sleep(internalJob);
}
}
protected long sleepHint() {
synchronized (lock) {
if (suspended)
return InternalJob.T_INFINITE;
if (!waiting.isEmpty())
return 0L;
InternalJob next = sleeping.peek();
if (next == null)
return InternalJob.T_INFINITE;
return next.getStartTime() - System.currentTimeMillis();
}
}
protected Job yieldRule(InternalJob job, IProgressMonitor monitor) {
Thread currentThread = Thread.currentThread();
Assert.isLegal(job.getState() == Job.RUNNING, "Cannot yieldRule job that is " + printState(job.internalGetState()));
Assert.isLegal(currentThread == job.getThread(), "Cannot yieldRule from outside job's thread");
InternalJob unblocked;
ThreadJob likeThreadJob;
synchronized (implicitJobs) {
synchronized (lock) {
likeThreadJob = implicitJobs.getThreadJob(currentThread);
unblocked = job.previous();
if (unblocked == null) {
if (likeThreadJob != null) {
unblocked = ((InternalJob) likeThreadJob).previous();
if (unblocked == null) {
unblocked = findBlockedJob(likeThreadJob, waitingThreadJobs.iterator());
}
} else {
unblocked = findBlockedJob(job, waitingThreadJobs.iterator());
}
}
if (unblocked == null)
return null;
changeState(job, InternalJob.YIELDING);
if (DEBUG_YIELDING)
JobManager.debug(job + " will yieldRule to " + unblocked);
if (likeThreadJob != null && likeThreadJob != job) {
changeState(likeThreadJob, InternalJob.YIELDING);
if (DEBUG_YIELDING)
JobManager.debug(job + " will yieldRule to " + unblocked);
}
if (likeThreadJob != null) {
job.setThread(null);
if (likeThreadJob.getRule() != null) {
getLockManager().removeLockThread(currentThread, likeThreadJob.getRule());
}
}
if ((job.getRule() != null) && !(job instanceof ThreadJob))
getLockManager().removeLockThread(currentThread, job.getRule());
}
}
if (DEBUG_YIELDING_DETAILED)
JobManager.debug(job + " is waiting for " + unblocked + " to transition from WAITING state");
waitForUnblocked(unblocked);
IProgressMonitor mon = monitorFor(monitor);
ProgressMonitorWrapper nonCanceling = new ProgressMonitorWrapper(mon) {
@Override
public boolean isCanceled() {
getWrappedProgressMonitor().isCanceled();
return false;
}
};
if (DEBUG_YIELDING)
JobManager.debug(job + " waiting to resume");
if (likeThreadJob == null) {
ThreadJob threadJob = new ThreadJob(job.getRule()) {
@Override
boolean isResumingAfterYield() {
return true;
}
};
threadJob.setRealJob((Job) job);
ThreadJob.joinRun(threadJob, nonCanceling);
synchronized (lock) {
changeState(threadJob, Job.NONE);
changeState(job, Job.RUNNING);
job.setThread(currentThread);
}
} else {
ThreadJob.joinRun(likeThreadJob, nonCanceling);
synchronized (lock) {
changeState(job, Job.RUNNING);
job.setThread(currentThread);
}
}
if (DEBUG_YIELDING) {
synchronized (lock) {
for (InternalJob other : running) {
if (other == job)
continue;
Assert.isTrue(!other.isConflicting(job), other + " conflicts and ran simultaneously with " + job);
}
}
JobManager.debug(job + " resumed");
}
if (unblocked instanceof ThreadJob && ((ThreadJob) unblocked).isResumingAfterYield()) {
return ((ThreadJob) unblocked).realJob;
}
return (Job) unblocked;
}
private void waitForUnblocked(InternalJob theJob) {
boolean interrupted = false;
synchronized (theJob.jobStateLock) {
if (theJob instanceof ThreadJob) {
while (((ThreadJob) theJob).isWaiting) {
try {
theJob.jobStateLock.wait();
} catch (InterruptedException e) {
interrupted = true;
}
}
} else {
while (theJob.internalGetState() == Job.WAITING) {
try {
theJob.jobStateLock.wait();
} catch (InterruptedException e) {
interrupted = true;
}
}
}
}
if (interrupted)
Thread.currentThread().interrupt();
}
private boolean shouldRun(Job job) {
Throwable t;
try {
return job.shouldRun();
} catch (Exception | LinkageError | AssertionError e) {
t = e;
}
RuntimeLog.log(new Status(IStatus.ERROR, JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, "Error invoking shouldRun() method on: " + job, t));
return false;
}
protected Job startJob(Worker worker) {
Job job = null;
while (true) {
job = nextJob();
if (job == null)
return null;
boolean shouldRun = shouldRun(job);
if (shouldRun)
jobListeners.aboutToRun(job);
boolean endJob = false;
synchronized (lock) {
JobGroup jobGroup = job.getJobGroup();
if (jobGroup != null && jobGroup.getState() == JobGroup.CANCELING)
shouldRun = false;
InternalJob internal = job;
synchronized (internal.jobStateLock) {
if (internal.internalGetState() == InternalJob.ABOUT_TO_RUN) {
if (shouldRun && !internal.isAboutToRunCanceled()) {
internal.setProgressMonitor(createMonitor(job));
internal.setThread(worker);
internal.internalSetState(Job.RUNNING);
internal.jobStateLock.notifyAll();
break;
}
endJob = true;
}
}
}
if (endJob) {
endJob(job, Status.CANCEL_STATUS, true);
continue;
}
}
jobListeners.running(job);
return job;
}
@Override
public final void suspend() {
synchronized (lock) {
suspended = true;
}
}
@Deprecated
@Override
public final void suspend(ISchedulingRule rule, IProgressMonitor monitor) {
Assert.isNotNull(rule);
implicitJobs.suspend(rule, monitorFor(monitor));
}
@Override
public void transferRule(ISchedulingRule rule, Thread destinationThread) {
implicitJobs.transfer(rule, destinationThread);
}
private void validateRule(ISchedulingRule rule) {
if (rule == null)
return;
if (rule instanceof MultiRule) {
ISchedulingRule[] children = ((MultiRule) rule).getChildren();
for (ISchedulingRule element : children) {
Assert.isLegal(element != rule);
validateRule(element);
}
}
Assert.isLegal(rule.contains(rule));
Assert.isLegal(!rule.contains(nullRule));
Assert.isLegal(rule.isConflicting(rule));
Assert.isLegal(!rule.isConflicting(nullRule));
}
protected void wakeUp(InternalJob job, long delay) {
Assert.isLegal(delay >= 0, "Scheduling delay is negative");
boolean scheduled;
synchronized (lock) {
if (job.getState() != Job.SLEEPING)
return;
scheduled = doSchedule(job, delay);
}
pool.jobQueued();
if (scheduled && delay == 0)
jobListeners.awake((Job) job);
}
@Override
public void wakeUp(Object family) {
for (InternalJob internalJob : select(family)) {
wakeUp(internalJob, 0L);
}
}
void endMonitoring(ThreadJob threadJob) {
synchronized (monitorStack) {
for (int i = monitorStack.size() - 1; i >= 0; i--) {
if (monitorStack.get(i)[0] == threadJob) {
monitorStack.remove(i);
monitorStack.notifyAll();
break;
}
}
}
}
void beginMonitoring(ThreadJob threadJob, IProgressMonitor monitor) {
synchronized (monitorStack) {
monitorStack.add(new Object[] {threadJob, monitor});
monitorStack.notifyAll();
}
}
private class JobGroupUpdater extends JobChangeAdapter {
Object jobManagerLock;
public JobGroupUpdater(Object jobManagerLock) {
this.jobManagerLock = jobManagerLock;
}
@Override
public void done(IJobChangeEvent event) {
InternalJob job = event.getJob();
InternalJobGroup jobGroup = job.getJobGroup();
if (jobGroup == null)
return;
IStatus jobResult = event.getResult();
boolean reschedule = ((JobChangeEvent) event).reschedule;
int jobGroupState;
int activeJobsCount;
int failedJobsCount;
int canceledJobsCount;
int seedJobsRemainingCount;
List<IStatus> jobResults = Collections.emptyList();
synchronized (jobManagerLock) {
jobGroupState = jobGroup.getState();
activeJobsCount = jobGroup.getActiveJobsCount();
failedJobsCount = jobGroup.getFailedJobsCount();
canceledJobsCount = jobGroup.getCanceledJobsCount();
seedJobsRemainingCount = jobGroup.getSeedJobsRemainingCount();
if (activeJobsCount == 0)
jobResults = jobGroup.getCompletedJobResults();
}
if (!reschedule && jobGroupState != JobGroup.NONE && activeJobsCount == 0 && (seedJobsRemainingCount <= 0 || jobGroupState == JobGroup.CANCELING)) {
MultiStatus jobGroupResult = jobGroup.computeGroupResult(jobResults);
Assert.isLegal(jobGroupResult != null, "The group result should not be null");
boolean isJobGroupCompleted = false;
synchronized (jobManagerLock) {
if (jobGroup.getState() != JobGroup.NONE && jobGroup.getActiveJobsCount() == 0) {
jobGroup.endJobGroup(jobGroupResult);
isJobGroupCompleted = true;
}
}
if (isJobGroupCompleted) {
((JobChangeEvent) event).jobGroupResult = jobGroupResult;
if (jobGroupResult.matches(IStatus.ERROR | IStatus.WARNING))
RuntimeLog.log(jobGroupResult);
}
return;
}
if (jobGroupState != JobGroup.CANCELING && jobGroup.shouldCancel(jobResult, failedJobsCount, canceledJobsCount))
cancel(jobGroup, true);
}
}
}