Queue.java 34.9 KB
Newer Older
K
kohsuke 已提交
1 2
package hudson.model;

3
import hudson.BulkChange;
4
import hudson.Util;
K
kohsuke 已提交
5
import hudson.XmlFile;
K
kohsuke 已提交
6
import hudson.model.Node.Mode;
7 8
import hudson.triggers.SafeTimerTask;
import hudson.triggers.Trigger;
K
kohsuke 已提交
9
import hudson.util.OneShotEvent;
10
import hudson.util.TimeUnit2;
11
import hudson.util.XStream2;
K
kohsuke 已提交
12

13 14 15 16 17
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
18
import java.lang.ref.WeakReference;
K
kohsuke 已提交
19 20 21 22 23 24 25 26
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
27
import java.util.NoSuchElementException;
K
kohsuke 已提交
28 29
import java.util.Set;
import java.util.TreeSet;
S
stephenconnolly 已提交
30
import java.util.Map.Entry;
K
kohsuke 已提交
31 32 33
import java.util.logging.Level;
import java.util.logging.Logger;

K
kohsuke 已提交
34
import javax.management.timer.Timer;
35
import javax.servlet.ServletException;
K
kohsuke 已提交
36 37

import org.acegisecurity.AccessDeniedException;
38 39
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
K
kohsuke 已提交
40 41 42 43 44 45
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.converters.basic.AbstractSingleValueConverter;

K
kohsuke 已提交
46 47
/**
 * Build queue.
48 49
 *
 * <p>
50 51
 * 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}
52
 * so that we can keep track of additional data used for deciding what to exeucte when.
53 54
 *
 * <p>
55 56 57 58 59 60 61 62
 * Items in queue goes through several stages, as depicted below:
 * <pre>
 * (enter) --> waitingList --+--> blockedProjects
 *                           |        ^
 *                           |        |
 *                           |        v
 *                           +--> buildables ---> (executed)
 * </pre>
63 64
 *
 * <p>
65 66 67
 * 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.
 *
K
kohsuke 已提交
68 69
 * @author Kohsuke Kawaguchi
 */
70
@ExportedBean
71
public class Queue extends ResourceController implements Saveable {
K
kohsuke 已提交
72
    /**
73
     * Items that are waiting for its quiet period to pass.
74 75
     *
     * <p>
K
kohsuke 已提交
76 77 78
     * This consists of {@link Item}s that cannot be run yet
     * because its time has not yet come.
     */
79
    private final Set<WaitingItem> waitingList = new TreeSet<WaitingItem>();
K
kohsuke 已提交
80 81 82

    /**
     * {@link Project}s that can be built immediately
83 84 85
     * but blocked because another build is in progress,
     * required {@link Resource}s are not available, or otherwise blocked
     * by {@link Task#isBuildBlocked()}.
86 87
     *
     * <p>
88 89
     * Conceptually a set of {@link BlockedItem}, but we often need to look up
     * {@link BlockedItem} from {@link Task}, so organized as a map.
K
kohsuke 已提交
90
     */
91
    private final Map<Task,BlockedItem> blockedProjects = new HashMap<Task,BlockedItem>();
K
kohsuke 已提交
92 93 94 95

    /**
     * {@link Project}s that can be built immediately
     * that are waiting for available {@link Executor}.
96 97
     *
     * <p>
98 99 100
     * Conceptually, this is a list of {@link BuildableItem} (FIFO list, not a set, so that
     * the item doesn't starve in the queue), but we often need to look up
     * {@link BuildableItem} from {@link Task}, so organized as a {@link LinkedHashMap}.
K
kohsuke 已提交
101
     */
102
    private final LinkedHashMap<Task,BuildableItem> buildables = new LinkedHashMap<Task,BuildableItem>();
103

K
kohsuke 已提交
104 105 106
    /**
     * Data structure created for each idle {@link Executor}.
     * This is an offer from the queue to an executor.
107 108
     *
     * <p>
109
     * It eventually receives a {@link #item} to build.
K
kohsuke 已提交
110 111 112 113 114 115 116 117 118 119 120 121 122
     */
    private static class JobOffer {
        final Executor executor;

        /**
         * Used to wake up an executor, when it has an offered
         * {@link Project} to build.
         */
        final OneShotEvent event = new OneShotEvent();
        /**
         * The project that this {@link Executor} is going to build.
         * (Or null, in which case event is used to trigger a queue maintenance.)
         */
123
        BuildableItem item;
K
kohsuke 已提交
124 125 126 127 128

