Copyright (c) 2004, 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) 2004, 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 org.eclipse.core.internal.runtime.RuntimeLog; import org.eclipse.core.runtime.*; import org.eclipse.core.runtime.jobs.*;
Captures the implicit job state for a given thread.
/** * Captures the implicit job state for a given thread. */
class ThreadJob extends Job {
Set to true if this thread job is running in a thread that did not own a rule already. This means it needs to acquire the rule during beginRule, and must release the rule during endRule.
/** * Set to true if this thread job is running in a thread that did * not own a rule already. This means it needs to acquire the * rule during beginRule, and must release the rule during endRule. */
protected boolean acquireRule = false;
Indicates that this thread job did report to the progress manager that it will be blocked, and therefore when it begins it must be reported to the job manager when it is no longer blocked.
/** * Indicates that this thread job did report to the progress manager * that it will be blocked, and therefore when it begins it must * be reported to the job manager when it is no longer blocked. */
boolean isBlocked = false;
True if this ThreadJob has begun execution "this")
/** * True if this ThreadJob has begun execution * @GuardedBy("this") */
protected boolean isRunning = false;
Used for diagnosing mismatched begin/end pairs. This field is only used when in debug mode, to capture the stack trace of the last call to beginRule.
/** * Used for diagnosing mismatched begin/end pairs. This field * is only used when in debug mode, to capture the stack trace * of the last call to beginRule. */
private RuntimeException lastPush = null;
The actual job that is running in the thread that this ThreadJob represents. This will be null if this thread job is capturing a rule acquired outside of a job. "JobManager.implicitJobs")
/** * The actual job that is running in the thread that this * ThreadJob represents. This will be null if this thread * job is capturing a rule acquired outside of a job. * @GuardedBy("JobManager.implicitJobs") */
protected Job realJob;
The stack of rules that have been begun in this thread, but not yet ended. @GuardedBy("JobManager.implicitJobs")
/** * The stack of rules that have been begun in this thread, but not yet ended. * @GuardedBy("JobManager.implicitJobs") */
private ISchedulingRule[] ruleStack;
Rule stack pointer. INV: 0 <= top <= ruleStack.length "JobManager.implicitJobs")
/** * Rule stack pointer. * INV: {@code 0 <= top <= ruleStack.length} * @GuardedBy("JobManager.implicitJobs") */
private int top;
Waiting state for thread jobs is independent of the internal state. When this variable is true, this ThreadJob is waiting in joinRun() "jobStateLock")
/** * Waiting state for thread jobs is independent of the internal state. When * this variable is true, this ThreadJob is waiting in joinRun() * @GuardedBy("jobStateLock") */
boolean isWaiting; ThreadJob(ISchedulingRule rule) { super("Implicit Job"); //$NON-NLS-1$ setSystem(true); // calling setPriority will try to acquire JobManager.lock, breaking // lock acquisition protocol. Since we are constructing this thread, // we can call internalSetPriority ((InternalJob) this).internalSetPriority(Job.INTERACTIVE); ruleStack = new ISchedulingRule[2]; top = -1; ((InternalJob) this).internalSetRule(rule); } boolean isResumingAfterYield() { return false; }
An endRule was called that did not match the last beginRule in the stack. Report and log a detailed informational message.
Params:
  • rule – The rule that was popped "JobManager.implicitJobs")
