Copyright (c) 2003, 2012 IBM Corporation and others.
This program and the accompanying materials
are made available under the terms of the Eclipse Public License 2.0
which accompanies this distribution, and is available at
https://www.eclipse.org/legal/epl-2.0/
SPDX-License-Identifier: EPL-2.0
Contributors:
IBM - Initial API and implementation
/*******************************************************************************
* Copyright (c) 2003, 2012 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM - Initial API and implementation
*******************************************************************************/
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.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;
Implicit jobs are jobs that are running by virtue of a JobManager.begin/end
pair. They act like normal jobs, except they are tied to an arbitrary thread
of the client's choosing, and they can be nested.
@ThreadSafe
/**
* Implicit jobs are jobs that are running by virtue of a JobManager.begin/end
* pair. They act like normal jobs, except they are tied to an arbitrary thread
* of the client's choosing, and they can be nested.
* @ThreadSafe
*/
class ImplicitJobs {
Cached unused instance that can be reused
"this")
/**
* Cached unused instance that can be reused
* @GuardedBy("this")
*/
private ThreadJob jobCache = null;
protected JobManager manager;
Set of suspended scheduling rules.
"this")
/**
* Set of suspended scheduling rules.
* @GuardedBy("this")
*/
private final Set<ISchedulingRule> suspendedRules = new HashSet<>(20);
Maps (Thread->ThreadJob), threads to the currently running job for that
thread.
"this")
/**
* Maps (Thread->ThreadJob), threads to the currently running job for that
* thread.
* @GuardedBy("this")
*/
private final Map<Thread, ThreadJob> threadJobs = new HashMap<>(20);
ImplicitJobs(JobManager manager) {
this.manager = manager;
}
/* (Non-javadoc)
* @see IJobManager#beginRule
*/
void begin(ISchedulingRule rule, IProgressMonitor monitor, boolean suspend) {
if (JobManager.DEBUG_BEGIN_END)
JobManager.debug("Begin rule: " + rule); //$NON-NLS-1$
final Thread currentThread = Thread.currentThread();
ThreadJob threadJob;
synchronized (this) {
threadJob = threadJobs.get(currentThread);
if (threadJob != null) {
//nested rule, just push on stack and return
threadJob.push(rule);
return;
}
//no need to schedule a thread job for a null rule
if (rule == null)
return;
//create a thread job for this thread, use the rule from the real job if it has one
Job realJob = manager.currentJob();
if (realJob != null && realJob.getRule() != null)
threadJob = newThreadJob(realJob.getRule());
else {
threadJob = newThreadJob(rule);
threadJob.acquireRule = true;
}
//don't acquire rule if it is a suspended rule
if (isSuspended(rule))
threadJob.acquireRule = false;
//indicate if it is a system job to ensure isBlocking works correctly
threadJob.setRealJob(realJob);
threadJob.setThread(currentThread);
}
try {
threadJob.push(rule);
//join the thread job outside sync block
if (threadJob.acquireRule) {
//no need to re-acquire any locks because the thread did not wait to get this lock
if (manager.runNow(threadJob, false) == null)
manager.getLockManager().addLockThread(Thread.currentThread(), rule);
else
threadJob = ThreadJob.joinRun(threadJob, monitor);
}
} finally {
//remember this thread job - only do this
//after the rule is acquired because it is ok for this thread to acquire
//and release other rules while waiting.
synchronized (this) {
threadJobs.put(currentThread, threadJob);
if (suspend)
suspendedRules.add(rule);
}
}
}
/* (Non-javadoc)
* @see IJobManager#endRule
*/
synchronized void end(ISchedulingRule rule, boolean resume) {
if (JobManager.DEBUG_BEGIN_END)
JobManager.debug("End rule: " + rule); //$NON-NLS-1$
ThreadJob threadJob = threadJobs.get(Thread.currentThread());
if (threadJob == null)
Assert.isLegal(rule == null, "endRule without matching beginRule: " + rule); //$NON-NLS-1$
else if (threadJob.pop(rule)) {
endThreadJob(threadJob, resume);
}
}
Called when a worker thread has finished running a job. At this
point, the worker thread must not own any scheduling rules
Params: - lastJob – The last job to run in this thread
/**
* Called when a worker thread has finished running a job. At this
* point, the worker thread must not own any scheduling rules
* @param lastJob The last job to run in this thread
*/
void endJob(InternalJob lastJob) {
final Thread currentThread = Thread.currentThread();
IStatus error;
synchronized (this) {
ThreadJob threadJob = threadJobs.get(currentThread);
if (threadJob == null) {
if (lastJob.getRule() != null)
notifyWaitingThreadJobs(lastJob);
return;
}
String msg = "Worker thread ended job: " + lastJob + ", but still holds rule: " + threadJob; //$NON-NLS-1$ //$NON-NLS-2$
error = new Status(IStatus.ERROR, JobManager.PI_JOBS, 1, msg, null);
//end the thread job
endThreadJob(threadJob, false);
}
try {
RuntimeLog.log(error);
} catch (RuntimeException e) {
//failed to log, so print to console instead
System.err.println(error.getMessage());
}
}
"this")
/**
* @GuardedBy("this")
*/
private void endThreadJob(ThreadJob threadJob, boolean resume) {
Thread currentThread = Thread.currentThread();
//clean up when last rule scope exits
threadJobs.remove(currentThread);
ISchedulingRule rule = threadJob.getRule();
if (resume && rule != null)
suspendedRules.remove(rule);
//if this job had a rule, then we are essentially releasing a lock
//note it is safe to do this even if the acquire was aborted
if (threadJob.acquireRule) {
manager.getLockManager().removeLockThread(currentThread, rule);
notifyWaitingThreadJobs(threadJob);
}
//if the job was started, we need to notify job manager to end it
if (threadJob.isRunning())
manager.endJob(threadJob, Status.OK_STATUS, false);
recycle(threadJob);
}
Returns true if this rule has been suspended, and false otherwise.
"this")
/**
* Returns true if this rule has been suspended, and false otherwise.
* @GuardedBy("this")
*/
private boolean isSuspended(ISchedulingRule rule) {
if (suspendedRules.isEmpty())
return false;
for (ISchedulingRule iSchedulingRule : suspendedRules)
if (iSchedulingRule.contains(rule))
return true;
return false;
}
Returns a new or reused ThreadJob instance.
"this")
/**
* Returns a new or reused ThreadJob instance.
* @GuardedBy("this")
*/
private ThreadJob newThreadJob(ISchedulingRule rule) {
if (jobCache != null) {
ThreadJob job = jobCache;
// calling setRule will try to acquire JobManager.lock, breaking
// lock acquisition protocol. Since we managing this special job
// ourselves we can call internalSetRule
((InternalJob) job).internalSetRule(rule);
job.acquireRule = job.isRunning = false;
job.realJob = null;
jobCache = null;
return job;
}
return new ThreadJob(rule);
}
A job has just finished that was holding a scheduling rule, and the
scheduling rule is now free. Wake any blocked thread jobs so they can
compete for the newly freed lock
/**
* A job has just finished that was holding a scheduling rule, and the
* scheduling rule is now free. Wake any blocked thread jobs so they can
* compete for the newly freed lock
*/
void notifyWaitingThreadJobs(InternalJob job) {
synchronized (job.jobStateLock) {
job.jobStateLock.notifyAll();
}
}
Indicates that a thread job is no longer in use and can be reused.
"this")
/**
* Indicates that a thread job is no longer in use and can be reused.
* @GuardedBy("this")
*/
private void recycle(ThreadJob job) {
if (jobCache == null && job.recycle())
jobCache = job;
}
Implements IJobManager#resume(ISchedulingRule)
Params: - rule –
/**
* Implements IJobManager#resume(ISchedulingRule)
* @param rule
*/
void resume(ISchedulingRule rule) {
//resume happens as a consequence of freeing the last rule in the stack
end(rule, true);
if (JobManager.DEBUG_BEGIN_END)
JobManager.debug("Resume rule: " + rule); //$NON-NLS-1$
}
Implements IJobManager#suspend(ISchedulingRule, IProgressMonitor)
Params: - rule –
- monitor –
/**
* Implements IJobManager#suspend(ISchedulingRule, IProgressMonitor)
* @param rule
* @param monitor
*/
void suspend(ISchedulingRule rule, IProgressMonitor monitor) {
if (JobManager.DEBUG_BEGIN_END)
JobManager.debug("Suspend rule: " + rule); //$NON-NLS-1$
//the suspend job will be remembered once the rule is acquired
begin(rule, monitor, true);
}
Implements IJobManager#transferRule(ISchedulingRule, Thread)
/**
* Implements IJobManager#transferRule(ISchedulingRule, Thread)
*/
synchronized void transfer(ISchedulingRule rule, Thread destinationThread) {
//nothing to do for null
if (rule == null)
return;
final Thread currentThread = Thread.currentThread();
//nothing to do if transferring to the same thread
if (currentThread == destinationThread)
return;
//ensure destination thread doesn't already have a rule
ThreadJob target = threadJobs.get(destinationThread);
Assert.isLegal(target == null, "Transfer rule to job that already owns a rule"); //$NON-NLS-1$
//ensure calling thread owns the job being transferred
ThreadJob source = threadJobs.get(currentThread);
Assert.isNotNull(source, "transferRule without beginRule"); //$NON-NLS-1$
Assert.isLegal(source.getRule() == rule, "transferred rule " + rule + " does not match beginRule: " + source.getRule()); //$NON-NLS-1$ //$NON-NLS-2$ // transfer the thread job without ending it
source.setThread(destinationThread);
threadJobs.remove(currentThread);
threadJobs.put(destinationThread, source);
// transfer lock
if (source.acquireRule) {
manager.getLockManager().removeLockThread(currentThread, rule);
manager.getLockManager().addLockThread(destinationThread, rule);
}
// Wake up any blocked jobs (waiting within yield or joinRun) waiting on
// this rule
notifyWaitingThreadJobs(source);
}
synchronized void removeWaiting(ThreadJob threadJob) {
synchronized (((InternalJob) threadJob).jobStateLock) {
threadJob.isWaiting = false;
notifyWaitingThreadJobs(threadJob);
((InternalJob) threadJob).setWaitQueueStamp(InternalJob.T_NONE);
}
manager.dequeue(manager.waitingThreadJobs, threadJob);
}
synchronized void addWaiting(ThreadJob threadJob) {
synchronized (((InternalJob) threadJob).jobStateLock) {
threadJob.isWaiting = true;
notifyWaitingThreadJobs(threadJob);
((InternalJob) threadJob).setWaitQueueStamp(manager.waitQueueCounter.increment());
}
manager.enqueue(manager.waitingThreadJobs, threadJob);
}
synchronized ThreadJob getThreadJob(Thread thread) {
return threadJobs.get(thread);
}
}