        public JobOffer(Executor executor) {
            this.executor = executor;
        }

129 130 131
        public void set(BuildableItem p) {
            assert this.item == null;
            this.item = p;
K
kohsuke 已提交
132 133 134 135
            event.signal();
        }

        public boolean isAvailable() {
136
            return item == null && !executor.getOwner().isOffline() && executor.getOwner().isAcceptingTasks();
K
kohsuke 已提交
137 138 139 140 141 142 143
        }

        public Node getNode() {
            return executor.getOwner().getNode();
        }

        public boolean isNotExclusive() {
144
            return getNode().getMode() == Mode.NORMAL;
K
kohsuke 已提交
145 146 147
        }
    }

S
stephenconnolly 已提交
148 149 150
    /**
     * The executors that are currently parked while waiting for a job to run.
     */
151
    private final Map<Executor, JobOffer> parked = new HashMap<Executor, JobOffer>();
K
kohsuke 已提交
152

153 154 155 156 157 158
    public Queue() {
        // 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);
    }

K
kohsuke 已提交
159 160 161 162 163
    /**
     * Loads the queue contents that was {@link #save() saved}.
     */
    public synchronized void load() {
        try {
K
TAB->WS  
kohsuke 已提交
164
            // first try the old format
K
kohsuke 已提交
165
            File queueFile = getQueueFile();
K
kohsuke 已提交
166 167
            if (queueFile.exists()) {
                BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(queueFile)));
K
TAB->WS  
kohsuke 已提交
168 169 170 171
                String line;
                while ((line = in.readLine()) != null) {
                    AbstractProject j = Hudson.getInstance().getItemByFullName(line, AbstractProject.class);
                    if (j != null)
M
mindless 已提交
172
                        j.scheduleBuild();
K
TAB->WS  
kohsuke 已提交
173 174 175 176 177 178 179
                }
                in.close();
                // discard the queue file now that we are done
                queueFile.delete();
            } else {
                queueFile = getXMLQueueFile();
                if (queueFile.exists()) {
180
                    List<Task> tasks = (List<Task>) new XmlFile(XSTREAM, queueFile).read();
K
TAB->WS  
kohsuke 已提交
181 182 183
                    for (Task task : tasks) {
                        add(task, 0);
                    }
184 185 186 187 188 189 190 191 192

                    // 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);
K
TAB->WS  
kohsuke 已提交
193 194 195
                    queueFile.delete();
                }
            }
196 197
        } catch (IOException e) {
            LOGGER.log(Level.WARNING, "Failed to load the queue file " + getQueueFile(), e);
K
kohsuke 已提交
198 199 200 201 202 203 204
        }
    }

    /**
     * Persists the queue contents to the disk.
     */
    public synchronized void save() {
205 206
        if(BulkChange.contains(this))  return;
        
K
kohsuke 已提交
207 208 209
        // write out the tasks on the queue
    	ArrayList<Task> tasks = new ArrayList<Task>();
    	for (Item item: getItems()) {
K
TAB->WS  
kohsuke 已提交
210
    	    tasks.add(item.task);
K
kohsuke 已提交
211 212
    	}
    	
K
kohsuke 已提交
213
        try {
214
            new XmlFile(XSTREAM, getXMLQueueFile()).write(tasks);
215 216
        } catch (IOException e) {
            LOGGER.log(Level.WARNING, "Failed to write out the queue file " + getQueueFile(), e);
K
kohsuke 已提交
217 218 219
        }
    }

220 221 222 223 224 225 226 227 228 229
    /**
     * Wipes out all the items currently in the queue, as if all of them are cancelled at once.
     */
    public synchronized void clear() {
        waitingList.clear();
        blockedProjects.clear();
        buildables.clear();
        scheduleMaintenance();
    }

K
kohsuke 已提交
230
    private File getQueueFile() {
K
TAB->WS  
kohsuke 已提交
231 232
        return new File(Hudson.getInstance().getRootDir(), "queue.txt");
    }
K
kohsuke 已提交
233

234
    /*package*/ File getXMLQueueFile() {
K
TAB->WS  
kohsuke 已提交
235 236
        return new File(Hudson.getInstance().getRootDir(), "queue.xml");
    }
