Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[JENKINS-68780] Blocked upstream projects in the queue block downstream projects #6675

Merged
merged 11 commits into from
Jul 19, 2022
Merged
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();
daniel-beck marked this conversation as resolved.
Show resolved Hide resolved
// 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();
daniel-beck marked this conversation as resolved.
Show resolved Hide resolved
// 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
103 changes: 103 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,109 @@ 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) {
boolean waitingItemsPresent = true;
while (waitingItemsPresent) {
waitingItemsPresent = false;
for (Queue.Item i : q.getItems()) {
if (i instanceof WaitingItem) {
waitingItemsPresent = true;
}
}
}
}
basil marked this conversation as resolved.
Show resolved Hide resolved

@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>