diff --git a/core/src/main/java/hudson/model/Queue.java b/core/src/main/java/hudson/model/Queue.java index d33b985fb7f997d51f72eeb696695934b4165f86..951b8722a353c5151b02953bb69a1ef52892e588 100644 --- a/core/src/main/java/hudson/model/Queue.java +++ b/core/src/main/java/hudson/model/Queue.java @@ -1408,15 +1408,29 @@ public class Queue extends ResourceController implements Saveable { } } + final QueueSorter s = sorter; {// blocked -> buildable - for (BlockedItem p : new ArrayList(blockedProjects.values())) {// copy as we'll mutate the list + // copy as we'll mutate the list and we want to process in a potentially different order + // TODO replace with <> operator after backporting to 1.609.3 + List blockedItems = new ArrayList(blockedProjects.values()); + // if facing a cycle of blocked tasks, ensure we process in the desired sort order + if (s != null) { + s.sortBlockedItems(blockedItems); + } else { + Collections.sort(blockedItems, QueueSorter.DEFAULT_BLOCKED_ITEM_COMPARATOR); + } + for (BlockedItem p : blockedItems) { if (!isBuildBlocked(p) && allowNewBuildableTask(p.task)) { // ready to be executed Runnable r = makeBuildable(new BuildableItem(p)); if (r != null) { p.leave(this); r.run(); + // JENKINS-28926 we have removed a task from the blocked projects and added to building + // thus we should update the snapshot so that subsequent blocked projects can correctly + // determine if they are blocked by the lucky winner + updateSnapshot(); } } } @@ -1446,7 +1460,6 @@ public class Queue extends ResourceController implements Saveable { } } - final QueueSorter s = sorter; if (s != null) s.sortBuildableItems(buildables); @@ -1461,6 +1474,9 @@ public class Queue extends ResourceController implements Saveable { p.leave(this); new BlockedItem(p).enter(this); LOGGER.log(Level.FINE, "Catching that {0} is blocked in the last minute", p); + // JENKINS-28926 we have moved an unblocked task into the blocked state, update snapshot + // so that other buildables which might have been blocked by this can see the state change + updateSnapshot(); continue; } diff --git a/core/src/main/java/hudson/model/queue/QueueSorter.java b/core/src/main/java/hudson/model/queue/QueueSorter.java index fafd9b0c1cc3b59baeefb819f16ececeb0081b28..296312b73ba33bd2c283f79a3ed444f5f3da4f89 100644 --- a/core/src/main/java/hudson/model/queue/QueueSorter.java +++ b/core/src/main/java/hudson/model/queue/QueueSorter.java @@ -8,6 +8,8 @@ import hudson.model.LoadBalancer; import hudson.model.Queue; import hudson.model.Queue.BuildableItem; +import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.logging.Logger; @@ -19,6 +21,20 @@ import static hudson.init.InitMilestone.JOB_LOADED; * @since 1.343 */ public abstract class QueueSorter implements ExtensionPoint { + /** + * A comparator that sorts {@link Queue.BlockedItem} instances based on how long they have been in the queue. + * (We want the time since in queue by default as blocking on upstream/downstream considers waiting items + * also and thus the blocking starts once the task is in the queue not once the task is buildable) + * + * @since 1.FIXME + */ + public static final Comparator DEFAULT_BLOCKED_ITEM_COMPARATOR = new Comparator() { + @Override + public int compare(Queue.BlockedItem o1, Queue.BlockedItem o2) { + return Long.compare(o1.getInQueueSince(), o2.getInQueueSince()); + } + }; + /** * Sorts the buildable items list. The items at the beginning will be executed * before the items at the end of the list. @@ -28,6 +44,18 @@ public abstract class QueueSorter implements ExtensionPoint { */ public abstract void sortBuildableItems(List buildables); + /** + * Sorts the blocked items list. The items at the beginning will be considered for removal from the blocked state + * before the items at the end of the list. + * + * @param blockedItems + * List of blocked items in the queue. Never null. + * @since 1.FIXME + */ + public void sortBlockedItems(List blockedItems) { + Collections.sort(blockedItems, DEFAULT_BLOCKED_ITEM_COMPARATOR); + } + /** * All registered {@link QueueSorter}s. Only the first one will be picked up, * unless explicitly overridden by {@link Queue#setSorter(QueueSorter)}.