K
kohsuke 已提交
237 238 239

    /**
     * Schedule a new build for this project.
240
     *
241 242 243
     * @return true if the project is actually added to the queue.
     *         false if the queue contained it and therefore the add()
     *         was noop
K
kohsuke 已提交
244
     */
245 246
    public boolean add(AbstractProject p) {
        return add(p, p.getQuietPeriod());
247 248 249 250
    }

    /**
     * Schedules a new build with a custom quiet period.
251 252
     *
     * <p>
K
kohsuke 已提交
253 254
     * Left for backward compatibility with &lt;1.114.
     *
255 256
     * @since 1.105
     */
257 258
    public synchronized boolean add(AbstractProject p, int quietPeriod) {
        return add((Task) p, quietPeriod);
K
kohsuke 已提交
259 260 261 262 263
    }

    /**
     * Schedules an execution of a task.
     *
264 265 266
     * @param quietPeriod Number of seconds that the task will be placed in queue.
     *                    Useful when the same task is likely scheduled for multiple
     *                    times.
K
kohsuke 已提交
267 268
     * @since 1.114
     */
269 270 271 272
    public synchronized boolean add(Task p, int quietPeriod) {
        Item item = getItem(p);
        Calendar due = new GregorianCalendar();
        due.add(Calendar.SECOND, quietPeriod);
273
        if (item != null) {
274 275 276 277 278 279
            if (!(item instanceof WaitingItem))
                // already in the blocked or buildable stage
                // no need to requeue
                return false;

            WaitingItem wi = (WaitingItem) item;
K
kohsuke 已提交
280

281 282 283
            if(quietPeriod<=0) {
                // the user really wants to build now, and they mean NOW.
                // so let's pull in the timestamp if we can.
284
                if (wi.timestamp.before(due))
285 286 287
                    return false;
            } else {
                // otherwise we do the normal quiet period implementation
288
                if (wi.timestamp.after(due))
289
                    return false;
290
                // quiet period timer reset. start the period over again
291
            }
292 293 294 295 296

            // waitingList is sorted, so when we change a timestamp we need to maintain order
            waitingList.remove(wi);
            wi.timestamp = due;
            waitingList.add(wi);
297
        } else {
K
kohsuke 已提交
298
            LOGGER.fine(p.getFullDisplayName() + " added to queue");
299

300
            // put the item in the queue
301
            waitingList.add(new WaitingItem(due,p));
K
kohsuke 已提交
302

303
        }
K
kohsuke 已提交
304
        scheduleMaintenance();   // let an executor know that a new item is in the queue.
305
        return true;
K
kohsuke 已提交
306 307
    }

K
kohsuke 已提交
308 309 310
    /**
     * Cancels the item in the queue.
     *
311 312
     * @return true if the project was indeed in the queue and was removed.
     *         false if this was no-op.
K
kohsuke 已提交
313
     */
K
kohsuke 已提交
314
    public synchronized boolean cancel(Task p) {
K
kohsuke 已提交
315
        LOGGER.fine("Cancelling " + p.getFullDisplayName());
K
kohsuke 已提交
316 317
        for (Iterator<WaitingItem> itr = waitingList.iterator(); itr.hasNext();) {
            Item item = itr.next();
K
kohsuke 已提交
318
            if (item.task.equals(p)) {
K
kohsuke 已提交
319
                itr.remove();
K
kohsuke 已提交
320
                return true;
K
kohsuke 已提交
321 322
            }
        }
K
kohsuke 已提交
323
        // use bitwise-OR to make sure that both branches get evaluated all the time
324
        return blockedProjects.remove(p)!=null | buildables.remove(p)!=null;
K
kohsuke 已提交
325 326 327
    }

    public synchronized boolean isEmpty() {
328
        return waitingList.isEmpty() && blockedProjects.isEmpty() && buildables.isEmpty();
K
kohsuke 已提交
329 330
    }

331
    private synchronized WaitingItem peek() {
332
        return waitingList.iterator().next();
K
kohsuke 已提交
333 334 335 336 337
    }

    /**
     * Gets a snapshot of items in the queue.
     */
338
    @Exported(inline=true)
