Copyright (c) 2014 Google Inc 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:
Thirumala Reddy Mutchukota - initial API and implementation
/*******************************************************************************
* Copyright (c) 2014 Google Inc 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:
* Thirumala Reddy Mutchukota - initial API and implementation
*******************************************************************************/
package org.eclipse.core.internal.jobs;
import java.util.*;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobGroup;
Internal implementation class for job groups.
@noextend This class is not intended to be extended by clients. All job groups
must be subclasses of the API org.eclipse.core.runtime.jobs.JobGroup
class.
/**
* Internal implementation class for job groups.
*
* @noextend This class is not intended to be extended by clients. All job groups
* must be subclasses of the API <code>org.eclipse.core.runtime.jobs.JobGroup</code> class.
*/
public class InternalJobGroup {
The maximum amount of time to wait on jobGroupStateLock
. Determines how often the progress monitor is checked for cancellation. /**
* The maximum amount of time to wait on {@link #jobGroupStateLock}.
* Determines how often the progress monitor is checked for cancellation.
*/
private static final long MAX_WAIT_INTERVAL = 100;
This lock will be held while performing state changes on this job group. It is
also used as a notifier to wake up the threads waiting for this job group to complete.
External code is never called while holding this lock, thus removing the hold and wait
condition necessary for deadlock.
"itself")
/**
* This lock will be held while performing state changes on this job group. It is
* also used as a notifier to wake up the threads waiting for this job group to complete.
*
* External code is never called while holding this lock, thus removing the hold and wait
* condition necessary for deadlock.
*
* @GuardedBy("itself")
*/
private final Object jobGroupStateLock = new Object();
private static final JobManager manager = JobManager.getInstance();
private final String name;
private final int maxThreads;
private volatile int state = JobGroup.NONE;
private volatile MultiStatus result;
private final Set<InternalJob> runningJobs = new HashSet<>();
private final Set<InternalJob> otherActiveJobs = new HashSet<>();
private final List<IStatus> results = new ArrayList<>();
private boolean cancelingDueToError;
private int failedJobsCount;
private int canceledJobsCount;
private int seedJobsCount;
private int seedJobsRemainingCount;
protected InternalJobGroup(String name, int maxThreads, int seedJobsCount) {
Assert.isNotNull(name);
Assert.isLegal(maxThreads >= 0);
Assert.isLegal(seedJobsCount >= 0);
this.name = name;
this.maxThreads = maxThreads;
this.seedJobsCount = seedJobsCount;
this.seedJobsRemainingCount = seedJobsCount;
}
protected String getName() {
return name;
}
protected int getMaxThreads() {
return maxThreads;
}
protected MultiStatus getResult() {
return result;
}
protected int getState() {
return state;
}
protected List<Job> getActiveJobs() {
return manager.find(this);
}
protected void cancel() {
manager.cancel(this);
}
protected boolean join(long timeout, IProgressMonitor monitor) throws InterruptedException, OperationCanceledException {
return manager.join(this, timeout, monitor);
}
Called by the JobManager when the state of a job belonging to this group has changed.
Must be called from JobManager#changeState
Params: - job – a job belonging to this group
- oldState – the old state of the job
- newState – the new state of the job
"JobManager.lock")
/**
* Called by the JobManager when the state of a job belonging to this group has changed.
* Must be called from JobManager#changeState
*
* @param job a job belonging to this group
* @param oldState the old state of the job
* @param newState the new state of the job
* @GuardedBy("JobManager.lock")
*/
final void jobStateChanged(InternalJob job, int oldState, int newState) {
switch (oldState) {
case Job.NONE :
break;
case Job.SLEEPING :
case Job.WAITING :
otherActiveJobs.remove(job);
break;
case Job.RUNNING :
runningJobs.remove(job);
break;
default :
Assert.isLegal(false, "Invalid job state: " + job + ", state: " + oldState); //$NON-NLS-1$//$NON-NLS-2$
break;
}
switch (newState) {
case Job.NONE :
break;
case Job.SLEEPING :
case Job.WAITING :
otherActiveJobs.add(job);
break;
case Job.RUNNING :
runningJobs.add(job);
break;
default :
Assert.isLegal(false, "Invalid job state: " + job + ", state: " + newState); //$NON-NLS-1$//$NON-NLS-2$
break;
}
/*
* We can determine if the job that is being scheduled is one of its job group's "seed"
* jobs by retrieving the currently running job for this thread. If there is a running
* job on this thread or that job's job group is the same as this job's job group, then
* this job is being scheduled in response to discovering more work, so it is not one
* of its group's seed jobs, otherwise it is.
*/
if (job.internalGetState() == InternalJob.ABOUT_TO_SCHEDULE && getGroupOfCurrentlyRunningJob() != job.getJobGroup()) {
seedJobsRemainingCount--;
}
if (oldState == Job.RUNNING && newState == Job.NONE) {
IStatus jobResult = job.getResult();
Assert.isLegal(jobResult != null);
if (cancelingDueToError && jobResult.getSeverity() == IStatus.CANCEL)
return;
results.add(jobResult);
int jobResultSeverity = jobResult.getSeverity();
if (jobResultSeverity == IStatus.ERROR) {
failedJobsCount++;
} else if (jobResultSeverity == IStatus.CANCEL) {
canceledJobsCount++;
}
}
//make sure this job group is running
if (getState() == JobGroup.NONE && getActiveJobsCount() > 0) {
synchronized (jobGroupStateLock) {
state = JobGroup.ACTIVE;
jobGroupStateLock.notifyAll();
}
}
}
Returns the job group of the job currently running in this thread. Will return null if
either this thread is not running a job or the job is not part of a job group.
/**
* Returns the job group of the job currently running in this thread. Will return null if
* either this thread is not running a job or the job is not part of a job group.
*/
private JobGroup getGroupOfCurrentlyRunningJob() {
Job job = manager.currentJob();
return job == null ? null : job.getJobGroup();
}
Called by the JobManager to signify that the group canceling reason is changed.
Must be called from JobManager#cancel(InternalJobGroup).
Params: - cancelDueToError –
true
if the group is getting canceled because
the shouldCancel(IStatus, int, int)
method returned true
,
false
otherwise.
"JobManager.lock")
/**
* Called by the JobManager to signify that the group canceling reason is changed.
* Must be called from JobManager#cancel(InternalJobGroup).
*
* @param cancelDueToError <code>true</code> if the group is getting canceled because
* the <code>shouldCancel(IStatus, int, int)</code> method returned <code>true</code>,
* <code>false</code> otherwise.
* @GuardedBy("JobManager.lock")
*/
final void updateCancelingReason(boolean cancelDueToError) {
cancelingDueToError = cancelDueToError;
if (!cancelDueToError) {
// add a dummy cancel status to the results to make sure the combined status
// will be of severity CANCEL.
results.add(Status.CANCEL_STATUS);
}
}
Called by the JobManager to signify that the group is getting canceled.
Must be called from JobManager#cancel(InternalJobGroup).
Params: - cancelDueToError –
true
if the group is getting canceled because
the shouldCancel(IStatus, int, int)
method returned true
,
false
otherwise.
"JobManager.lock")
"jobGroupStateLock")
/**
* Called by the JobManager to signify that the group is getting canceled.
* Must be called from JobManager#cancel(InternalJobGroup).
*
* @param cancelDueToError <code>true</code> if the group is getting canceled because
* the <code>shouldCancel(IStatus, int, int)</code> method returned <code>true</code>,
* <code>false</code> otherwise.
* @GuardedBy("JobManager.lock")
* @GuardedBy("jobGroupStateLock")
*/
final void cancelAndNotify(boolean cancelDueToError) {
synchronized (jobGroupStateLock) {
state = JobGroup.CANCELING;
updateCancelingReason(cancelDueToError);
jobGroupStateLock.notifyAll();
}
for (Job job : internalGetActiveJobs())
job.cancel();
}
Called by the JobManager to notify the group when the last job belonging
to the group has finished execution. Must be called from JobManager#updateJobGroup.
Params: - groupResult – the combined status of the job group
"JobManager.lock")
"jobGroupStateLock")
/**
* Called by the JobManager to notify the group when the last job belonging
* to the group has finished execution. Must be called from JobManager#updateJobGroup.
*
* @param groupResult the combined status of the job group
* @GuardedBy("JobManager.lock")
* @GuardedBy("jobGroupStateLock")
*/
final void endJobGroup(MultiStatus groupResult) {
synchronized (jobGroupStateLock) {
if (seedJobsRemainingCount > 0 && !groupResult.matches(IStatus.CANCEL))
throw new IllegalStateException("Invalid initial jobs remaining count"); //$NON-NLS-1$
state = JobGroup.NONE;
result = groupResult;
results.clear();
cancelingDueToError = false;
failedJobsCount = 0;
canceledJobsCount = 0;
seedJobsRemainingCount = seedJobsCount;
jobGroupStateLock.notifyAll();
}
}
final List<Job> internalGetActiveJobs() {
List<Job> activeJobs = new ArrayList<>(runningJobs.size() + otherActiveJobs.size());
for (InternalJob job : runningJobs)
activeJobs.add((Job) job);
for (InternalJob job : otherActiveJobs)
activeJobs.add((Job) job);
return activeJobs;
}
Called by the JobManager when updating the job group status. Prevents the job group
from entering the NONE state prematurely, which can happen if all scheduled jobs
run to completion while the master thread still has more "seed" jobs to schedule.
Returns: the count of initial jobs remaining to be scheduled
/**
* Called by the JobManager when updating the job group status. Prevents the job group
* from entering the NONE state prematurely, which can happen if all scheduled jobs
* run to completion while the master thread still has more "seed" jobs to schedule.
*
* @return the count of initial jobs remaining to be scheduled
*/
final int getSeedJobsRemainingCount() {
return seedJobsRemainingCount;
}
final int getActiveJobsCount() {
return runningJobs.size() + otherActiveJobs.size();
}
final int getRunningJobsCount() {
return runningJobs.size();
}
final int getFailedJobsCount() {
return failedJobsCount;
}
final int getCanceledJobsCount() {
return canceledJobsCount;
}
final List<IStatus> getCompletedJobResults() {
return new ArrayList<>(results);
}
protected boolean shouldCancel(IStatus lastCompletedJobResult, int numberOfFailedJobs, int numberOfCanceledJobs) {
return numberOfFailedJobs > 0;
}
protected MultiStatus computeGroupResult(List<IStatus> jobResults) {
List<IStatus> importantResults = new ArrayList<>();
for (IStatus jobResult : jobResults) {
if (jobResult.getSeverity() != IStatus.OK)
importantResults.add(jobResult);
}
if (importantResults.isEmpty())
return new MultiStatus("org.eclipse.core.jobs", 0, name, null); //$NON-NLS-1$
String pluginId = importantResults.get(0).getPlugin();
IStatus[] groupResults = importantResults.toArray(new IStatus[importantResults.size()]);
return new MultiStatus(pluginId, 0, groupResults, name, null);
}
Implementation of joining a job group.
Params: - remainingTime –
Returns: true
if the join completed, and false otherwise (still waiting).
/**
* Implementation of joining a job group.
* @param remainingTime
* @return <code>true</code> if the join completed, and false otherwise (still waiting).
*/
boolean doJoin(long remainingTime) throws InterruptedException {
synchronized (jobGroupStateLock) {
if (getState() == JobGroup.NONE)
return true;
// If remaining time is greater than MAX_WAIT_INTERVAL, sleep only for
// MAX_WAIT_INTERVAL instead to be more responsive to monitor cancellation.
long sleepTime = remainingTime != 0 && remainingTime <= MAX_WAIT_INTERVAL ? remainingTime : MAX_WAIT_INTERVAL;
jobGroupStateLock.wait(sleepTime);
return getState() == JobGroup.NONE;
}
}
}