diff --git a/core/src/main/java/hudson/model/AbstractProject.java b/core/src/main/java/hudson/model/AbstractProject.java
index 8b46d0cd0a7e..ad7f2f9f7830 100644
--- a/core/src/main/java/hudson/model/AbstractProject.java
+++ b/core/src/main/java/hudson/model/AbstractProject.java
@@ -1100,16 +1100,27 @@ public CauseOfBlockage getCauseOfBlockage() {
/**
* Returns the project if any of the downstream project is either
- * building, waiting, pending or buildable.
+ * building, or queued and not blocked by an upstream/downstream project build.
*
* This means eventually there will be an automatic triggering of
* the given project (provided that all builds went smoothly.)
*/
public AbstractProject getBuildingDownstream() {
- Set unblockedTasks = Jenkins.get().getQueue().getUnblockedTasks();
+ // Unblocked downstream tasks must block this project.
+ Set tasks = Jenkins.get().getQueue().getUnblockedTasks();
+ // Blocked downstream tasks must block this project.
+ // Projects blocked by upstream or downstream builds
+ // are ignored to break deadlocks.
+ for (Queue.Item item : Jenkins.get().getQueue().getBlockedItems()) {
+ if (item.getCauseOfBlockage() instanceof AbstractProject.BecauseOfUpstreamBuildInProgress ||
+ item.getCauseOfBlockage() instanceof AbstractProject.BecauseOfDownstreamBuildInProgress) {
+ continue;
+ }
+ tasks.add(item.task);
+ }
for (AbstractProject tup : getTransitiveDownstreamProjects()) {
- if (tup != this && (tup.isBuilding() || unblockedTasks.contains(tup)))
+ if (tup != this && (tup.isBuilding() || tasks.contains(tup)))
return tup;
}
return null;
@@ -1117,16 +1128,27 @@ public AbstractProject getBuildingDownstream() {
/**
* Returns the project if any of the upstream project is either
- * building or is in the queue.
+ * building, or queued and not blocked by an upstream/downstream project build.
*
* This means eventually there will be an automatic triggering of
* the given project (provided that all builds went smoothly.)
*/
public AbstractProject getBuildingUpstream() {
- Set unblockedTasks = Jenkins.get().getQueue().getUnblockedTasks();
+ // Unblocked upstream tasks must block this project.
+ Set tasks = Jenkins.get().getQueue().getUnblockedTasks();
+ // Blocked upstream tasks must block this project.
+ // Projects blocked by upstream or downstream builds
+ // are ignored to break deadlocks.
+ for (Queue.Item item : Jenkins.get().getQueue().getBlockedItems()) {
+ if (item.getCauseOfBlockage() instanceof AbstractProject.BecauseOfUpstreamBuildInProgress ||
+ item.getCauseOfBlockage() instanceof AbstractProject.BecauseOfDownstreamBuildInProgress) {
+ continue;
+ }
+ tasks.add(item.task);
+ }
for (AbstractProject tup : getTransitiveUpstreamProjects()) {
- if (tup != this && (tup.isBuilding() || unblockedTasks.contains(tup)))
+ if (tup != this && (tup.isBuilding() || tasks.contains(tup)))
return tup;
}
return null;
diff --git a/test/src/test/java/hudson/model/QueueTest.java b/test/src/test/java/hudson/model/QueueTest.java
index 85ba24bbc4df..e38f9d92dcc2 100644
--- a/test/src/test/java/hudson/model/QueueTest.java
+++ b/test/src/test/java/hudson/model/QueueTest.java
@@ -568,6 +568,111 @@ public void upstreamDownstreamCycle() throws Exception {
assertThat("The cycle should have been defanged and chain3 executed", queue.getItem(chain3), nullValue());
}
+
+ @TestExtension({"upstreamProjectsInQueueBlock", "downstreamProjectsInQueueBlock"})
+ public static class BlockingQueueTaskDispatcher extends QueueTaskDispatcher {
+
+ public static final String NAME_OF_BLOCKED_PROJECT = "blocked project";
+
+ @Override
+ public CauseOfBlockage canRun(hudson.model.Queue.Item item) {
+ if (item.task.getOwnerTask().getDisplayName().equals(NAME_OF_BLOCKED_PROJECT)) {
+ return new CauseOfBlockage() {
+
+ @Override
+ public String getShortDescription() {
+ return NAME_OF_BLOCKED_PROJECT + " is permanently blocked.";
+ }
+
+ };
+ }
+ return super.canRun(item);
+ }
+
+ }
+
+ private void waitUntilWaitingListIsEmpty(Queue q) throws InterruptedException {
+ boolean waitingItemsPresent = true;
+ while (waitingItemsPresent) {
+ waitingItemsPresent = false;
+ for (Queue.Item i : q.getItems()) {
+ if (i instanceof WaitingItem) {
+ waitingItemsPresent = true;
+ break;
+ }
+ }
+ Thread.sleep(1000);
+ }
+ }
+
+ @Issue("JENKINS-68780")
+ @Test
+ public void upstreamProjectsInQueueBlock() throws Exception {
+
+ FreeStyleProject a = r.createFreeStyleProject(BlockingQueueTaskDispatcher.NAME_OF_BLOCKED_PROJECT);
+ FreeStyleProject b = r.createFreeStyleProject();
+ a.getPublishersList().add(new BuildTrigger(b.getName(), true));
+ b.setBlockBuildWhenUpstreamBuilding(true);
+
+ r.jenkins.rebuildDependencyGraph();
+
+ a.scheduleBuild(0, new UserIdCause());
+
+ Queue q = r.jenkins.getQueue();
+
+ waitUntilWaitingListIsEmpty(q);
+
+ b.scheduleBuild(0, new UserIdCause());
+
+ waitUntilWaitingListIsEmpty(q);
+
+ // This call is necessary because the queue blocks projects
+ // at first only temporarily. By calling the maintain method
+ // all temporarily blocked projects either become buildable or
+ // become permanently blocked
+ q.scheduleMaintenance().get();
+
+ assertEquals("Queue should contain two blocked items but didn't.", 2, q.getBlockedItems().size());
+
+ //Ensure orderly shutdown
+ q.clear();
+ r.waitUntilNoActivity();
+ }
+
+ @Issue("JENKINS-68780")
+ @Test
+ public void downstreamProjectsInQueueBlock() throws Exception {
+
+ FreeStyleProject a = r.createFreeStyleProject();
+ FreeStyleProject b = r.createFreeStyleProject(BlockingQueueTaskDispatcher.NAME_OF_BLOCKED_PROJECT);
+ a.getPublishersList().add(new BuildTrigger(b.getName(), true));
+ a.setBlockBuildWhenDownstreamBuilding(true);
+
+ r.jenkins.rebuildDependencyGraph();
+
+ b.scheduleBuild(0, new UserIdCause());
+
+ Queue q = r.jenkins.getQueue();
+
+ waitUntilWaitingListIsEmpty(q);
+
+ a.scheduleBuild(0, new UserIdCause());
+
+ waitUntilWaitingListIsEmpty(q);
+
+ // This call is necessary because the queue blocks projects
+ // at first only temporarily. By calling the maintain method
+ // all temporarily blocked projects either become buildable or
+ // become permanently blocked
+ q.scheduleMaintenance().get();
+
+ assertEquals("Queue should contain two blocked items but didn't.", 2, q.getBlockedItems().size());
+
+ //Ensure orderly shutdown
+ q.clear();
+ r.waitUntilNoActivity();
+ }
+
public static class TestFlyweightTask extends TestTask implements Queue.FlyweightTask {
Executor exec;
private final Label assignedLabel;
diff --git a/war/src/main/webapp/help/project-config/block-downstream-building.html b/war/src/main/webapp/help/project-config/block-downstream-building.html
index 441d6c51b20b..9b1cfdae21bf 100644
--- a/war/src/main/webapp/help/project-config/block-downstream-building.html
+++ b/war/src/main/webapp/help/project-config/block-downstream-building.html
@@ -1,3 +1,6 @@
When this option is checked, Jenkins will prevent the project from building when a child of this project is in the queue, or building. The children include the direct as well as the transitive children.
+
+ However, if projects are circular dependent on each other, Jenkins will break the cycle by starting with the oldest requested build from the cycle.
+
\ No newline at end of file
diff --git a/war/src/main/webapp/help/project-config/block-downstream-building_de.html b/war/src/main/webapp/help/project-config/block-downstream-building_de.html
new file mode 100644
index 000000000000..0df022fb3280
--- /dev/null
+++ b/war/src/main/webapp/help/project-config/block-downstream-building_de.html
@@ -0,0 +1,8 @@
+
+ Wenn angewählt, verhindert das Bauen des Projekts, solange sich ein nachgelagertes Projekt in
+ der Build-Warteschlange befindet oder gerade gebaut wird. Dabei werden sowohl
+ direkte als auch transitive Abhängigkeiten berücksichtigt.
+
+
+ Jedoch werden Verklemmung von zirkulär abhängigen Projekten aufgelöst, indem der älteste angeforderte Build begonnen wird.
+
\ No newline at end of file
diff --git a/war/src/main/webapp/help/project-config/block-upstream-building.html b/war/src/main/webapp/help/project-config/block-upstream-building.html
index cee2b6248169..2e39c7410209 100644
--- a/war/src/main/webapp/help/project-config/block-upstream-building.html
+++ b/war/src/main/webapp/help/project-config/block-upstream-building.html
@@ -1,3 +1,6 @@
When this option is checked, Jenkins will prevent the project from building when a dependency of this project is in the queue, or building. The dependencies include the direct as well as the transitive dependencies.
+
+ However, if projects are circular dependent on each other, Jenkins will break the cycle by starting with the oldest requested build from the cycle.
+
diff --git a/war/src/main/webapp/help/project-config/block-upstream-building_de.html b/war/src/main/webapp/help/project-config/block-upstream-building_de.html
index 72ca4bae984b..25fead38c291 100644
--- a/war/src/main/webapp/help/project-config/block-upstream-building_de.html
+++ b/war/src/main/webapp/help/project-config/block-upstream-building_de.html
@@ -3,3 +3,6 @@
der Build-Warteschlange befindet oder gerade gebaut wird. Dabei werden sowohl
direkte als auch transitive Abhängigkeiten berücksichtigt.
+
+ Jedoch werden Verklemmung von zirkulär abhängigen Projekten aufgelöst, indem der älteste angeforderte Build begonnen wird.
+
\ No newline at end of file