K
kohsuke 已提交
339
    public synchronized Item[] getItems() {
340 341 342
        Item[] r = new Item[waitingList.size() + blockedProjects.size() + buildables.size()];
        waitingList.toArray(r);
        int idx = waitingList.size();
343 344 345 346
        for (BlockedItem p : blockedProjects.values())
            r[idx++] = p;
        for (BuildableItem p : buildables.values())
            r[idx++] = p;
K
kohsuke 已提交
347 348 349
        return r;
    }

K
kohsuke 已提交
350 351 352
    /**
     * Gets all the {@link BuildableItem}s that are waiting for an executor in the given {@link Computer}.
     */
353 354 355 356
    public synchronized List<BuildableItem> getBuildableItems(Computer c) {
        List<BuildableItem> result = new ArrayList<BuildableItem>();
        for (BuildableItem p : buildables.values()) {
            Label l = p.task.getAssignedLabel();
357 358 359 360 361
            if (l != null) {
                // if a project has assigned label, it can be only built on it
                if (!l.contains(c.getNode()))
                    continue;
            }
362
            result.add(p);
363
        }
364
        return result;
365 366
    }

K
kohsuke 已提交
367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384
    /**
     * Gets the snapshot of {@link #buildables}.
     */
    public synchronized List<BuildableItem> getBuildableItems() {
        return new ArrayList<BuildableItem>(buildables.values());
    }

    /**
     * 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++;
        return r;
    }

K
kohsuke 已提交
385 386 387 388 389
    /**
     * Gets the information about the queue item for the given project.
     *
     * @return null if the project is not in the queue.
     */
390 391
    public synchronized Item getItem(Task t) {
        BlockedItem bp = blockedProjects.get(t);
392
        if (bp!=null)
393 394
            return bp;
        BuildableItem bi = buildables.get(t);
395
        if(bi!=null)
396 397
            return bi;

398
        for (Item item : waitingList) {
399
            if (item.task == t)
K
kohsuke 已提交
400 401 402 403 404
                return item;
        }
        return null;
    }

405 406
    /**
     * Left for backward compatibility.
407
     *
408 409
     * @see #getItem(Task)
    public synchronized Item getItem(AbstractProject p) {
410
        return getItem((Task) p);
411
    }
K
kohsuke 已提交
412
     */
413

K
kohsuke 已提交
414
    /**
K
kohsuke 已提交
415
     * Returns true if this queue contains the said project.
K
kohsuke 已提交
416
     */
417 418
    public synchronized boolean contains(Task t) {
        if (blockedProjects.containsKey(t) || buildables.containsKey(t))
K
kohsuke 已提交
419
            return true;
420
        for (Item item : waitingList) {
421
            if (item.task == t)
K
kohsuke 已提交
422 423 424 425 426 427 428
                return true;
        }
        return false;
    }

    /**
     * Called by the executor to fetch something to build next.
429
     * <p>
K
kohsuke 已提交
430 431
     * This method blocks until a next project becomes buildable.
     */
