Bug 528248 - Project#build() greedily uses root scheduling rule
Introduce "relaxed" rule, like on workspace.build() for notifications,
and use project-specific build rule for project build.
Change-Id: Idcf46d3663fa355dce66e3ef618f3e4e72000c3b
Signed-off-by: Mickael Istria <mistria@redhat.com>
diff --git a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Project.java b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Project.java
index a0727b9..8a2a6bf 100644
--- a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Project.java
+++ b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/Project.java
@@ -26,6 +26,7 @@
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.content.IContentTypeMatcher;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
+import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.osgi.util.NLS;
public class Project extends Container implements IProject {
@@ -545,31 +546,35 @@
@Override
public void run(IProgressMonitor innerMonitor) throws CoreException {
innerMonitor = Policy.monitorFor(innerMonitor);
- final ISchedulingRule rule = workspace.getRoot();
+ final ISchedulingRule projectBuildRule = workspace.getBuildManager().getRule(config, trigger, builderName, args);
+ final boolean relaxed = Job.getJobManager().currentRule() == null && projectBuildRule != null && projectBuildRule.contains(workspace.getRoot());
+
+ // PRE + POST_BUILD, and the build itself are allowed to modify resources, so require the current thread's scheduling rule
+ // to either contain the WR or be null. Therefore, if not null, ensure it contains the WR rule...
+ final ISchedulingRule notificationsRule = relaxed ? null : workspace.getRuleFactory().buildRule();
try {
innerMonitor.beginTask("", Policy.totalWork); //$NON-NLS-1$
try {
- workspace.prepareOperation(rule, innerMonitor);
+ workspace.prepareOperation(notificationsRule, innerMonitor);
if (!shouldBuild())
return;
workspace.beginOperation(true);
workspace.aboutToBuild(Project.this, trigger);
} finally {
- workspace.endOperation(rule, false);
+ workspace.endOperation(notificationsRule, false);
}
- final ISchedulingRule buildRule = workspace.getBuildManager().getRule(config, trigger, builderName, args);
try {
IStatus result;
- workspace.prepareOperation(buildRule, innerMonitor);
+ workspace.prepareOperation(projectBuildRule, innerMonitor);
//don't open the tree eagerly because it will be wasted if no build occurs
workspace.beginOperation(false);
result = workspace.getBuildManager().build(config, trigger, builderName, args, Policy.subMonitorFor(innerMonitor, Policy.opWork));
if (!result.isOK())
throw new ResourceException(result);
} finally {
- workspace.endOperation(buildRule, false);
+ workspace.endOperation(projectBuildRule, false);
try {
- workspace.prepareOperation(rule, innerMonitor);
+ workspace.prepareOperation(notificationsRule, innerMonitor);
//don't open the tree eagerly because it will be wasted if no change occurs
workspace.beginOperation(false);
workspace.broadcastBuildEvent(Project.this, IResourceChangeEvent.POST_BUILD, trigger);
@@ -577,7 +582,7 @@
if (workspace.getElementTree().isImmutable())
workspace.newWorkingTree();
} finally {
- workspace.endOperation(rule, false);
+ workspace.endOperation(notificationsRule, false);
}
}
} finally {
diff --git a/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/IncrementalProjectBuilder.java b/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/IncrementalProjectBuilder.java
index ed5654d..d65ab73 100644
--- a/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/IncrementalProjectBuilder.java
+++ b/bundles/org.eclipse.core.resources/src/org/eclipse/core/resources/IncrementalProjectBuilder.java
@@ -410,9 +410,10 @@
* <strong>Notes:</strong>
* <ul>
* <li>
- * If the builder rule is non-<code>null</code> it must be "contained" in the workspace root rule.
- * I.e. {@link ISchedulingRule#contains(ISchedulingRule)} must return
- * <code>true</code> when invoked on the workspace root with the builder rule.
+ * The rule may be <i>relaxed</i> and in some cases let the builder be scheduled in
+ * parallel of any other operation using a rule based on {@link IResource}). To
+ * implement such <i>relaxed</i> rule for the builder, simply make the rule return
+ * <code>true</code> for <code>rule.contains(workspaceRoot)</code>
* </li>
* <li>
* The rule returned here may have no effect if the build is invoked within the
@@ -424,6 +425,7 @@
* The delta returned by {@link #getDelta(IProject)} for any project
* outside the scope of the builder's rule may not contain changes that occurred
* concurrently with the build.
+ * </li>
* </ul>
* </p>
* <p>
diff --git a/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/builders/ParallelBuildChainTest.java b/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/builders/ParallelBuildChainTest.java
index 35463cc..ae1e3e2 100644
--- a/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/builders/ParallelBuildChainTest.java
+++ b/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/builders/ParallelBuildChainTest.java
@@ -88,8 +88,8 @@
@Test
public void testIndividualProjectBuildsInParallelNoConflict() throws CoreException, OperationCanceledException, InterruptedException {
- int projectsCount = getWorkspace().getRoot().getProjects().length;
- JobGroup group = new JobGroup("Build Group", 5, projectsCount);
+ long duration = System.currentTimeMillis();
+ JobGroup group = new JobGroup("Build Group", 5, getWorkspace().getRoot().getProjects().length);
for (IProject project : getWorkspace().getRoot().getProjects()) {
Job job = new Job("Building " + project) {
@Override
@@ -106,13 +106,37 @@
job.schedule();
}
Assert.assertTrue("Timeout, most likely a deadlock", group.join(5000, getMonitor()));
+ duration = System.currentTimeMillis() - duration;
assertEquals(getWorkspace().getRoot().getProjects().length, TimerBuilder.getTotalBuilds());
+ assertTrue(TimerBuilder.getMaxSimultaneousBuilds() >= 3);
+ assertTrue(duration < 3000);
}
@Test
public void testIndividualProjectBuildsInParallelProjectScheduling() throws CoreException, OperationCanceledException, InterruptedException {
- setTimerBuilderSchedulingRuleForAllProjects(RuleType.CURRENT_PROJECT, getMonitor());
- testIndividualProjectBuildsInParallelNoConflict();
+ setTimerBuilderSchedulingRuleForAllProjects(RuleType.CURRENT_PROJECT_RELAXED, getMonitor());
+ long duration = System.currentTimeMillis();
+ JobGroup group = new JobGroup("Build Group", 5, getWorkspace().getRoot().getProjects().length);
+ for (IProject project : getWorkspace().getRoot().getProjects()) {
+ Job job = new Job("Building " + project) {
+ @Override
+ public IStatus run(IProgressMonitor monitor) {
+ try {
+ project.build(IncrementalProjectBuilder.FULL_BUILD, monitor);
+ return Status.OK_STATUS;
+ } catch (CoreException e) {
+ return new Status(IStatus.ERROR, "org.eclipse.core.tests.resources", e.getMessage(), e);
+ }
+ }
+ };
+ job.setJobGroup(group);
+ job.schedule();
+ }
+ Assert.assertTrue("Timeout, most likely a deadlock", group.join(5000, getMonitor()));
+ duration = System.currentTimeMillis() - duration;
+ assertEquals(getWorkspace().getRoot().getProjects().length, TimerBuilder.getTotalBuilds());
+ assertTrue(TimerBuilder.getMaxSimultaneousBuilds() >= 3);
+ assertTrue(duration < 3000);
}
}
diff --git a/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/builders/TimerBuilder.java b/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/builders/TimerBuilder.java
index 8b23c87..7ae0b2c 100644
--- a/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/builders/TimerBuilder.java
+++ b/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/internal/builders/TimerBuilder.java
@@ -11,8 +11,7 @@
package org.eclipse.core.tests.internal.builders;
import java.util.Map;
-import org.eclipse.core.resources.IProject;
-import org.eclipse.core.resources.IncrementalProjectBuilder;
+import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
@@ -30,20 +29,33 @@
private static int maxSimultaneousBuilds = 0;
public static enum RuleType {
- NO_CONFLICT, CURRENT_PROJECT, WORKSPACE_ROOT
+ NO_CONFLICT, CURRENT_PROJECT, WORKSPACE_ROOT, NO_CONFLICT_RELAXED, CURRENT_PROJECT_RELAXED;
}
- private final ISchedulingRule noConflictRule = new ISchedulingRule() {
- @Override
- public boolean isConflicting(ISchedulingRule rule) {
- return this == rule;
- }
+ final ISchedulingRule noConflictRule = new ISchedulingRule() {
+ @Override
+ public boolean isConflicting(ISchedulingRule rule) {
+ return this == rule;
+ }
- @Override
- public boolean contains(ISchedulingRule rule) {
- return this == rule;
- }
- };
+ @Override
+ public boolean contains(ISchedulingRule rule) {
+ return this == rule;
+ }
+ };
+
+ final ISchedulingRule relaxedProjetRule = new ISchedulingRule() {
+
+ @Override
+ public boolean isConflicting(ISchedulingRule rule) {
+ return this == rule;
+ }
+
+ @Override
+ public boolean contains(ISchedulingRule rule) {
+ return this == rule || ResourcesPlugin.getWorkspace().getRoot().contains(rule);
+ }
+ };
@Override
protected IProject[] build(int kind, Map<String, String> args, IProgressMonitor monitor) throws CoreException {
@@ -76,6 +88,8 @@
return getProject();
case WORKSPACE_ROOT :
return getProject().getWorkspace().getRoot();
+ case CURRENT_PROJECT_RELAXED :
+ return relaxedProjetRule;
}
}
return noConflictRule;