Package org.eclipse.core.internal.jobs

Source Code of org.eclipse.core.internal.jobs.ThreadJob

/*******************************************************************************
* Copyright (c) 2004, 2006 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* 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.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;

/**
* Captures the implicit job state for a given thread.
*/
class ThreadJob extends Job {
  /**
   * The notifier is a shared object used to wake up waiting thread jobs
   * when another job completes that is releasing a scheduling rule.
   */
  static final Object notifier = new Object();
 
  private final JobManager manager;
  /**
   * 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.
   */
  boolean isBlocked = false;

  /**
   * True if this ThreadJob has begun execution
   */
  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.
   */
  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.
   */
  protected Job realJob;
  /**
   * The stack of rules that have been begun in this thread, but not yet ended.
   */
  private ISchedulingRule[] ruleStack;
  /**
   * Rule stack pointer.
   */
  private int top;

  ThreadJob(JobManager manager, ISchedulingRule rule) {
    super("Implicit Job"); //$NON-NLS-1$
    this.manager = manager;
    setSystem(true);
    setPriority(Job.INTERACTIVE);
    ruleStack = new ISchedulingRule[2];
    top = -1;
    setRule(rule);
  }

  /**
   * 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
   */
  private void illegalPop(ISchedulingRule rule) {
    StringBuffer buf = new StringBuffer("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.
   */
  private void illegalPush(ISchedulingRule pushRule, ISchedulingRule baseRule) {
    StringBuffer buf = new StringBuffer("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.
   */
  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.
   */
  synchronized boolean isRunning() {
    return isRunning;
  }
 
  /**
   * Schedule the job and block the calling thread until the job starts running.
   * Returns the ThreadJob instance that was started.
   */
  ThreadJob joinRun(IProgressMonitor monitor) {
    if (isCanceled(monitor))
      throw new OperationCanceledException();
    //check if there is a blocking thread before waiting
    InternalJob blockingJob = manager.findBlockingJob(this);
    Thread blocker = blockingJob == null ? null : blockingJob.getThread();
    ThreadJob result = this;
    try {
      //just return if lock listener decided to grant immediate access
      if (manager.getLockManager().aboutToWait(blocker))
        return this;
      try {
        waitStart(monitor, blockingJob);
        final Thread currentThread = Thread.currentThread();
        while (true) {
          if (isCanceled(monitor))
            throw new OperationCanceledException();
          //try to run the job
          if (manager.runNow(this))
            return this;
          //update blocking job
          blockingJob = manager.findBlockingJob(this);
          //the rule could have been transferred to this thread while we were waiting
          blocker = blockingJob == null ? null : blockingJob.getThread();
          if (blocker == currentThread && blockingJob instanceof ThreadJob) {
            //now we are just the nested acquire case
            result = (ThreadJob)blockingJob;
            result.push(getRule());
            result.isBlocked = this.isBlocked;
            return result;
          }
          //just return if lock listener decided to grant immediate access
          if (manager.getLockManager().aboutToWait(blocker))
            return this;
          //must lock instance before calling wait
          synchronized (notifier) {
            try {
              notifier.wait(250);
            } catch (InterruptedException e) {
              //ignore
            }
          }
        }
      } finally {
        if (this == result)
          waitEnd(monitor);
      }
    } finally {
      manager.getLockManager().aboutToRelease();
    }
  }

  /**
   * Pops a rule. Returns true if it was the last rule for this thread
   * job, and false otherwise.
   */
  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.
   */
  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))
      illegalPush(rule, baseRule);
  }

  /**
   * Reset all of this job's fields so it can be reused.  Returns false if
   * reuse is not possible
   */
  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 org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor)
   */
  public IStatus run(IProgressMonitor monitor) {
    synchronized (this) {
      isRunning = true;
    }
    return ASYNC_FINISH;
  }

  /**
   * Records the job that is actually running in this thread, if any
   * @param realJob The running job
   */
  void setRealJob(Job realJob) {
    this.realJob = realJob;
  }

  /**
   * Returns true if this job should cause a self-canceling job
   * to cancel itself, and false otherwise.
   */
  boolean shouldInterrupt() {
    return realJob == null ? true : !realJob.isSystem();
  }

  /* (non-javadoc)
   * For debugging purposes only
   */
  public String toString() {
    StringBuffer buf = new StringBuffer("ThreadJob"); //$NON-NLS-1$
    buf.append('(').append(realJob).append(',').append('[');
    for (int i = 0; i <= top && i < ruleStack.length; i++)
      buf.append(ruleStack[i]).append(',');
    buf.append(']').append(')');
    return buf.toString();
  }

  /**
   * Reports that this thread was blocked, but is no longer blocked and is able
   * to proceed.
   * @param monitor The monitor to report unblocking to.
   */
  private void waitEnd(IProgressMonitor monitor) {
    final LockManager lockManager = manager.getLockManager();
    final Thread currentThread = Thread.currentThread();
    if (isRunning()) {
      lockManager.addLockThread(currentThread, 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, getRule());
    }
  }

  /**
   * Indicates the start of a wait on a scheduling rule. Report the
   * blockage to the progress manager and update the lock manager.
   * @param monitor The monitor to report blocking to
   * @param blockingJob The job that is blocking this thread, or <code>null</code>
   */
  private void waitStart(IProgressMonitor monitor, InternalJob blockingJob) {
    manager.getLockManager().addLockWaitThread(Thread.currentThread(), getRule());
    isBlocked = true;
    manager.reportBlocked(monitor, blockingJob);
  }
}
TOP

Related Classes of org.eclipse.core.internal.jobs.ThreadJob

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.