432
    public Task pop() throws InterruptedException {
K
kohsuke 已提交
433
        final Executor exec = Executor.currentExecutor();
434

K
kohsuke 已提交
435
        try {
436
            while (true) {
K
kohsuke 已提交
437 438 439
                final JobOffer offer = new JobOffer(exec);
                long sleep = -1;

440
                synchronized (this) {
K
kohsuke 已提交
441 442
                    // consider myself parked
                    assert !parked.containsKey(exec);
443
                    parked.put(exec, offer);
K
kohsuke 已提交
444

K
kohsuke 已提交
445
                    // reuse executor thread to do a queue maintenance.
K
kohsuke 已提交
446 447 448 449 450
                    // at the end of this we get all the buildable jobs
                    // in the buildables field.
                    maintain();

                    // allocate buildable jobs to executors
451
                    Iterator<BuildableItem> itr = buildables.values().iterator();
452
                    while (itr.hasNext()) {
453
                        BuildableItem p = itr.next();
454 455

                        // one last check to make sure this build is not blocked.
456
                        if (isBuildBlocked(p.task)) {
457
                            itr.remove();
458
                            blockedProjects.put(p.task,new BlockedItem(p));
459 460
                            continue;
                        }
461

462
                        JobOffer runner = choose(p.task);
463
                        if (runner == null)
K
kohsuke 已提交
464 465 466 467 468 469 470 471 472 473 474 475 476 477 478
                            // 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.
                        runner.set(p);
                        itr.remove();
                    }

                    // 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.

479
                    if (!waitingList.isEmpty()) {
K
kohsuke 已提交
480
                        // wait until the first item in the queue is due
481 482
                        sleep = peek().timestamp.getTimeInMillis() - new GregorianCalendar().getTimeInMillis();
                        if (sleep < 100) sleep = 100;    // avoid wait(0)
K
kohsuke 已提交
483 484 485 486 487
                    }
                }

                // this needs to be done outside synchronized block,
                // so that executors can maintain a queue while others are sleeping
488
                if (sleep == -1)
K
kohsuke 已提交
489 490 491 492
                    offer.event.block();
                else
                    offer.event.block(sleep);

493
                synchronized (this) {
494
                    // retract the offer object
495
                    assert parked.get(exec) == offer;
496 497
                    parked.remove(exec);

K
kohsuke 已提交
498
                    // am I woken up because I have a project to build?
499 500
                    if (offer.item != null) {
                        LOGGER.fine("Pop returning " + offer.item + " for " + exec.getName());
K
kohsuke 已提交
501
                        // if so, just build it
502
                        return offer.item.task;
K
kohsuke 已提交
503 504 505 506 507
                    }
                    // otherwise run a queue maintenance
                }
            }
        } finally {
508
            synchronized (this) {
K
kohsuke 已提交
509
                // remove myself from the parked list
510
                JobOffer offer = parked.remove(exec);
511
                if (offer != null && offer.item != null) {
512 513 514 515 516
                    // we are already assigned a project,
                    // ask for someone else to build it.
                    // note that while this thread is waiting for CPU
                    // someone else can schedule this build again,
                    // so check the contains method first.
517
                    if (!contains(offer.item.task))
518
                        buildables.put(offer.item.task,offer.item);
K
kohsuke 已提交
519
                }
520 521 522 523 524 525

                // 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();
K
kohsuke 已提交
526 527 528 529 530
            }
        }
    }

    /**
K
kohsuke 已提交
531
     * Chooses the executor to carry out the build for the given project.
K
kohsuke 已提交
532
     *
533
     * @return null if no {@link Executor} can run it.
K
kohsuke 已提交
534
     */
535
    private JobOffer choose(Task p) {
536
        if (Hudson.getInstance().isQuietingDown()) {
K
kohsuke 已提交
537 538 539 540 541
            // if we are quieting down, don't run anything so that
            // all executors will be free.
            return null;
        }

542
        Label l = p.getAssignedLabel();
543
        if (l != null) {
544
            // if a project has assigned label, it can be only built on it
K
kohsuke 已提交
545
            for (JobOffer offer : parked.values()) {
546
                if (offer.isAvailable() && l.contains(offer.getNode()))
K
kohsuke 已提交
547 548 549 550 551
                    return offer;
            }
            return null;
        }

552
        // if we are a large deployment, then we will favor slaves
K
kohsuke 已提交
553
        boolean isLargeHudson = Hudson.getInstance().getNodes().size() > 10;
554

555
        // otherwise let's see if the last node where this project was built is available
K
kohsuke 已提交
556 557
        // it has up-to-date workspace, so that's usually preferable.
        // (but we can't use an exclusive node)
558
        Node n = p.getLastBuiltOn();
559
        if (n != null && n.getMode() == Mode.NORMAL) {
K
kohsuke 已提交
560
            for (JobOffer offer : parked.values()) {
561 562
                if (offer.isAvailable() && offer.getNode() == n) {
                    if (isLargeHudson && offer.getNode() instanceof Slave)
S
stephenconnolly 已提交
563
                        // but if we are a large Hudson, then we really do want to keep the master free from builds
564
                        continue;
K
kohsuke 已提交
565
                    return offer;
566
                }
K
kohsuke 已提交
567 568 569 570 571 572
            }
        }

        // duration of a build on a slave tends not to have an impact on
        // the master/slave communication, so that means we should favor
        // running long jobs on slaves.
573 574
        // Similarly if we have many slaves, master should be made available
        // for HTTP requests and coordination as much as possible
575
        if (isLargeHudson || p.getEstimatedDuration() > 15 * 60 * 1000) {
K
kohsuke 已提交
576 577
            // consider a long job to be > 15 mins
            for (JobOffer offer : parked.values()) {
578
                if (offer.isAvailable() && offer.getNode() instanceof Slave && offer.isNotExclusive())
K
kohsuke 已提交
579 580 581 582 583 584
                    return offer;
            }
        }

        // lastly, just look for any idle executor
        for (JobOffer offer : parked.values()) {
585
            if (offer.isAvailable() && offer.isNotExclusive())
K
kohsuke 已提交
586 587 588 589 590 591 592 593 594
                return offer;
        }

        // nothing available
        return null;
    }

    /**
     * Checks the queue and runs anything that can be run.
595 596
     *
     * <p>
K
kohsuke 已提交
597
     * When conditions are changed, this method should be invoked.
598
     * <p>
K
kohsuke 已提交
599 600 601 602 603 604 605
     * 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()) {
606
            if (av.getValue().item == null) {
K
kohsuke 已提交
607 608 609 610 611 612
                av.getValue().event.signal();
                return;
            }
        }
    }

613 614 615
    /**
     * Checks if the given task is blocked.
     */
