| /******************************************************************************* |
| * Copyright (c) 2003, 2012 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 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 |
| */ |
| class ImplicitJobs { |
| |
| /** |
| * Cached unused instance that can be reused |
| * @GuardedBy("this") |
| */ |
| private ThreadJob jobCache = null; |
| protected JobManager manager; |
| |
| /** |
| * Set of suspended scheduling rules. |
| * @GuardedBy("this") |
| */ |
| private final Set suspendedRules = new HashSet(20); |
| |
| /** |
| * Maps (Thread->ThreadJob), threads to the currently running job for that |
| * thread. |
| * @GuardedBy("this") |
| */ |
| private final Map 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 = (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 = (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 |
| * @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 = (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()); |
| } |
| } |
| |
| /** |
| * @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. |
| * @GuardedBy("this") |
| */ |
| private boolean isSuspended(ISchedulingRule rule) { |
| if (suspendedRules.size() == 0) |
| return false; |
| for (Iterator it = suspendedRules.iterator(); it.hasNext();) |
| if (((ISchedulingRule) it.next()).contains(rule)) |
| return true; |
| return false; |
| } |
| |
| /** |
| * 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 |
| */ |
| void notifyWaitingThreadJobs(InternalJob job) { |
| synchronized (job.jobStateLock) { |
| job.jobStateLock.notifyAll(); |
| } |
| } |
| |
| /** |
| * 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) |
| * @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) |
| * @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) |
| */ |
| 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 = (ThreadJob) 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 = (ThreadJob) 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 (ThreadJob) threadJobs.get(thread); |
| } |
| |
| } |