/** * An endRule was called that did not match the last beginRule in * the stack. Report and log a detailed informational message. * @param rule The rule that was popped * @GuardedBy("JobManager.implicitJobs") */
private void illegalPop(ISchedulingRule rule) { StringBuilder buf = new StringBuilder("Attempted to endRule: "); //$NON-NLS-1$ buf.append(rule); if (top >= 0 && top < ruleStack.length) { buf.append(", does not match most recent begin: "); //$NON-NLS-1$ buf.append(ruleStack[top]); } else { if (top < 0) buf.append(", but there was no matching beginRule"); //$NON-NLS-1$ else buf.append(", but the rule stack was out of bounds: " + top); //$NON-NLS-1$ } buf.append(". See log for trace information if rule tracing is enabled."); //$NON-NLS-1$ String msg = buf.toString(); if (JobManager.DEBUG || JobManager.DEBUG_BEGIN_END) { System.out.println(msg); Throwable t = lastPush == null ? new IllegalArgumentException() : lastPush; IStatus error = new Status(IStatus.ERROR, JobManager.PI_JOBS, 1, msg, t); RuntimeLog.log(error); } Assert.isLegal(false, msg); }
Client has attempted to begin a rule that is not contained within the outer rule.
/** * Client has attempted to begin a rule that is not contained within * the outer rule. */
private void illegalPush(ISchedulingRule pushRule, ISchedulingRule baseRule) { StringBuilder buf = new StringBuilder("Attempted to beginRule: "); //$NON-NLS-1$ buf.append(pushRule); buf.append(", does not match outer scope rule: "); //$NON-NLS-1$ buf.append(baseRule); String msg = buf.toString(); if (JobManager.DEBUG) { System.out.println(msg); IStatus error = new Status(IStatus.ERROR, JobManager.PI_JOBS, 1, msg, new IllegalArgumentException()); RuntimeLog.log(error); } Assert.isLegal(false, msg); }
Returns true if the monitor is canceled, and false otherwise. Protects the caller from exception in the monitor implementation.
/** * Returns true if the monitor is canceled, and false otherwise. * Protects the caller from exception in the monitor implementation. */
static private boolean isCanceled(IProgressMonitor monitor) { try { return monitor.isCanceled(); } catch (RuntimeException e) { //logged message should not be translated IStatus status = new Status(IStatus.ERROR, JobManager.PI_JOBS, JobManager.PLUGIN_ERROR, "ThreadJob.isCanceled", e); //$NON-NLS-1$ RuntimeLog.log(status); } return false; }
Returns true if this thread job was scheduled and actually started running. "this")
/** * Returns true if this thread job was scheduled and actually started running. * @GuardedBy("this") */
synchronized boolean isRunning() { return isRunning; }
A reentrant method which will run given ThreadJob immediately if there are no existing jobs with conflicting rules, or block until the rule can be acquired.
  • If given job must block, the LockListener is given a chance to override.
  • If override is not granted, then this method will block until the rule is available.
  • If LockListener#canBlock returns true, then the monitor will not be periodically checked for cancellation. It will only be rechecked if this thread is interrupted.
  • If LockListener#canBlock returns false the monitor will be checked periodically for cancellation.
When a UI is present, it is recommended that the LockListener should not allow the UI thread to block without checking the monitor. This ensures that the UI remains responsive.
Params:
  • threadJob – - job to run or to wait until the rule can be acquired
  • monitor – - The IProgressMonitor used to report blocking status and cancellation.
Throws:
See Also:
Returns:given job, or the ThreadJob instance that was unblocked (due to transferRule) in the case of reentrant invocations of this method.
/** * A reentrant method which will run given <code>ThreadJob</code> immediately if there * are no existing jobs with conflicting rules, or block until the rule can be acquired. * <ul> * <li>If given job must block, the <code>LockListener</code> is given a chance to override. * <li>If override is not granted, then this method will block until the rule is available. * <li>If <code>LockListener#canBlock</code> returns <code>true</code>, then the <code>monitor</code> * <i>will not</i> be periodically checked for cancellation. It will only be rechecked if this * thread is interrupted. * <li>If <code>LockListener#canBlock</code> returns <code>false</code> the * <code>monitor</code> <i>will</i> be checked periodically for cancellation. * </ul> * When a UI is present, it is recommended that the <code>LockListener</code> * should not allow the UI thread to block without checking the <code>monitor</code>. This * ensures that the UI remains responsive. * * @see LockListener#aboutToWait(Thread) * @see LockListener#canBlock() * @see JobManager#transferRule(ISchedulingRule, Thread) * @return given job, or the <code>ThreadJob</code> instance that was * unblocked (due to transferRule) in the case of reentrant invocations of this method. * * @param threadJob - job to run or to wait until the rule can be acquired * @param monitor - The <code>IProgressMonitor</code> used to report blocking status and * cancellation. * * @throws OperationCanceledException if this job was canceled before it was started. */
static ThreadJob joinRun(ThreadJob threadJob, IProgressMonitor monitor) { if (isCanceled(monitor)) throw new OperationCanceledException(); // check if there is a blocking thread before waiting InternalJob blockingJob = manager.findBlockingJob(threadJob); Thread blocker = blockingJob == null ? null : blockingJob.getThread(); ThreadJob result; boolean interruptedDuringWaitForRun; try { // just return if lock listener decided to grant immediate access if (manager.getLockManager().aboutToWait(blocker)) return threadJob; result = waitForRun(threadJob, monitor, blockingJob); } finally { // We need to check for interruption unconditionally in order to // ensure we clear the thread's interrupted state. However, we only // throw an OperationCanceledException outside of the finally block // because we only want to throw that exception if we're not already // throwing some other exception here. interruptedDuringWaitForRun = Thread.interrupted(); manager.getLockManager().aboutToRelease(); } // During the call to waitForRun, we use the thread's interrupt flag to // trigger cancellation, so thread interruption at this time should // trigger an OCE. if (interruptedDuringWaitForRun) { throw new OperationCanceledException(); } return result; }
Waits until given ThreadJob "runs" (acquires the rule conflicting with given blockingJob or is canceled. While the given ThreadJob waits to acquire the rule, it is put to the JobManager.waitingThreadJobs queue.
Params:
  • threadJob – job which should wait until the rule blocked by the blockingJob can be acquired
  • monitor – the IProgressMonitor used to report blocking status and cancellation.
  • blockingJob – running or blocked job whose scheduling rule blocks (conflicts with) the scheduling rule of the given waiting job, or null
Returns:given job, or the ThreadJob instance that was unblocked (due to transferRule) in the case of reentrant invocations of this method.
/** * Waits until given {@code ThreadJob} "runs" (acquires the rule conflicting * with given {@code blockingJob} or is canceled. While the given * {@code ThreadJob} waits to acquire the rule, it is put to the * {@link JobManager#waitingThreadJobs} queue. * * @param threadJob * job which should wait until the rule blocked by the * {@code blockingJob} can be acquired * @param monitor * the {@code IProgressMonitor} used to report blocking status and * cancellation. * @param blockingJob * running or blocked job whose scheduling rule blocks (conflicts * with) the scheduling rule of the given waiting job, or * {@code null} * @return given job, or the <code>ThreadJob</code> instance that was unblocked * (due to transferRule) in the case of reentrant invocations of this * method. */
private static ThreadJob waitForRun(final ThreadJob threadJob, IProgressMonitor monitor, InternalJob blockingJob) { // Ask lock manager if it safe to block this thread final boolean canBlock = manager.getLockManager().canBlock(); ThreadJob result = threadJob; boolean interrupted = false; boolean waiting = false; boolean ruleCompatibleAndTransferred = false; try { waitStart(threadJob, monitor, blockingJob); manager.implicitJobs.addWaiting(threadJob); waiting = true; // If we're allowed to block this thread we won't be checking the monitor. In order // to respond to cancellation, register this monitor with the internal JobManager // worker thread. The worker thread will check for cancellation and will interrupt // this thread when the monitor is canceled. T if (canBlock) manager.beginMonitoring(threadJob, monitor); final Thread currentThread = Thread.currentThread(); // Ultimately, this loop will wait until the job "runs" (acquires the rule) // or is canceled. However, there are many ways for that to occur. // The exit conditions of this loop are: // 1) This job no longer conflicts with any other running job. // 2) The LockManager#aboutToWait allowed this thread to run. // This usually occurs during reentrant situations. i.e. This is a UI thread, // and a syncExec is performed from conflicting job/thread. // 3) A rule is transfered to this thread. This can be invoked programmatically, // or commonly in JFace via ModalContext (for wizards/etc). // 4) Monitor is canceled. while (true) { // monitor is foreign code so do not hold locks while calling into monitor if (interrupted || isCanceled(monitor)) // Condition #4. throw new OperationCanceledException(); // Try to run the job. If result is null, this job was allowed to run. // If the result is successful, atomically release thread from waiting // status. blockingJob = manager.runNow(threadJob, true); if (blockingJob == null) { // Condition #1. waiting = false; return threadJob; } Thread blocker = blockingJob.getThread(); // the rule could have been transferred to this thread while we were waiting if (blocker == currentThread && blockingJob instanceof ThreadJob) { // now we are just the nested acquire case result = (ThreadJob) blockingJob; // push expects a compatible rule, otherwise an exception will be thrown result.push(threadJob.getRule()); // rule was either accepted or both jobs have null rules ruleCompatibleAndTransferred = true; result.isBlocked = threadJob.isBlocked; // Condition #3. return result; } // just return if lock listener decided to grant immediate access if (manager.getLockManager().aboutToWait(blocker)) // Condition #2. return threadJob; // Notify the lock manager that we're about to block waiting for the scheduling rule manager.getLockManager().addLockWaitThread(currentThread, threadJob.getRule()); synchronized (blockingJob.jobStateLock) { try { // Wait until we are no longer definitely blocked (not running). // The actual exit conditions are listed above at the beginning of // this while loop int state = blockingJob.getState(); //ensure we don't wait forever if the blocker is waiting, because it might have yielded to me if (state == Job.RUNNING && canBlock) blockingJob.jobStateLock.wait(); else if (state != Job.NONE) blockingJob.jobStateLock.wait(250); } catch (InterruptedException e) { // This thread may be interrupted via two common scenarios. 1) If // the UISynchronizer is in use and this thread is a UI thread // and a syncExec() is performed, this thread will be interrupted // every 1000ms. 2) If this thread is allowed to be blocked and // the progress monitor was canceled, the internal JobManager // worker thread will interrupt this thread so cancellation can // be carried out. interrupted = true; } } // Going around the loop again. Ensure we're not marked as waiting for the thread // as external code is run via the monitor (Bug 262032). manager.getLockManager().removeLockWaitThread(currentThread, threadJob.getRule()); } } finally { boolean canStopWaiting; boolean updateLockState; if (threadJob != result) { // The rule which was blocking given threadJob could have been transferred to // this thread while we were waiting, and if our rule was contained in the // blocking rule, we can remove this job from the waiting queue canStopWaiting = ruleCompatibleAndTransferred; // lock sate should be unchanged, the thread is same as before updateLockState = false; } else { // job acquired blocked rule, so it can be removed from the waiting queue canStopWaiting = true; // update the lock state because our thread acquired the rule now updateLockState = true; } waitEnd(threadJob, updateLockState, monitor); if (canStopWaiting) { if (waiting) manager.implicitJobs.removeWaiting(threadJob); } if (canBlock) { // must unregister monitoring this job manager.endMonitoring(threadJob); } } }
Pops a rule. Returns true if it was the last rule for this thread job, and false otherwise. @GuardedBy("JobManager.implicitJobs")
/** * Pops a rule. Returns true if it was the last rule for this thread * job, and false otherwise. * @GuardedBy("JobManager.implicitJobs") */
boolean pop(ISchedulingRule rule) { if (top < 0 || ruleStack[top] != rule) illegalPop(rule); ruleStack[top--] = null; return top < 0; }
Adds a new scheduling rule to the stack of rules for this thread. Throws a runtime exception if the new rule is not compatible with the base scheduling rule for this thread. @GuardedBy("JobManager.implicitJobs")
/** * Adds a new scheduling rule to the stack of rules for this thread. Throws * a runtime exception if the new rule is not compatible with the base * scheduling rule for this thread. * @GuardedBy("JobManager.implicitJobs") */
void push(final ISchedulingRule rule) { final ISchedulingRule baseRule = getRule(); if (++top >= ruleStack.length) { ISchedulingRule[] newStack = new ISchedulingRule[ruleStack.length * 2]; System.arraycopy(ruleStack, 0, newStack, 0, ruleStack.length); ruleStack = newStack; } ruleStack[top] = rule; if (JobManager.DEBUG_BEGIN_END) lastPush = (RuntimeException) new RuntimeException().fillInStackTrace(); //check for containment last because we don't want to fail again on endRule if (baseRule != null && rule != null && !(baseRule.contains(rule) && baseRule.isConflicting(rule))) illegalPush(rule, baseRule); }
Reset all of this job's fields so it can be reused. Returns false if reuse is not possible @GuardedBy("JobManager.implicitJobs")
/** * Reset all of this job's fields so it can be reused. Returns false if * reuse is not possible * @GuardedBy("JobManager.implicitJobs") */
boolean recycle() { //don't recycle if still running for any reason if (getState() != Job.NONE) return false; //clear and reset all fields acquireRule = isRunning = isBlocked = false; realJob = null; setRule(null); setThread(null); if (ruleStack.length != 2) ruleStack = new ISchedulingRule[2]; else ruleStack[0] = ruleStack[1] = null; top = -1; return true; }
(non-Javadoc)
See Also:
  • run.run(IProgressMonitor)
/** (non-Javadoc) * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor) */
@Override public IStatus run(IProgressMonitor monitor) { synchronized (this) { isRunning = true; } return ASYNC_FINISH; }
Records the job that is actually running in this thread, if any
Params:
  • realJob – The running job @GuardedBy("JobManager.implicitJobs")
/** * Records the job that is actually running in this thread, if any * @param realJob The running job * @GuardedBy("JobManager.implicitJobs") */
void setRealJob(Job realJob) { this.realJob = realJob; }
Returns true if this job should cause a self-canceling job to cancel itself, and false otherwise. "JobManager.implicitJobs")
/** * Returns true if this job should cause a self-canceling job * to cancel itself, and false otherwise. * @GuardedBy("JobManager.implicitJobs") */
boolean shouldInterrupt() { return realJob == null ? true : !realJob.isSystem(); } /* (non-javadoc) * For debugging purposes only */ @Override public String toString() { StringBuilder buf = new StringBuilder("ThreadJob"); //$NON-NLS-1$ buf.append('(').append(realJob).append(',').append(getRuleStack()).append(')'); return buf.toString(); } String getRuleStack() { StringBuilder buf = new StringBuilder(); buf.append('['); for (int i = 0; i <= top && i < ruleStack.length; i++) { buf.append(ruleStack[i]); if (i != top) buf.append(','); } buf.append(']'); return buf.toString(); }
Reports that this thread was blocked, but is no longer blocked and is able to proceed.
Params:
  • monitor – The monitor to report unblocking to.
/** * Reports that this thread was blocked, but is no longer blocked and is able * to proceed. * @param monitor The monitor to report unblocking to. */
static private void waitEnd(ThreadJob threadJob, boolean updateLockManager, IProgressMonitor monitor) { if (updateLockManager) { final LockManager lockManager = manager.getLockManager(); final Thread currentThread = Thread.currentThread(); if (threadJob.isRunning()) { lockManager.addLockThread(currentThread, threadJob.getRule()); //need to re-acquire any locks that were suspended while this thread was blocked on the rule lockManager.resumeSuspendedLocks(currentThread); } else { //tell lock manager that this thread gave up waiting lockManager.removeLockWaitThread(currentThread, threadJob.getRule()); } } if (threadJob.isBlocked) { threadJob.isBlocked = false; manager.reportUnblocked(monitor); } }
Indicates the start of a wait on a scheduling rule. Report the blockage to the progress manager.
Params:
  • monitor – The monitor to report blocking to
  • blockingJob – The job that is blocking this thread, or null
/** * Indicates the start of a wait on a scheduling rule. Report the * blockage to the progress manager. * @param monitor The monitor to report blocking to * @param blockingJob The job that is blocking this thread, or <code>null</code> */
static private void waitStart(ThreadJob threadJob, IProgressMonitor monitor, InternalJob blockingJob) { threadJob.isBlocked = true; manager.reportBlocked(monitor, blockingJob); }
ThreadJobs are one-shot jobs, and they must ignore all attempts to schedule them.
/** * ThreadJobs are one-shot jobs, and they must ignore all attempts to schedule them. */
@Override public boolean shouldSchedule() { return false; } }