616
    private boolean isBuildBlocked(Task t) {
617 618 619
        return t.isBuildBlocked() || !canRun(t.getResourceList());
    }

K
kohsuke 已提交
620 621

    /**
K
kohsuke 已提交
622
     * Queue maintenance.
623
     * <p>
624
     * Move projects between {@link #waitingList}, {@link #blockedProjects}, and {@link #buildables}
K
kohsuke 已提交
625 626
     * appropriately.
     */
627
    public synchronized void maintain() {
628 629
        if (LOGGER.isLoggable(Level.FINE))
            LOGGER.fine("Queue maintenance started " + this);
630

631
        Iterator<BlockedItem> itr = blockedProjects.values().iterator();
632
        while (itr.hasNext()) {
633 634
            BlockedItem p = itr.next();
            if (!isBuildBlocked(p.task)) {
K
kohsuke 已提交
635
                // ready to be executed
K
kohsuke 已提交
636
                LOGGER.fine(p.task.getFullDisplayName() + " no longer blocked");
K
kohsuke 已提交
637
                itr.remove();
638
                buildables.put(p.task,new BuildableItem(p));
K
kohsuke 已提交
639 640 641
            }
        }

642
        while (!waitingList.isEmpty()) {
643
            WaitingItem top = peek();
K
kohsuke 已提交
644

645
            if (!top.timestamp.before(new GregorianCalendar()))
K
kohsuke 已提交
646 647
                return; // finished moving all ready items from queue

648
            Task p = top.task;
649
            if (!isBuildBlocked(p)) {
K
kohsuke 已提交
650
                // ready to be executed immediately
651
                waitingList.remove(top);
K
kohsuke 已提交
652
                LOGGER.fine(p.getFullDisplayName() + " ready to build");
653
                buildables.put(p,new BuildableItem(top));
K
kohsuke 已提交
654
            } else {
655
                // this can't be built now because another build is in progress
K
kohsuke 已提交
656
                // set this project aside.
657
                waitingList.remove(top);
K
kohsuke 已提交
658
                LOGGER.fine(p.getFullDisplayName() + " is blocked");
659
                blockedProjects.put(p,new BlockedItem(top));
K
kohsuke 已提交
660 661 662 663
            }
        }
    }

664 665 666 667
    public Api getApi() {
        return new Api(this);
    }

K
kohsuke 已提交
668 669
    /**
     * Task whose execution is controlled by the queue.
670
     *
671
     * <p>
K
kohsuke 已提交
672 673 674
     * {@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.
675 676 677 678
     *
     * <p>
     * Pending {@link Task}s are persisted when Hudson shuts down, so
     * it needs to be persistable.
K
kohsuke 已提交
679
     */
