/hudson-core/src/main/java/hudson/model/Queue.java
Java | 1664 lines | 891 code | 199 blank | 574 comment | 193 complexity | 89d12a0e8d743f859f98a8dcde859878 MD5 | raw file
Possible License(s): MIT, BSD-3-Clause
- /*
- * The MIT License
- *
- * Copyright (c) 2004-2010, Sun Microsystems, Inc., Kohsuke Kawaguchi,
- * Stephen Connolly, Tom Huybrechts, InfraDNA, Inc.
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
- package hudson.model;
- import hudson.AbortException;
- import hudson.BulkChange;
- import hudson.ExtensionList;
- import hudson.ExtensionPoint;
- import hudson.Util;
- import hudson.XmlFile;
- import hudson.init.Initializer;
- import static hudson.init.InitMilestone.JOB_LOADED;
- import static hudson.util.Iterators.reverse;
- import hudson.cli.declarative.CLIMethod;
- import hudson.cli.declarative.CLIResolver;
- import hudson.model.queue.AbstractQueueTask;
- import hudson.model.queue.Executables;
- import hudson.model.queue.SubTask;
- import hudson.model.queue.FutureImpl;
- import hudson.model.queue.MappingWorksheet;
- import hudson.model.queue.MappingWorksheet.Mapping;
- import hudson.model.queue.QueueSorter;
- import hudson.model.queue.QueueTaskDispatcher;
- import hudson.model.queue.Tasks;
- import hudson.model.queue.WorkUnit;
- import hudson.model.Node.Mode;
- import hudson.model.listeners.SaveableListener;
- import hudson.model.queue.CauseOfBlockage;
- import hudson.model.queue.FoldableAction;
- import hudson.model.queue.CauseOfBlockage.BecauseLabelIsBusy;
- import hudson.model.queue.CauseOfBlockage.BecauseNodeIsOffline;
- import hudson.model.queue.CauseOfBlockage.BecauseLabelIsOffline;
- import hudson.model.queue.CauseOfBlockage.BecauseNodeIsBusy;
- import hudson.model.queue.WorkUnitContext;
- import hudson.triggers.SafeTimerTask;
- import hudson.triggers.Trigger;
- import hudson.util.OneShotEvent;
- import hudson.util.TimeUnit2;
- import hudson.util.XStream2;
- import hudson.util.ConsistentHash;
- import hudson.util.ConsistentHash.Hash;
- import java.io.BufferedReader;
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.IOException;
- import java.io.InputStreamReader;
- import java.lang.ref.WeakReference;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.Calendar;
- import java.util.Collection;
- import java.util.Collections;
- import java.util.GregorianCalendar;
- import java.util.HashMap;
- import java.util.Iterator;
- import java.util.List;
- import java.util.Map;
- import java.util.NoSuchElementException;
- import java.util.Set;
- import java.util.TreeSet;
- import java.util.Map.Entry;
- import java.util.concurrent.atomic.AtomicInteger;
- import java.util.concurrent.Future;
- import java.util.logging.Level;
- import java.util.logging.Logger;
- import javax.management.timer.Timer;
- import javax.servlet.ServletException;
- import org.kohsuke.stapler.HttpResponse;
- import org.kohsuke.stapler.HttpResponses;
- import org.kohsuke.stapler.export.Exported;
- import org.kohsuke.stapler.export.ExportedBean;
- import com.thoughtworks.xstream.XStream;
- import com.thoughtworks.xstream.converters.basic.AbstractSingleValueConverter;
- import java.util.HashSet;
- /**
- * Build queue.
- *
- * <p>
- * This class implements the core scheduling logic. {@link Task} represents the executable
- * task that are placed in the queue. While in the queue, it's wrapped into {@link Item}
- * so that we can keep track of additional data used for deciding what to exeucte when.
- *
- * <p>
- * Items in queue goes through several stages, as depicted below:
- * <pre>
- * (enter) --> waitingList --+--> blockedProjects
- * | ^
- * | |
- * | v
- * +--> buildables ---> pending ---> (executed)
- * </pre>
- *
- * <p>
- * In addition, at any stage, an item can be removed from the queue (for example, when the user
- * cancels a job in the queue.) See the corresponding field for their exact meanings.
- *
- * @author Kohsuke Kawaguchi
- */
- @ExportedBean
- public class Queue extends ResourceController implements Saveable {
- /**
- * Items that are waiting for its quiet period to pass.
- *
- * <p>
- * This consists of {@link Item}s that cannot be run yet
- * because its time has not yet come.
- */
- private final Set<WaitingItem> waitingList = new TreeSet<WaitingItem>();
- /**
- * {@link Task}s that can be built immediately
- * but blocked because another build is in progress,
- * required {@link Resource}s are not available, or otherwise blocked
- * by {@link Task#isBuildBlocked()}.
- */
- private final ItemList<BlockedItem> blockedProjects = new ItemList<BlockedItem>();
- /**
- * {@link Task}s that can be built immediately
- * that are waiting for available {@link Executor}.
- * This list is sorted in such a way that earlier items are built earlier.
- */
- private final ItemList<BuildableItem> buildables = new ItemList<BuildableItem>();
- /**
- * {@link Task}s that are being handed over to the executor, but execution
- * has not started yet.
- */
- private final ItemList<BuildableItem> pendings = new ItemList<BuildableItem>();
- /**
- * Data structure created for each idle {@link Executor}.
- * This is a job offer from the queue to an executor.
- *
- * <p>
- * An idle executor (that calls {@link Queue#pop()} creates
- * a new {@link JobOffer} and gets itself {@linkplain Queue#parked parked},
- * and we'll eventually hand out an {@link #workUnit} to build.
- */
- public class JobOffer extends MappingWorksheet.ExecutorSlot {
- public final Executor executor;
- /**
- * Used to wake up an executor, when it has an offered
- * {@link Project} to build.
- */
- private final OneShotEvent event = new OneShotEvent(Queue.this);
- /**
- * The work unit that this {@link Executor} is going to handle.
- * (Or null, in which case event is used to trigger a queue maintenance.)
- */
- private WorkUnit workUnit;
- private JobOffer(Executor executor) {
- this.executor = executor;
- }
- @Override
- protected void set(WorkUnit p) {
- assert this.workUnit == null;
- this.workUnit = p;
- event.signal();
- }
- @Override
- public Executor getExecutor() {
- return executor;
- }
- /**
- * Verifies that the {@link Executor} represented by this object is capable of executing the given task.
- */
- public boolean canTake(Task task) {
- Node node = getNode();
- if (node==null) return false; // this executor is about to die
- if(node.canTake(task)!=null)
- return false; // this node is not able to take the task
- for (QueueTaskDispatcher d : QueueTaskDispatcher.all())
- if (d.canTake(node,task)!=null)
- return false;
- return isAvailable();
- }
- /**
- * Is this executor ready to accept some tasks?
- */
- public boolean isAvailable() {
- return workUnit == null && !executor.getOwner().isOffline() && executor.getOwner().isAcceptingTasks();
- }
- public Node getNode() {
- return executor.getOwner().getNode();
- }
- public boolean isNotExclusive() {
- return getNode().getMode() == Mode.NORMAL;
- }
- }
- /**
- * The executors that are currently waiting for a job to run.
- */
- private final Map<Executor,JobOffer> parked = new HashMap<Executor,JobOffer>();
- private volatile transient LoadBalancer loadBalancer;
- private volatile transient QueueSorter sorter;
- public Queue(LoadBalancer loadBalancer) {
- this.loadBalancer = loadBalancer.sanitize();
- // if all the executors are busy doing something, then the queue won't be maintained in
- // timely fashion, so use another thread to make sure it happens.
- new MaintainTask(this);
- }
- public LoadBalancer getLoadBalancer() {
- return loadBalancer;
- }
- public void setLoadBalancer(LoadBalancer loadBalancer) {
- if(loadBalancer==null) throw new IllegalArgumentException();
- this.loadBalancer = loadBalancer;
- }
- public QueueSorter getSorter() {
- return sorter;
- }
- public void setSorter(QueueSorter sorter) {
- this.sorter = sorter;
- }
- /**
- * Loads the queue contents that was {@link #save() saved}.
- */
- public synchronized void load() {
- try {
- // first try the old format
- File queueFile = getQueueFile();
- if (queueFile.exists()) {
- BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(queueFile)));
- String line;
- while ((line = in.readLine()) != null) {
- AbstractProject j = Hudson.getInstance().getItemByFullName(line, AbstractProject.class);
- if (j != null)
- j.scheduleBuild();
- }
- in.close();
- // discard the queue file now that we are done
- queueFile.delete();
- } else {
- queueFile = getXMLQueueFile();
- if (queueFile.exists()) {
- List list = (List) new XmlFile(XSTREAM, queueFile).read();
- int maxId = 0;
- for (Object o : list) {
- if (o instanceof Task) {
- // backward compatibility
- schedule((Task)o, 0);
- } else if (o instanceof Item) {
- Item item = (Item)o;
- if(item.task==null)
- continue; // botched persistence. throw this one away
- maxId = Math.max(maxId, item.id);
- if (item instanceof WaitingItem) {
- waitingList.add((WaitingItem) item);
- } else if (item instanceof BlockedItem) {
- blockedProjects.put(item.task, (BlockedItem) item);
- } else if (item instanceof BuildableItem) {
- buildables.add((BuildableItem) item);
- } else {
- throw new IllegalStateException("Unknown item type! " + item);
- }
- } // this conveniently ignores null
- }
- WaitingItem.COUNTER.set(maxId);
- // I just had an incident where all the executors are dead at AbstractProject._getRuns()
- // because runs is null. Debugger revealed that this is caused by a MatrixConfiguration
- // object that doesn't appear to be de-serialized properly.
- // I don't know how this problem happened, but to diagnose this problem better
- // when it happens again, save the old queue file for introspection.
- File bk = new File(queueFile.getPath() + ".bak");
- bk.delete();
- queueFile.renameTo(bk);
- queueFile.delete();
- }
- }
- } catch (IOException e) {
- LOGGER.log(Level.WARNING, "Failed to load the queue file " + getXMLQueueFile(), e);
- }
- }
- /**
- * Persists the queue contents to the disk.
- */
- public synchronized void save() {
- if(BulkChange.contains(this)) return;
-
- // write out the tasks on the queue
- ArrayList<Queue.Item> items = new ArrayList<Queue.Item>();
- for (Item item: getItems()) {
- if(item.task instanceof TransientTask) continue;
- items.add(item);
- }
- try {
- XmlFile queueFile = new XmlFile(XSTREAM, getXMLQueueFile());
- queueFile.write(items);
- SaveableListener.fireOnChange(this, queueFile);
- } catch (IOException e) {
- LOGGER.log(Level.WARNING, "Failed to write out the queue file " + getXMLQueueFile(), e);
- }
- }
- /**
- * Wipes out all the items currently in the queue, as if all of them are cancelled at once.
- */
- @CLIMethod(name="clear-queue")
- public synchronized void clear() {
- Hudson.getInstance().checkPermission(Hudson.ADMINISTER);
- for (WaitingItem i : waitingList)
- i.onCancelled();
- waitingList.clear();
- blockedProjects.cancelAll();
- buildables.cancelAll();
- scheduleMaintenance();
- }
- /**
- * Called from queue.jelly.
- */
- public HttpResponse doClearQueue() throws IOException, ServletException {
- Hudson.getInstance().getQueue().clear();
- return HttpResponses.forwardToPreviousPage();
- }
- private File getQueueFile() {
- return new File(Hudson.getInstance().getRootDir(), "queue.txt");
- }
- /*package*/ File getXMLQueueFile() {
- return new File(Hudson.getInstance().getRootDir(), "queue.xml");
- }
- /**
- * @deprecated as of 1.311
- * Use {@link #schedule(AbstractProject)}
- */
- public boolean add(AbstractProject p) {
- return schedule(p)!=null;
- }
- /**
- * Schedule a new build for this project.
- *
- * @return true if the project is actually added to the queue.
- * false if the queue contained it and therefore the add()
- * was noop
- */
- public WaitingItem schedule(AbstractProject p) {
- return schedule(p, p.getQuietPeriod());
- }
- /**
- * Schedules a new build with a custom quiet period.
- *
- * <p>
- * Left for backward compatibility with <1.114.
- *
- * @since 1.105
- * @deprecated as of 1.311
- * Use {@link #schedule(Task, int)}
- */
- public boolean add(AbstractProject p, int quietPeriod) {
- return schedule(p, quietPeriod)!=null;
- }
- /**
- * Schedules an execution of a task.
- *
- * @param actions
- * These actions can be used for associating information scoped to a particular build, to
- * the task being queued. Upon the start of the build, these {@link Action}s will be automatically
- * added to the {@link Run} object, and hence avaialable to everyone.
- * For the convenience of the caller, this list can contain null, and those will be silently ignored.
- * @since 1.311
- * @return
- * null if this task is already in the queue and therefore the add operation was no-op.
- * Otherwise indicates the {@link WaitingItem} object added, although the nature of the queue
- * is that such {@link Item} only captures the state of the item at a particular moment,
- * and by the time you inspect the object, some of its information can be already stale.
- *
- * That said, one can still look at {@link WaitingItem#future}, {@link WaitingItem#id}, etc.
- */
- public synchronized WaitingItem schedule(Task p, int quietPeriod, List<Action> actions) {
- // remove nulls
- actions = new ArrayList<Action>(actions);
- for (Iterator<Action> itr = actions.iterator(); itr.hasNext();) {
- Action a = itr.next();
- if (a==null) itr.remove();
- }
- for(QueueDecisionHandler h : QueueDecisionHandler.all())
- if (!h.shouldSchedule(p, actions))
- return null; // veto
- return scheduleInternal(p, quietPeriod, actions);
- }
- /**
- * Schedules an execution of a task.
- *
- * @since 1.311
- * @return
- * null if this task is already in the queue and therefore the add operation was no-op.
- * Otherwise indicates the {@link WaitingItem} object added, although the nature of the queue
- * is that such {@link Item} only captures the state of the item at a particular moment,
- * and by the time you inspect the object, some of its information can be already stale.
- *
- * That said, one can still look at {@link WaitingItem#future}, {@link WaitingItem#id}, etc.
- */
- private synchronized WaitingItem scheduleInternal(Task p, int quietPeriod, List<Action> actions) {
- Calendar due = new GregorianCalendar();
- due.add(Calendar.SECOND, quietPeriod);
- // Do we already have this task in the queue? Because if so, we won't schedule a new one.
- List<Item> duplicatesInQueue = new ArrayList<Item>();
- for(Item item : getItems(p)) {
- boolean shouldScheduleItem = false;
- for (QueueAction action: item.getActions(QueueAction.class)) {
- shouldScheduleItem |= action.shouldSchedule(actions);
- }
- for (QueueAction action: Util.filter(actions,QueueAction.class)) {
- shouldScheduleItem |= action.shouldSchedule(item.getActions());
- }
- if(!shouldScheduleItem) {
- duplicatesInQueue.add(item);
- }
- }
- if (duplicatesInQueue.isEmpty()) {
- LOGGER.fine(p.getFullDisplayName() + " added to queue");
- // put the item in the queue
- WaitingItem added = new WaitingItem(due,p,actions);
- waitingList.add(added);
- scheduleMaintenance(); // let an executor know that a new item is in the queue.
- return added;
- }
- LOGGER.fine(p.getFullDisplayName() + " is already in the queue");
- // but let the actions affect the existing stuff.
- for(Item item : duplicatesInQueue) {
- for(FoldableAction a : Util.filter(actions,FoldableAction.class)) {
- a.foldIntoExisting(item, p, actions);
- }
- }
- boolean queueUpdated = false;
- for(WaitingItem wi : Util.filter(duplicatesInQueue,WaitingItem.class)) {
- if(quietPeriod<=0) {
- // the user really wants to build now, and they mean NOW.
- // so let's pull in the timestamp if we can.
- if (wi.timestamp.before(due))
- continue;
- } else {
- // otherwise we do the normal quiet period implementation
- if (wi.timestamp.after(due))
- continue;
- // quiet period timer reset. start the period over again
- }
- // waitingList is sorted, so when we change a timestamp we need to maintain order
- waitingList.remove(wi);
- wi.timestamp = due;
- waitingList.add(wi);
- queueUpdated=true;
- }
- if (queueUpdated) scheduleMaintenance();
- return null;
- }
-
- /**
- * @deprecated as of 1.311
- * Use {@link #schedule(Task, int)}
- */
- public synchronized boolean add(Task p, int quietPeriod) {
- return schedule(p, quietPeriod)!=null;
- }
- public synchronized WaitingItem schedule(Task p, int quietPeriod) {
- return schedule(p, quietPeriod, new Action[0]);
- }
- /**
- * @deprecated as of 1.311
- * Use {@link #schedule(Task, int, Action...)}
- */
- public synchronized boolean add(Task p, int quietPeriod, Action... actions) {
- return schedule(p, quietPeriod, actions)!=null;
- }
- /**
- * Convenience wrapper method around {@link #schedule(Task, int, List)}
- */
- public synchronized WaitingItem schedule(Task p, int quietPeriod, Action... actions) {
- return schedule(p, quietPeriod, Arrays.asList(actions));
- }
- /**
- * Cancels the item in the queue. If the item is scheduled more than once, cancels the first occurrence.
- *
- * @return true if the project was indeed in the queue and was removed.
- * false if this was no-op.
- */
- public synchronized boolean cancel(Task p) {
- LOGGER.fine("Cancelling " + p.getFullDisplayName());
- for (Iterator<WaitingItem> itr = waitingList.iterator(); itr.hasNext();) {
- Item item = itr.next();
- if (item.task.equals(p)) {
- itr.remove();
- item.onCancelled();
- return true;
- }
- }
- // use bitwise-OR to make sure that both branches get evaluated all the time
- return blockedProjects.cancel(p)!=null | buildables.cancel(p)!=null;
- }
-
- public synchronized boolean cancel(Item item) {
- LOGGER.fine("Cancelling " + item.task.getFullDisplayName() + " item#" + item.id);
- // use bitwise-OR to make sure that all the branches get evaluated all the time
- boolean r = (item instanceof WaitingItem && waitingList.remove(item)) | blockedProjects.remove(item) | buildables.remove(item);
- if(r)
- item.onCancelled();
- return r;
- }
- public synchronized boolean isEmpty() {
- return waitingList.isEmpty() && blockedProjects.isEmpty() && buildables.isEmpty() && pendings.isEmpty();
- }
- private synchronized WaitingItem peek() {
- return waitingList.iterator().next();
- }
- /**
- * Gets a snapshot of items in the queue.
- *
- * Generally speaking the array is sorted such that the items that are most likely built sooner are
- * at the end.
- */
- @Exported(inline=true)
- public synchronized Item[] getItems() {
- Item[] r = new Item[waitingList.size() + blockedProjects.size() + buildables.size() + pendings.size()];
- waitingList.toArray(r);
- int idx = waitingList.size();
- for (BlockedItem p : blockedProjects.values())
- r[idx++] = p;
- for (BuildableItem p : reverse(buildables.values()))
- r[idx++] = p;
- for (BuildableItem p : reverse(pendings.values()))
- r[idx++] = p;
- return r;
- }
-
- public synchronized Item getItem(int id) {
- for (Item item: waitingList) if (item.id == id) return item;
- for (Item item: blockedProjects) if (item.id == id) return item;
- for (Item item: buildables) if (item.id == id) return item;
- for (Item item: pendings) if (item.id == id) return item;
- return null;
- }
- /**
- * Gets all the {@link BuildableItem}s that are waiting for an executor in the given {@link Computer}.
- */
- public synchronized List<BuildableItem> getBuildableItems(Computer c) {
- List<BuildableItem> result = new ArrayList<BuildableItem>();
- _getBuildableItems(c, buildables, result);
- _getBuildableItems(c, pendings, result);
- return result;
- }
- private void _getBuildableItems(Computer c, ItemList<BuildableItem> col, List<BuildableItem> result) {
- Node node = c.getNode();
- for (BuildableItem p : col.values()) {
- if (node.canTake(p.task) == null)
- result.add(p);
- }
- }
- /**
- * Gets the snapshot of all {@link BuildableItem}s.
- */
- public synchronized List<BuildableItem> getBuildableItems() {
- ArrayList<BuildableItem> r = new ArrayList<BuildableItem>(buildables.values());
- r.addAll(pendings.values());
- return r;
- }
- /**
- * Gets the snapshot of all {@link BuildableItem}s.
- */
- public synchronized List<BuildableItem> getPendingItems() {
- return new ArrayList<BuildableItem>(pendings.values());
- }
-
- synchronized Set<Task> getUnblockedQueuedTasks() {
- Set<Task> unblockedQueuedTasks = new HashSet<Task>();
- for (Item item: waitingList) {
- unblockedQueuedTasks.add(item.task);
- }
- for (Item item: pendings) {
- unblockedQueuedTasks.add(item.task);
- }
- for (Item item: buildables) {
- unblockedQueuedTasks.add(item.task);
- }
- return unblockedQueuedTasks;
- }
- /**
- * Is the given task currently pending execution?
- */
- public synchronized boolean isPending(Task t) {
- for (BuildableItem i : pendings)
- if (i.task.equals(t))
- return true;
- return false;
- }
- /**
- * How many {@link BuildableItem}s are assigned for the given label?
- */
- public synchronized int countBuildableItemsFor(Label l) {
- int r = 0;
- for (BuildableItem bi : buildables.values())
- if(bi.task.getAssignedLabel()==l)
- r++;
- for (BuildableItem bi : pendings.values())
- if(bi.task.getAssignedLabel()==l)
- r++;
- return r;
- }
- /**
- * Gets the information about the queue item for the given project.
- *
- * @return null if the project is not in the queue.
- */
- public synchronized Item getItem(Task t) {
- BlockedItem bp = blockedProjects.get(t);
- if (bp!=null)
- return bp;
- BuildableItem bi = buildables.get(t);
- if(bi!=null)
- return bi;
- bi = pendings.get(t);
- if(bi!=null)
- return bi;
- for (Item item : waitingList) {
- if (item.task == t)
- return item;
- }
- return null;
- }
- /**
- * Gets the information about the queue item for the given project.
- *
- * @return null if the project is not in the queue.
- */
- public synchronized List<Item> getItems(Task t) {
- List<Item> result =new ArrayList<Item>();
- result.addAll(blockedProjects.getAll(t));
- result.addAll(buildables.getAll(t));
- result.addAll(pendings.getAll(t));
- for (Item item : waitingList) {
- if (item.task == t)
- result.add(item);
- }
- return result;
- }
- /**
- * Left for backward compatibility.
- *
- * @see #getItem(Task)
- public synchronized Item getItem(AbstractProject p) {
- return getItem((Task) p);
- }
- */
- /**
- * Returns true if this queue contains the said project.
- */
- public synchronized boolean contains(Task t) {
- if (blockedProjects.containsKey(t) || buildables.containsKey(t) || pendings.containsKey(t))
- return true;
- for (Item item : waitingList) {
- if (item.task == t)
- return true;
- }
- return false;
- }
- /**
- * Called by the executor to fetch something to build next.
- * <p>
- * This method blocks until a next project becomes buildable.
- */
- public synchronized WorkUnit pop() throws InterruptedException {
- final Executor exec = Executor.currentExecutor();
- if (exec instanceof OneOffExecutor) {
- OneOffExecutor ooe = (OneOffExecutor) exec;
- final WorkUnit wu = ooe.getWorkUnit();
- pendings.remove(wu.context.item);
- return wu;
- }
- try {
- while (true) {
- final JobOffer offer = new JobOffer(exec);
- long sleep = -1;
- // consider myself parked
- assert !parked.containsKey(exec);
- parked.put(exec, offer);
- // reuse executor thread to do a queue maintenance.
- // at the end of this we get all the buildable jobs
- // in the buildables field.
- maintain();
- // allocate buildable jobs to executors
- Iterator<BuildableItem> itr = buildables.iterator();
- while (itr.hasNext()) {
- BuildableItem p = itr.next();
- // one last check to make sure this build is not blocked.
- if (isBuildBlocked(p.task)) {
- itr.remove();
- blockedProjects.put(p.task,new BlockedItem(p));
- continue;
- }
- List<JobOffer> candidates = new ArrayList<JobOffer>(parked.size());
- for (JobOffer j : parked.values())
- if(j.canTake(p.task))
- candidates.add(j);
- MappingWorksheet ws = new MappingWorksheet(p, candidates);
- Mapping m = loadBalancer.map(p.task, ws);
- if (m == null)
- // if we couldn't find the executor that fits,
- // just leave it in the buildables list and
- // check if we can execute other projects
- continue;
- // found a matching executor. use it.
- WorkUnitContext wuc = new WorkUnitContext(p);
- m.execute(wuc);
- itr.remove();
- if (!wuc.getWorkUnits().isEmpty())
- pendings.add(p);
- }
- // we went over all the buildable projects and awaken
- // all the executors that got work to do. now, go to sleep
- // until this thread is awakened. If this executor assigned a job to
- // itself above, the block method will return immediately.
- if (!waitingList.isEmpty()) {
- // wait until the first item in the queue is due
- sleep = peek().timestamp.getTimeInMillis() - new GregorianCalendar().getTimeInMillis();
- if (sleep < 100) sleep = 100; // avoid wait(0)
- }
- if (sleep == -1)
- offer.event.block();
- else
- offer.event.block(sleep);
- // retract the offer object
- assert parked.get(exec) == offer;
- parked.remove(exec);
- // am I woken up because I have a project to build?
- if (offer.workUnit != null) {
- // if so, just build it
- LOGGER.fine("Pop returning " + offer.workUnit + " for " + exec.getName());
- // TODO: I think this has to be done by the last executor that leaves the pop(), not by main executor
- if (offer.workUnit.isMainWork())
- pendings.remove(offer.workUnit.context.item);
- return offer.workUnit;
- }
- // otherwise run a queue maintenance
- }
- } finally {
- // remove myself from the parked list
- JobOffer offer = parked.remove(exec);
- if (offer != null && offer.workUnit != null) {
- // we are already assigned a project, but now we can't handle it.
- offer.workUnit.context.abort(new AbortException());
- }
- // since this executor might have been chosen for
- // maintenance, schedule another one. Worst case
- // we'll just run a pointless maintenance, and that's
- // fine.
- scheduleMaintenance();
- }
- }
- /**
- * Checks the queue and runs anything that can be run.
- *
- * <p>
- * When conditions are changed, this method should be invoked.
- * <p>
- * This wakes up one {@link Executor} so that it will maintain a queue.
- */
- public synchronized void scheduleMaintenance() {
- // this code assumes that after this method is called
- // no more executors will be offered job except by
- // the pop() code.
- for (Entry<Executor, JobOffer> av : parked.entrySet()) {
- if (av.getValue().workUnit == null) {
- av.getValue().event.signal();
- return;
- }
- }
- }
- /**
- * Checks if the given task is blocked.
- */
- private boolean isBuildBlocked(Task t) {
- return t.isBuildBlocked() || !canRun(t.getResourceList());
- }
- /**
- * Make sure we don't queue two tasks of the same project to be built
- * unless that project allows concurrent builds.
- */
- private boolean allowNewBuildableTask(Task t) {
- try {
- if (t.isConcurrentBuild())
- return true;
- } catch (AbstractMethodError e) {
- // earlier versions don't have the "isConcurrentBuild" method, so fall back gracefully
- }
- return !buildables.containsKey(t) && !pendings.containsKey(t);
- }
- /**
- * Queue maintenance.
- * <p>
- * Move projects between {@link #waitingList}, {@link #blockedProjects}, and {@link #buildables}
- * appropriately.
- */
- public synchronized void maintain() {
- if (LOGGER.isLoggable(Level.FINE))
- LOGGER.fine("Queue maintenance started " + this);
- // blocked -> buildable
- Iterator<BlockedItem> itr = blockedProjects.values().iterator();
- while (itr.hasNext()) {
- BlockedItem p = itr.next();
- if (!isBuildBlocked(p.task) && allowNewBuildableTask(p.task)) {
- // ready to be executed
- LOGGER.fine(p.task.getFullDisplayName() + " no longer blocked");
- itr.remove();
- makeBuildable(new BuildableItem(p));
- }
- }
- while (!waitingList.isEmpty()) {
- WaitingItem top = peek();
- if (!top.timestamp.before(new GregorianCalendar()))
- return; // finished moving all ready items from queue
- waitingList.remove(top);
- Task p = top.task;
- if (!isBuildBlocked(p) && allowNewBuildableTask(p)) {
- // ready to be executed immediately
- LOGGER.fine(p.getFullDisplayName() + " ready to build");
- makeBuildable(new BuildableItem(top));
- } else {
- // this can't be built now because another build is in progress
- // set this project aside.
- LOGGER.fine(p.getFullDisplayName() + " is blocked");
- blockedProjects.put(p,new BlockedItem(top));
- }
- }
- final QueueSorter s = sorter;
- if (s != null)
- s.sortBuildableItems(buildables);
- }
- private void makeBuildable(BuildableItem p) {
- if(Hudson.FLYWEIGHT_SUPPORT && p.task instanceof FlyweightTask && !ifBlockedByHudsonShutdown(p.task)) {
- ConsistentHash<Node> hash = new ConsistentHash<Node>(new Hash<Node>() {
- public String hash(Node node) {
- return node.getNodeName();
- }
- });
- Hudson h = Hudson.getInstance();
- hash.add(h, h.getNumExecutors()*100);
- for (Node n : h.getNodes())
- hash.add(n,n.getNumExecutors()*100);
- Label lbl = p.task.getAssignedLabel();
- for (Node n : hash.list(p.task.getFullDisplayName())) {
- Computer c = n.toComputer();
- if (c==null || c.isOffline()) continue;
- if (lbl!=null && !lbl.contains(n)) continue;
- c.startFlyWeightTask(new WorkUnitContext(p).createWorkUnit(p.task));
- pendings.add(p);
- return;
- }
- // if the execution get here, it means we couldn't schedule it anywhere.
- // so do the scheduling like other normal jobs.
- }
-
- buildables.put(p.task,p);
- }
- public static boolean ifBlockedByHudsonShutdown(Task task) {
- return Hudson.getInstance().isQuietingDown() && !(task instanceof NonBlockingTask);
- }
- public Api getApi() {
- return new Api(this);
- }
- /**
- * Marks {@link Task}s that are not persisted.
- * @since 1.311
- */
- public interface TransientTask extends Task {}
- /**
- * Marks {@link Task}s that do not consume {@link Executor}.
- * @see OneOffExecutor
- * @since 1.318
- */
- public interface FlyweightTask extends Task {}
- /**
- * Marks {@link Task}s that are not affected by the {@linkplain Hudson#isQuietingDown()} quieting down},
- * because these tasks keep other tasks executing.
- *
- * @since 1.336
- */
- public interface NonBlockingTask extends Task {}
- /**
- * Task whose execution is controlled by the queue.
- *
- * <p>
- * {@link #equals(Object) Value equality} of {@link Task}s is used
- * to collapse two tasks into one. This is used to avoid infinite
- * queue backlog.
- *
- * <p>
- * Pending {@link Task}s are persisted when Hudson shuts down, so
- * it needs to be persistable via XStream. To create a non-persisted
- * transient Task, extend {@link TransientTask} marker interface.
- *
- * <p>
- * Plugins are encouraged to extend from {@link AbstractQueueTask}
- * instead of implementing this interface directly, to maintain
- * compatibility with future changes to this interface.
- *
- * <p>
- * For historical reasons, {@link Task} object by itself
- * also represents the "primary" sub-task (and as implied by this
- * design, a {@link Task} must have at least one sub-task.)
- * Most of the time, the primary subtask is the only sub task.
- */
- public interface Task extends ModelObject, SubTask {
- /**
- * Returns true if the execution should be blocked
- * for temporary reasons.
- *
- * <p>
- * Short-hand for {@code getCauseOfBlockage()!=null}.
- */
- boolean isBuildBlocked();
- /**
- * @deprecated as of 1.330
- * Use {@link CauseOfBlockage#getShortDescription()} instead.
- */
- String getWhyBlocked();
- /**
- * If the execution of this task should be blocked for temporary reasons,
- * this method returns a non-null object explaining why.
- *
- * <p>
- * Otherwise this method returns null, indicating that the build can proceed right away.
- *
- * <p>
- * This can be used to define mutual exclusion that goes beyond
- * {@link #getResourceList()}.
- */
- CauseOfBlockage getCauseOfBlockage();
- /**
- * Unique name of this task.
- *
- * <p>
- * This method is no longer used, left here for compatibility. Just return {@link #getDisplayName()}.
- */
- String getName();
- /**
- * @see hudson.model.Item#getFullDisplayName()
- */
- String getFullDisplayName();
- /**
- * Checks the permission to see if the current user can abort this executable.
- * Returns normally from this method if it's OK.
- *
- * @throws org.acegisecurity.AccessDeniedException if the permission is not granted.
- */
- void checkAbortPermission();
- /**
- * Works just like {@link #checkAbortPermission()} except it indicates the status by a return value,
- * instead of exception.
- */
- boolean hasAbortPermission();
-
- /**
- * Returns the URL of this task relative to the context root of the application.
- *
- * <p>
- * When the user clicks an item in the queue, this is the page where the user is taken to.
- * Hudson expects the current instance to be bound to the URL returned by this method.
- *
- * @return
- * URL that ends with '/'.
- */
- String getUrl();
-
- /**
- * True if the task allows concurrent builds
- *
- * @since 1.338
- */
- boolean isConcurrentBuild();
- /**
- * Obtains the {@link SubTask}s that constitute this task.
- *
- * <p>
- * The collection returned by this method must also contain the primary {@link SubTask}
- * represented by this {@link Task} object itself as the first element.
- * The returned value is read-only.
- *
- * <p>
- * At least size 1.
- *
- * <p>
- * Since this is a newly added method, the invocation may results in {@link AbstractMethodError}.
- * Use {@link Tasks#getSubTasksOf(Task)} that avoids this.
- *
- * @since 1.377
- */
- Collection<? extends SubTask> getSubTasks();
- }
- /**
- * Represents the real meat of the computation run by {@link Executor}.
- *
- * <h2>Views</h2>
- * <p>
- * Implementation must have <tt>executorCell.jelly</tt>, which is
- * used to render the HTML that indicates this executable is executing.
- */
- public interface Executable extends Runnable {
- /**
- * Task from which this executable was created.
- * Never null.
- *
- * <p>
- * Since this method went through a signature change in 1.377, the invocation may results in
- * {@link AbstractMethodError}.
- * Use {@link Executables#getParentOf(Executable)} that avoids this.
- */
- SubTask getParent();
- /**
- * Called by {@link Executor} to perform the task
- */
- void run();
-
- /**
- * Estimate of how long will it take to execute this executable.
- * Measured in milliseconds.
- *
- * Please, consider using {@link Executables#getEstimatedDurationFor(Executable)}
- * to protected against AbstractMethodErrors!
- *
- * @return -1 if it's impossible to estimate.
- * @since 1.383
- */
- long getEstimatedDuration();
- /**
- * Used to render the HTML. Should be a human readable text of what this executable is.
- */
- @Override String toString();
- }
- /**
- * Item in a queue.
- */
- @ExportedBean(defaultVisibility = 999)
- public static abstract class Item extends Actionable {
- /**
- * VM-wide unique ID that tracks the {@link Task} as it moves through different stages
- * in the queue (each represented by different subtypes of {@link Item}.
- */
- //TODO: review and check whether we can do it private
- public final int id;
-
- /**
- * Project to be built.
- */
- //TODO: review and check whether we can do it private
- @Exported
- public final Task task;
- private /*almost final*/ transient FutureImpl future;
- /**
- * Build is blocked because another build is in progress,
- * required {@link Resource}s are not available, or otherwise blocked
- * by {@link Task#isBuildBlocked()}.
- */
- @Exported
- public boolean isBlocked() { return this instanceof BlockedItem; }
- /**
- * Build is waiting the executor to become available.
- * This flag is only used in {@link Queue#getItems()} for
- * 'pseudo' items that are actually not really in the queue.
- */
- @Exported
- public boolean isBuildable() { return this instanceof BuildableItem; }
- /**
- * True if the item is starving for an executor for too long.
- */
- @Exported
- public boolean isStuck() { return false; }
- /**
- * Can be used to wait for the completion (either normal, abnormal, or cancellation) of the {@link Task}.
- * <p>
- * Just like {@link #id}, the same object tracks various stages of the queue.
- */
- public Future<Executable> getFuture() { return future; }
- /**
- * Convenience method that returns a read only view of the {@link Cause}s associated with this item in the queue.
- *
- * @return can be empty but never null
- * @since 1.343
- */
- public final List<Cause> getCauses() {
- CauseAction ca = getAction(CauseAction.class);
- if (ca!=null)
- return Collections.unmodifiableList(ca.getCauses());
- return Collections.emptyList();
- }
- protected Item(Task task, List<Action> actions, int id, FutureImpl future) {
- this.task = task;
- this.id = id;
- this.future = future;
- for (Action action: actions) addAction(action);
- }
-
- protected Item(Item item) {
- this(item.task, item.getActions(), item.id, item.future);
- }
- public int getId() {
- return id;
- }
- public Task getTask() {
- return task;
- }
- /**
- * Gets a human-readable status message describing why it's in the queue.
- */
- @Exported
- public final String getWhy() {
- CauseOfBlockage cob = getCauseOfBlockage();
- return cob!=null ? cob.getShortDescription() : null;
- }
- /**
- * Gets an object that describes why this item is in the queue.
- */
- //TODO: review and check whether we can do it private
- public abstract CauseOfBlockage getCauseOfBlockage();
- /**
- * Gets a human-readable message about the parameters of this item
- * @return String
- */
- @Exported
- public String getParams() {
- StringBuilder s = new StringBuilder();
- for(Action action : getActions()) {
- if(action instanceof ParametersAction) {
- ParametersAction pa = (ParametersAction)action;
- for (ParameterValue p : pa.getParameters()) {
- s.append('\n').append(p.getShortDescription());
- }
- }
- }
- return s.toString();
- }
-
- public boolean hasCancelPermission() {
- return task.hasAbortPermission();
- }
-
- public String getDisplayName() {
- // TODO Auto-generated method stub
- return null;
- }
- public String getSearchUrl() {
- // TODO Auto-generated method stub
- return null;
- }
- /**
- * Called from queue.jelly.
- */
- public HttpResponse doCancelQueue() throws IOException, ServletException {
- Hudson.getInstance().getQueue().cancel(this);
- return HttpResponses.forwardToPreviousPage();
- }
- /**
- * Participates in the cancellation logic to set the {@link #future} accordingly.
- */
- /*package*/ void onCancelled() {
- future.setAsCancelled();
- }
- private Object readResolve() {
- this.future = new FutureImpl(task);
- return this;
- }
- @Override
- public String toString() {
- return getClass().getName()+':'+task.toString();
- }
- }
-
- /**
- * An optional interface for actions on Queue.Item.
- * Lets the action cooperate in queue management.
- *
- * @since 1.300-ish.
- */
- public interface QueueAction extends Action {
- /**
- * Returns whether the new item should be scheduled.
- * An action should return true if the associated task is 'different enough' to warrant a separate execution.
- */
- public boolean shouldSchedule(List<Action> actions);
- }
- /**
- * Extension point for deciding if particular job should be scheduled or not.
- *
- * <p>
- * This handler is consulted every time someone tries to submit a task to the queue.
- * If any of the registered handlers returns false, the task will not be added
- * to the queue, and the task will never get executed.
- *
- * <p>
- * This extension point is still a subject to change, as we are seeking more
- * comprehensive Queue pluggability. See HUDSON-2072.
- *
- * @since 1.316
- */
- public static abstract class QueueDecisionHandler implements ExtensionPoint {
- /**
- * Returns whether the new item should be scheduled.
- */
- public abstract boolean shouldSchedule(Task p, List<Action> actions);
-
- /**
- * All registered {@link QueueDecisionHandler}s
- * @return
- */
- public static ExtensionList<QueueDecisionHandler> all() {
- return Hudson.getInstance().getExtensionList(QueueDecisionHandler.class);
- }
- }
-
- /**
- * {@link Item} in the {@link Queue#waitingList} stage.
- */
- public static final class WaitingItem extends Item implements Comparable<WaitingItem> {
- private static final AtomicInteger COUNTER = new AtomicInteger(0);
-
- /**
- * This item can be run after this time.
- */
- //TODO: review and check whether we can do it private
- @Exported
- public Calendar timestamp;
- public Calendar getTimestamp() {
- return timestamp;
- }
- public WaitingItem(Calendar timestamp, Task project, List<Action> actions) {
- super(project, actions, COUNTER.incrementAndGet(), new FutureImpl(project));
- this.timestamp = timestamp;
- }
-
- public int compareTo(WaitingItem that) {
- int r = this.timestamp.getTime().compareTo(that.timestamp.getTime());
- if (r != 0) return r;
- return this.id - that.id;
- }
- public CauseOfBlockage getCauseOfBlockage() {
- long diff = timestamp.getTimeInMillis() - System.currentTimeMillis();
- if (diff > 0)
- return CauseOfBlockage.fromMessage(Messages._Queue_InQuietPeriod(Util.getTimeSpanString(diff)));
- else
- return CauseOfBlockage.fromMessage(Messages._Queue_Unknown());
- }
- }
- /**
- * Common part between {@link BlockedItem} and {@link BuildableItem}.
- */
- public static abstract class NotWaitingItem extends Item {
- /**
- * When did this job exit the {@link Queue#waitingList} phase?
- */
- @Exported
- public final long buildableStartMilliseconds;
- protected NotWaitingItem(WaitingItem wi) {
- super(wi);
- buildableStartMilliseconds = System.currentTimeMillis();
- }
- protected NotWaitingItem(NotWaitingItem ni) {
- super(ni);
- buildableStartMilliseconds = ni.buildableStartMilliseconds;
- }
- }
- /**
- * {@link Item} in the {@link Queue#blockedProjects} stage.
- */
- public final class BlockedItem extends NotWaitingItem {
- public BlockedItem(WaitingItem wi) {
- super(wi);
- }
- public BlockedItem(NotWaitingItem ni) {
- super(ni);
- }
- public CauseOfBlockage getCauseOfBlockage() {
- ResourceActivity r = getBlockingActivity(task);
- if (r != null) {
- if (r == task) // blocked by itself, meaning another build is in progress
- return CauseOfBlockage.fromMessage(Messages._Queue_InProgress());
- return CauseOfBlockage.fromMessage(Messages._Queue_BlockedBy(r.getDisplayName()));
- }
- return task.getCauseOfBlockage();
- }
- }
- /**
- * {@link Item} in the {@link Queue#buildables} stage.
- */
- public final static class BuildableItem extends NotWaitingItem {
- public BuildableItem(WaitingItem wi) {
- super(wi);
- }
- public BuildableItem(NotWaitingItem ni) {
- super(ni);
- }
- public CauseOfBlockage getCauseOfBlockage() {
- Hudson hudson = Hudson.getInstance();
- if(ifBlockedByHudsonShutdown(task))
- return CauseOfBlockage.fromMessage(Messages._Queue_HudsonIsAboutToShutDown());
- Label label = task.getAssignedLabel();
- if (hudson.getNodes().isEmpty())
- label = null; // no master/slave. pointless to talk about nodes
- if (label != null) {
- if (label.isOffline()) {
- Set<Node> nodes = label.getNodes();
- if (nodes.size() != 1) return new BecauseLabelIsOffline(label);
- else return new BecauseNodeIsOffline(nodes.iterator().next());
- }
- }
- if(label==null)
- return CauseOfBlockage.fromMessage(Messages._Queue_WaitingForNextAvailableExecutor());
- Set<Node> nodes = label.getNodes();
- if (nodes.size() != 1) return new BecauseLabelIsBusy(label);
- else return new BecauseNodeIsBusy(nodes.iterator().next());
- }
- @Override
- public boolean isStuck() {
- Label label = task.getAssignedLabel();
- if(label!=null && label.isOffline())
- // no executor online to process this job. definitely stuck.
- return true;
- long d = task.getEstimatedDuration();
- long elapsed = System.currentTimeMillis()-buildableStartMilliseconds;
- if(d>=0) {
- // if we were running elsewhere, we would have done this build ten times.
- return elapsed > Math.max(d,60000L)*10;
- } else {
- // more than a day in the queue
- return TimeUnit2.MILLISECONDS.toHours(elapsed)>24;
- }
- }
- }
- private static final Logger LOGGER = Logger.getLogger(Queue.class.getName());
- /**
- * This {@link XStream} instance is used to persist {@link Task}s.
- */
- public static final XStream XSTREAM = new XStream2();
- static {
- XSTREAM.registerConverter(new AbstractSingleValueConverter() {
- @Override
- @SuppressWarnings("unchecked")
- public boolean canConvert(Class klazz) {
- return hudson.model.Item.class.isAssignableFrom(klazz);
- }
- @Override
- public Object fromString(String string) {
- Object item = Hudson.getInstance().getItemByFullName(string);
- if(item==null) throw new NoSuchElementException("No such job exists: "+string);
- return item;
- }
- @Override
- public String toString(Object item) {
- return ((hudson.model.Item) item).getFullName();
- }
- });
- XSTREAM.registerConverter(new AbstractSingleValueConverter() {
- @SuppressWarnings("unchecked")
- @Override
- public boolean canConvert(Class klazz) {
- return Run.class.isAssignableFrom(klazz);
- }
- @Override
- public Object fromString(String string) {
- String[] split = string.split("#");
- String projectName = split[0];
- int buildNumber = Integer.parseInt(split[1]);
- Job<?,?> job = (Job<?,?>) Hudson.getInstance().getItemByFullName(projectName);
- if(job==null) throw new NoSuchElementException("No such job exists: "+projectName);
- Run<?,?> run = job.getBuildByNumber(buildNumber);
- if(run==null) throw new NoSuchElementException("No such build: "+string);
- return run;
- }
- @Override
- public String toString(Object object) {
- Run<?,?> run = (Run<?,?>) object;
- return run.getParent().getFullName() + "#" + run.getNumber();
- }
- });
- }
- /**
- * Regularly invokes {@link Queue#maintain()} and clean itself up when
- * {@link Queue} gets GC-ed.
- */
- private static class MaintainTask extends SafeTimerTask {
- private final WeakReference<Queue> queue;
- MaintainTask(Queue queue) {
- this.queue = new WeakReference<Queue>(queue);
- long interval = 5 * Timer.ONE_SECOND;
- Trigger.timer.schedule(this, interval, interval);
- }
- protected void doRun() {
- Queue q = queue.get();
- if (q != null)
- q.maintain();
- else
- cancel();
- }
- }
-
- /**
- * {@link ArrayList} of {@link Item} with more convenience methods.
- */
- private static class ItemList<T extends Item> extends ArrayList<T> {
- public T get(Task task) {
- for (T item: this) {
- if (item.task == task) {
- return item;
- }
- }
- return null;
- }
-
- public List<T> getAll(Task task) {
- List<T> result = new ArrayList<T>();
- for (T item: this) {
- if (item.task == task) {
- result.add(item);
- }
- }
- return result;
- }
-
- public boolean containsKey(Task task) {
- return get(task) != null;
- }
-
- public T remove(Task task) {
- Iterator<T> it = iterator();
- while (it.hasNext()) {
- T t = it.next();
- if (t.task == task) {
- it.remove();
- return t;
- }
- }
- return null;
- }
-
- public void put(Task task, T item) {
- assert item.task == task;
- add(item);
- }
-
- public ItemList<T> values() {
- return this;
- }
- /**
- * Works like {@link #remove(Task)} but also marks the {@link Item} as cancelled.
- */
- public T cancel(Task p) {
- T x = remove(p);
- if(x!=null) x.onCancelled();
- return x;
- }
- /**
- * Works like {@link #remove(Object)} but also marks the {@link Item} as cancelled.
- */
- public boolean cancel(Item t) {
- boolean r = remove(t);
- if(r) t.onCancelled();
- return r;
- }
- public void cancelAll() {
- for (T t : this)
- t.onCancelled();
- clear();
- }
- }
- @CLIResolver
- public static Queue getInstance() {
- return Hudson.getInstance().getQueue();
- }
- /**
- * Restores the queue content during the start up.
- */
- @Initializer(after=JOB_LOADED)
- public static void init(Hudson h) {
- h.getQueue().load();
- }
- }