Skip to content

Commit

Permalink
[JENKINS-68780] Blocked upstream projects in the queue block downstre…
Browse files Browse the repository at this point in the history
…am projects (jenkinsci#6675)
  • Loading branch information
Si-So authored Jul 19, 2022
1 parent 884ac79 commit 237b9ca
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 6 deletions.
34 changes: 28 additions & 6 deletions core/src/main/java/hudson/model/AbstractProject.java
Original file line number Diff line number Diff line change
Expand Up @@ -1100,33 +1100,55 @@ 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.
* <p>
* This means eventually there will be an automatic triggering of
* the given project (provided that all builds went smoothly.)
*/
public AbstractProject getBuildingDownstream() {
Set<Task> unblockedTasks = Jenkins.get().getQueue().getUnblockedTasks();
// Unblocked downstream tasks must block this project.
Set<Task> 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;
}

/**
* 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.
* <p>
* This means eventually there will be an automatic triggering of
* the given project (provided that all builds went smoothly.)
*/
public AbstractProject getBuildingUpstream() {
Set<Task> unblockedTasks = Jenkins.get().getQueue().getUnblockedTasks();
// Unblocked upstream tasks must block this project.
Set<Task> 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;
Expand Down
105 changes: 105 additions & 0 deletions test/src/test/java/hudson/model/QueueTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
<div>
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.
</div>
<div>
However, if projects are circular dependent on each other, Jenkins will break the cycle by starting with the oldest requested build from the cycle.
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<div>
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.
</div>
<div>
Jedoch werden Verklemmung von zirkulär abhängigen Projekten aufgelöst, indem der älteste angeforderte Build begonnen wird.
</div>
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
<div>
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.
</div>
<div>
However, if projects are circular dependent on each other, Jenkins will break the cycle by starting with the oldest requested build from the cycle.
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@
der Build-Warteschlange befindet oder gerade gebaut wird. Dabei werden sowohl
direkte als auch transitive Abhängigkeiten berücksichtigt.
</div>
<div>
Jedoch werden Verklemmung von zirkulär abhängigen Projekten aufgelöst, indem der älteste angeforderte Build begonnen wird.
</div>

0 comments on commit 237b9ca

Please sign in to comment.