680
    public interface Task extends ModelObject, ResourceActivity {
681
        /**
682 683 684
         * If this task needs to be run on a node with a particular label,
         * return that {@link Label}. Otherwise null, indicating
         * it can run on anywhere.
685
         */
686
        Label getAssignedLabel();
687 688 689 690 691 692 693 694 695 696 697

        /**
         * If the previous execution of this task run on a certain node
         * and this task prefers to run on the same node, return that.
         * Otherwise null.
         */
        Node getLastBuiltOn();

        /**
         * Returns true if the execution should be blocked
         * for temporary reasons.
698 699
         *
         * <p>
K
kohsuke 已提交
700 701
         * This can be used to define mutual exclusion that goes beyond
         * {@link #getResourceList()}.
702 703 704 705 706 707 708 709 710 711 712
         */
        boolean isBuildBlocked();

        /**
         * When {@link #isBuildBlocked()} is true, this method returns
         * human readable description of why the build is blocked.
         * Used for HTML rendering.
         */
        String getWhyBlocked();

        /**
K
kohsuke 已提交
713
         * Unique name of this task.
K
kohsuke 已提交
714
         *
K
kohsuke 已提交
715 716
         * <p>
         * This method is no longer used, left here for compatibility. Just return {@link #getDisplayName()}.
717 718 719
         */
        String getName();

720 721 722 723 724
        /**
         * @see hudson.model.Item#getFullDisplayName()
         */
        String getFullDisplayName();

725 726 727 728
        /**
         * Estimate of how long will it take to execute this task.
         * Measured in milliseconds.
         *
729
         * @return -1 if it's impossible to estimate.
730 731 732
         */
        long getEstimatedDuration();

K
kohsuke 已提交
733
        /**
734
         * Creates {@link Executable}, which performs the actual execution of the task.
K
kohsuke 已提交
735
         */
736
        Executable createExecutable() throws IOException;
737 738 739 740 741 742 743 744

        /**
         * Checks the permission to see if the current user can abort this executable.
         * Returns normally from this method if it's OK.
         *
         * @throws AccessDeniedException if the permission is not granted.
         */
        void checkAbortPermission();
K
kohsuke 已提交
745 746 747 748 749 750

        /**
         * Works just like {@link #checkAbortPermission()} except it indicates the status by a return value,
         * instead of exception.
         */
        boolean hasAbortPermission();
751 752
        
        /**
K
kohsuke 已提交
753 754 755 756 757 758 759 760
         * 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 '/'.
761 762 763 764 765 766 767
         */
        String getUrl();
        
        /**
         * Called from queue.jelly.
         */
        void doCancelQueue( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException;
768 769 770 771 772
    }

    public interface Executable extends Runnable {
        /**
         * Task from which this executable was created.
K
kohsuke 已提交
773
         * Never null.
774 775 776 777 778 779 780
         */
        Task getParent();

        /**
         * Called by {@link Executor} to perform the task
         */
        void run();
781 782
    }

K
kohsuke 已提交
783 784 785
    /**
     * Item in a queue.
     */
786
    @ExportedBean(defaultVisibility = 999)
787
    public abstract class Item {
K
kohsuke 已提交
788 789 790
        /**
         * Project to be built.
         */
791
        @Exported
792
        public final Task task;
K
kohsuke 已提交
793

794
        /**
795 796 797
         * Build is blocked because another build is in progress,
         * required {@link Resource}s are not available, or otherwise blocked
         * by {@link Task#isBuildBlocked()}.
798
         */
K
kohsuke 已提交
799
        @Exported
800
        public boolean isBlocked() { return this instanceof BlockedItem; }
801 802 803 804 805 806

        /**
         * 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.
         */
K
kohsuke 已提交
807
        @Exported
808
        public boolean isBuildable() { return this instanceof BuildableItem; }
809

810 811 812 813 814 815
        /**
         * True if the item is starving for an executor for too long.
         */
        @Exported
        public boolean isStuck() { return false; }

816
        protected Item(Task project) {
817
            this.task = project;
K
kohsuke 已提交
818 819
        }

820 821 822
        /**
         * Gets a human-readable status message describing why it's in the queu.
         */
K
kohsuke 已提交
823
        @Exported
824
        public abstract String getWhy();
K
kohsuke 已提交
825

826 827 828 829
        public boolean hasCancelPermission() {
            return task.hasAbortPermission();
        }
    }
830

831 832 833 834 835 836 837 838 839
    /**
     * {@link Item} in the {@link Queue#waitingList} stage.
     */
    public final class WaitingItem extends Item implements Comparable<WaitingItem> {
        /**
         * This item can be run after this time.
         */
        @Exported
        public Calendar timestamp;
840

K
kohsuke 已提交
841 842 843 844 845 846
        /**
         * Unique number of this {@link WaitingItem}.
         * Used to differentiate {@link WaitingItem}s with the same due date, to make it sortable.
         */
        public final int id;

847 848 849
        WaitingItem(Calendar timestamp, Task project) {
            super(project);
            this.timestamp = timestamp;
K
kohsuke 已提交
850 851 852
            synchronized (Queue.this) {
                this.id = iota++;
            }
853 854 855 856 857 858 859 860 861 862 863
        }

        public int compareTo(WaitingItem that) {
            int r = this.timestamp.getTime().compareTo(that.timestamp.getTime());
            if (r != 0) return r;

            return this.id - that.id;
        }

        @Override
        public String getWhy() {
864
            long diff = timestamp.getTimeInMillis() - System.currentTimeMillis();
865
            if (diff > 0)
K
i18n  
kohsuke 已提交
866
                return Messages.Queue_InQuietPeriod(Util.getTimeSpanString(diff));
867 868 869 870
            else
                return Messages.Queue_Unknown();
        }
    }
K
kohsuke 已提交
871

872 873 874 875 876 877 878 879 880 881 882 883 884
    /**
     * Common part between {@link BlockedItem} and {@link BuildableItem}.
     */
    public 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.task);
            buildableStartMilliseconds = System.currentTimeMillis();
K
kohsuke 已提交
885
        }
886

887 888 889
        protected NotWaitingItem(NotWaitingItem ni) {
            super(ni.task);
            buildableStartMilliseconds = ni.buildableStartMilliseconds;
890
        }
891
    }
892

893 894 895 896 897 898 899
    /**
     * {@link Item} in the {@link Queue#blockedProjects} stage.
     */
    public final class BlockedItem extends NotWaitingItem {
        public BlockedItem(WaitingItem wi) {
            super(wi);
        }
900

901 902 903 904 905 906 907 908 909 910 911 912 913
        public BlockedItem(NotWaitingItem ni) {
            super(ni);
        }

        @Override
        public String getWhy() {
            ResourceActivity r = getBlockingActivity(task);
            if (r != null) {
                if (r == task) // blocked by itself, meaning another build is in progress
                    return Messages.Queue_InProgress();
                return Messages.Queue_BlockedBy(r.getDisplayName());
            }
            return task.getWhyBlocked();
914
        }
915
    }
916

917 918 919 920 921 922 923 924 925 926 927 928 929 930
    /**
     * {@link Item} in the {@link Queue#buildables} stage.
     */
    public final class BuildableItem extends NotWaitingItem {
        public BuildableItem(WaitingItem wi) {
            super(wi);
        }

        public BuildableItem(NotWaitingItem ni) {
            super(ni);
        }

        @Override
        public String getWhy() {
931
            Label label = task.getAssignedLabel();
932
            Hudson hudson = Hudson.getInstance();
K
kohsuke 已提交
933
            if (hudson.getNodes().isEmpty())
934
                label = null;    // no master/slave. pointless to talk about nodes
935 936

            String name = null;
937 938 939 940
            if (label != null) {
                name = label.getName();
                if (label.isOffline()) {
                    if (label.getNodes().size() > 1)
K
i18n  
kohsuke 已提交
941
                        return Messages.Queue_AllNodesOffline(name);
942
                    else
K
i18n  
kohsuke 已提交
943
                        return Messages.Queue_NodeOffline(name);
944 945 946
                }
            }

K
i18n  
kohsuke 已提交
947 948 949 950
            if(name==null)
                return Messages.Queue_WaitingForNextAvailableExecutor();
            else
                return Messages.Queue_WaitingForNextAvailableExecutorOn(name);
951
        }
952 953 954 955 956 957 958 959 960 961 962

        @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) {
963
                // if we were running elsewhere, we would have done this build ten times.
964
                return elapsed > Math.max(d,60000L)*10;
965 966
            } else {
                // more than a day in the queue
967
                return TimeUnit2.MILLISECONDS.toHours(elapsed)>24;
968 969
            }
        }
K
kohsuke 已提交
970 971 972 973 974
    }

    /**
     * Unique number generator
     */
975
    private int iota = 0;
K
kohsuke 已提交
976 977

    private static final Logger LOGGER = Logger.getLogger(Queue.class.getName());
978

979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020
    /**
     * 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);
K
kohsuke 已提交
1021
                if(run==null)  throw new NoSuchElementException("No such build: "+string);
1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032
				return run;
			}

			@Override
			public String toString(Object object) {
				Run<?,?> run = (Run<?,?>) object;
				return run.getParent().getFullName() + "#" + run.getNumber();
			}
        });
    }

1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048
    /**
     * 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();
1049
            if (q != null)
1050 1051 1052 1053 1054
                q.maintain();
            else
                cancel();
        }
    }
K
kohsuke 已提交
1055
}