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

3
import hudson.Util;
K
kohsuke 已提交
4
import hudson.model.Node.Mode;
5 6
import hudson.triggers.SafeTimerTask;
import hudson.triggers.Trigger;
K
kohsuke 已提交
7
import hudson.util.OneShotEvent;
8 9 10
import org.acegisecurity.AccessDeniedException;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
K
kohsuke 已提交
11

12
import javax.management.timer.Timer;
13 14 15 16 17 18 19
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
20
import java.lang.ref.WeakReference;
S
stephenconnolly 已提交
21
import java.util.Map.Entry;
22
import java.util.*;
K
kohsuke 已提交
23 24 25 26 27
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Build queue.
28 29
 *
 * <p>
30 31
 * 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}
32
 * so that we can keep track of additional data used for deciding what to exeucte when.
33 34
 *
 * <p>
35 36 37 38 39 40 41 42
 * Items in queue goes through several stages, as depicted below:
 * <pre>
 * (enter) --> waitingList --+--> blockedProjects
 *                           |        ^
 *                           |        |
 *                           |        v
 *                           +--> buildables ---> (executed)
 * </pre>
43 44
 *
 * <p>
45 46 47
 * 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 已提交
48 49
 * @author Kohsuke Kawaguchi
 */
50
@ExportedBean
51
public class Queue extends ResourceController {
K
kohsuke 已提交
52
    /**
53
     * Items that are waiting for its quiet period to pass.
54 55
     *
     * <p>
K
kohsuke 已提交
56 57 58
     * This consists of {@link Item}s that cannot be run yet
     * because its time has not yet come.
     */
59
    private final Set<WaitingItem> waitingList = new TreeSet<WaitingItem>();
K
kohsuke 已提交
60 61 62

    /**
     * {@link Project}s that can be built immediately
63 64 65
     * but blocked because another build is in progress,
     * required {@link Resource}s are not available, or otherwise blocked
     * by {@link Task#isBuildBlocked()}.
66 67
     *
     * <p>
68 69
     * 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 已提交
70
     */
71
    private final Map<Task,BlockedItem> blockedProjects = new HashMap<Task,BlockedItem>();
K
kohsuke 已提交
72 73 74 75

    /**
     * {@link Project}s that can be built immediately
     * that are waiting for available {@link Executor}.
76 77
     *
     * <p>
78 79 80
     * 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 已提交
81
     */
82
    private final LinkedHashMap<Task,BuildableItem> buildables = new LinkedHashMap<Task,BuildableItem>();
83

K
kohsuke 已提交
84 85 86
    /**
     * Data structure created for each idle {@link Executor}.
     * This is an offer from the queue to an executor.
87 88
     *
     * <p>
89
     * It eventually receives a {@link #item} to build.
K
kohsuke 已提交
90 91 92 93 94 95 96 97 98 99 100 101 102
     */
    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.)
         */
103
        BuildableItem item;
K
kohsuke 已提交
104 105 106 107 108

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

109 110 111
        public void set(BuildableItem p) {
            assert this.item == null;
            this.item = p;
K
kohsuke 已提交
112 113 114 115
            event.signal();
        }

        public boolean isAvailable() {
116
            return item == null && !executor.getOwner().isOffline() && executor.getOwner().isAcceptingTasks();
K
kohsuke 已提交
117 118 119 120 121 122 123
        }

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

        public boolean isNotExclusive() {
124
            return getNode().getMode() == Mode.NORMAL;
K
kohsuke 已提交
125 126 127
        }
    }

S
stephenconnolly 已提交
128 129 130
    /**
     * The executors that are currently parked while waiting for a job to run.
     */
131
    private final Map<Executor, JobOffer> parked = new HashMap<Executor, JobOffer>();
K
kohsuke 已提交
132

133 134 135 136 137 138
    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 已提交
139 140 141 142 143 144 145
    /**
     * Loads the queue contents that was {@link #save() saved}.
     */
    public synchronized void load() {
        // write out the contents of the queue
        try {
            File queueFile = getQueueFile();
146
            if (!queueFile.exists())
K
kohsuke 已提交
147 148 149 150
                return;

            BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(queueFile)));
            String line;
151 152 153
            while ((line = in.readLine()) != null) {
                AbstractProject j = Hudson.getInstance().getItemByFullName(line, AbstractProject.class);
                if (j != null)
154
                    j.scheduleBuild();
K
kohsuke 已提交
155 156 157 158
            }
            in.close();
            // discard the queue file now that we are done
            queueFile.delete();
159 160
        } catch (IOException e) {
            LOGGER.log(Level.WARNING, "Failed to load the queue file " + getQueueFile(), e);
K
kohsuke 已提交
161 162 163 164 165 166 167 168 169 170
        }
    }

    /**
     * Persists the queue contents to the disk.
     */
    public synchronized void save() {
        // write out the contents of the queue
        try {
            PrintWriter w = new PrintWriter(new FileOutputStream(
171
                    getQueueFile()));
K
kohsuke 已提交
172
            for (Item i : getItems())
173
                w.println(i.task.getName());
K
kohsuke 已提交
174
            w.close();
175 176
        } catch (IOException e) {
            LOGGER.log(Level.WARNING, "Failed to write out the queue file " + getQueueFile(), e);
K
kohsuke 已提交
177 178 179 180
        }
    }

    private File getQueueFile() {
181
        return new File(Hudson.getInstance().getRootDir(), "queue.txt");
K
kohsuke 已提交
182 183 184 185
    }

    /**
     * Schedule a new build for this project.
186
     *
187 188 189
     * @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 已提交
190
     */
191 192
    public boolean add(AbstractProject p) {
        return add(p, p.getQuietPeriod());
193 194 195 196
    }

    /**
     * Schedules a new build with a custom quiet period.
197 198
     *
     * <p>
K
kohsuke 已提交
199 200
     * Left for backward compatibility with &lt;1.114.
     *
201 202
     * @since 1.105
     */
203 204
    public synchronized boolean add(AbstractProject p, int quietPeriod) {
        return add((Task) p, quietPeriod);
K
kohsuke 已提交
205 206 207 208 209
    }

    /**
     * Schedules an execution of a task.
     *
210 211 212
     * @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 已提交
213 214
     * @since 1.114
     */
215 216 217 218
    public synchronized boolean add(Task p, int quietPeriod) {
        Item item = getItem(p);
        Calendar due = new GregorianCalendar();
        due.add(Calendar.SECOND, quietPeriod);
219
        if (item != null) {
220 221 222 223 224 225 226
            if (!(item instanceof WaitingItem))
                // already in the blocked or buildable stage
                // no need to requeue
                return false;

            WaitingItem wi = (WaitingItem) item;
            if (wi.timestamp.before(due))
227
                return false; // no double queueing
K
kohsuke 已提交
228

229
            // allow the due date to be pulled in
230
            wi.timestamp = due;
231 232
        } else {
            LOGGER.fine(p.getName() + " added to queue");
233

234
            // put the item in the queue
235
            waitingList.add(new WaitingItem(due,p));
K
kohsuke 已提交
236

237
        }
K
kohsuke 已提交
238
        scheduleMaintenance();   // let an executor know that a new item is in the queue.
239
        return true;
K
kohsuke 已提交
240 241
    }

K
kohsuke 已提交
242 243 244
    /**
     * Cancels the item in the queue.
     *
245 246
     * @return true if the project was indeed in the queue and was removed.
     *         false if this was no-op.
K
kohsuke 已提交
247
     */
248 249
    public synchronized boolean cancel(AbstractProject<?, ?> p) {
        LOGGER.fine("Cancelling " + p.getName());
250
        for (Iterator itr = waitingList.iterator(); itr.hasNext();) {
K
kohsuke 已提交
251
            Item item = (Item) itr.next();
252
            if (item.task == p) {
K
kohsuke 已提交
253
                itr.remove();
K
kohsuke 已提交
254
                return true;
K
kohsuke 已提交
255 256
            }
        }
K
kohsuke 已提交
257
        // use bitwise-OR to make sure that both branches get evaluated all the time
258
        return blockedProjects.remove(p)!=null | buildables.remove(p)!=null;
K
kohsuke 已提交
259 260 261
    }

    public synchronized boolean isEmpty() {
262
        return waitingList.isEmpty() && blockedProjects.isEmpty() && buildables.isEmpty();
K
kohsuke 已提交
263 264
    }

265
    private synchronized WaitingItem peek() {
266
        return waitingList.iterator().next();
K
kohsuke 已提交
267 268 269 270 271
    }

    /**
     * Gets a snapshot of items in the queue.
     */
272
    @Exported(inline=true)
K
kohsuke 已提交
273
    public synchronized Item[] getItems() {
274 275 276
        Item[] r = new Item[waitingList.size() + blockedProjects.size() + buildables.size()];
        waitingList.toArray(r);
        int idx = waitingList.size();
277 278 279 280
        for (BlockedItem p : blockedProjects.values())
            r[idx++] = p;
        for (BuildableItem p : buildables.values())
            r[idx++] = p;
K
kohsuke 已提交
281 282 283
        return r;
    }

284 285 286 287
    public synchronized List<BuildableItem> getBuildableItems(Computer c) {
        List<BuildableItem> result = new ArrayList<BuildableItem>();
        for (BuildableItem p : buildables.values()) {
            Label l = p.task.getAssignedLabel();
288 289 290 291 292
            if (l != null) {
                // if a project has assigned label, it can be only built on it
                if (!l.contains(c.getNode()))
                    continue;
            }
293
            result.add(p);
294
        }
295
        return result;
296 297
    }

K
kohsuke 已提交
298 299 300 301 302
    /**
     * Gets the information about the queue item for the given project.
     *
     * @return null if the project is not in the queue.
     */
303 304
    public synchronized Item getItem(Task t) {
        BlockedItem bp = blockedProjects.get(t);
305
        if (bp!=null)
306 307
            return bp;
        BuildableItem bi = buildables.get(t);
308
        if(bi!=null)
309 310
            return bi;

311
        for (Item item : waitingList) {
312
            if (item.task == t)
K
kohsuke 已提交
313 314 315 316 317
                return item;
        }
        return null;
    }

318 319
    /**
     * Left for backward compatibility.
320
     *
321 322 323
     * @see #getItem(Task)
     */
    public synchronized Item getItem(AbstractProject p) {
324
        return getItem((Task) p);
325 326
    }

K
kohsuke 已提交
327
    /**
K
kohsuke 已提交
328
     * Returns true if this queue contains the said project.
K
kohsuke 已提交
329
     */
330 331
    public synchronized boolean contains(Task t) {
        if (blockedProjects.containsKey(t) || buildables.containsKey(t))
K
kohsuke 已提交
332
            return true;
333
        for (Item item : waitingList) {
334
            if (item.task == t)
K
kohsuke 已提交
335 336 337 338 339 340 341
                return true;
        }
        return false;
    }

    /**
     * Called by the executor to fetch something to build next.
342
     * <p>
K
kohsuke 已提交
343 344
     * This method blocks until a next project becomes buildable.
     */
345
    public Task pop() throws InterruptedException {
K
kohsuke 已提交
346
        final Executor exec = Executor.currentExecutor();
347

K
kohsuke 已提交
348
        try {
349
            while (true) {
K
kohsuke 已提交
350 351 352
                final JobOffer offer = new JobOffer(exec);
                long sleep = -1;

353
                synchronized (this) {
K
kohsuke 已提交
354 355
                    // consider myself parked
                    assert !parked.containsKey(exec);
356
                    parked.put(exec, offer);
K
kohsuke 已提交
357

K
kohsuke 已提交
358
                    // reuse executor thread to do a queue maintenance.
K
kohsuke 已提交
359 360 361 362 363
                    // at the end of this we get all the buildable jobs
                    // in the buildables field.
                    maintain();

                    // allocate buildable jobs to executors
364
                    Iterator<BuildableItem> itr = buildables.values().iterator();
365
                    while (itr.hasNext()) {
366
                        BuildableItem p = itr.next();
367 368

                        // one last check to make sure this build is not blocked.
369
                        if (isBuildBlocked(p.task)) {
370
                            itr.remove();
371
                            blockedProjects.put(p.task,new BlockedItem(p));
372 373
                            continue;
                        }
374

375
                        JobOffer runner = choose(p.task);
376
                        if (runner == null)
K
kohsuke 已提交
377 378 379 380 381 382 383 384 385 386 387 388 389 390 391
                            // 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.

392
                    if (!waitingList.isEmpty()) {
K
kohsuke 已提交
393
                        // wait until the first item in the queue is due
394 395
                        sleep = peek().timestamp.getTimeInMillis() - new GregorianCalendar().getTimeInMillis();
                        if (sleep < 100) sleep = 100;    // avoid wait(0)
K
kohsuke 已提交
396 397 398 399 400
                    }
                }

                // this needs to be done outside synchronized block,
                // so that executors can maintain a queue while others are sleeping
401
                if (sleep == -1)
K
kohsuke 已提交
402 403 404 405
                    offer.event.block();
                else
                    offer.event.block(sleep);

406
                synchronized (this) {
407
                    // retract the offer object
408
                    assert parked.get(exec) == offer;
409 410
                    parked.remove(exec);

K
kohsuke 已提交
411
                    // am I woken up because I have a project to build?
412 413
                    if (offer.item != null) {
                        LOGGER.fine("Pop returning " + offer.item + " for " + exec.getName());
K
kohsuke 已提交
414
                        // if so, just build it
415
                        return offer.item.task;
K
kohsuke 已提交
416 417 418 419 420
                    }
                    // otherwise run a queue maintenance
                }
            }
        } finally {
421
            synchronized (this) {
K
kohsuke 已提交
422
                // remove myself from the parked list
423
                JobOffer offer = parked.remove(exec);
424
                if (offer != null && offer.item != null) {
425 426 427 428 429
                    // 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.
430
                    if (!contains(offer.item.task))
431
                        buildables.put(offer.item.task,offer.item);
K
kohsuke 已提交
432
                }
433 434 435 436 437 438

                // 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 已提交
439 440 441 442 443
            }
        }
    }

    /**
K
kohsuke 已提交
444
     * Chooses the executor to carry out the build for the given project.
K
kohsuke 已提交
445
     *
446
     * @return null if no {@link Executor} can run it.
K
kohsuke 已提交
447
     */
448
    private JobOffer choose(Task p) {
449
        if (Hudson.getInstance().isQuietingDown()) {
K
kohsuke 已提交
450 451 452 453 454
            // if we are quieting down, don't run anything so that
            // all executors will be free.
            return null;
        }

455
        Label l = p.getAssignedLabel();
456
        if (l != null) {
457
            // if a project has assigned label, it can be only built on it
K
kohsuke 已提交
458
            for (JobOffer offer : parked.values()) {
459
                if (offer.isAvailable() && l.contains(offer.getNode()))
K
kohsuke 已提交
460 461 462 463 464
                    return offer;
            }
            return null;
        }

465
        // if we are a large deployment, then we will favor slaves
466
        boolean isLargeHudson = Hudson.getInstance().getSlaves().size() > 10;
467

468
        // otherwise let's see if the last node where this project was built is available
K
kohsuke 已提交
469 470
        // it has up-to-date workspace, so that's usually preferable.
        // (but we can't use an exclusive node)
471
        Node n = p.getLastBuiltOn();
472
        if (n != null && n.getMode() == Mode.NORMAL) {
K
kohsuke 已提交
473
            for (JobOffer offer : parked.values()) {
474 475
                if (offer.isAvailable() && offer.getNode() == n) {
                    if (isLargeHudson && offer.getNode() instanceof Slave)
S
stephenconnolly 已提交
476
                        // but if we are a large Hudson, then we really do want to keep the master free from builds
477
                        continue;
K
kohsuke 已提交
478
                    return offer;
479
                }
K
kohsuke 已提交
480 481 482 483 484 485
            }
        }

        // 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.
486 487
        // Similarly if we have many slaves, master should be made available
        // for HTTP requests and coordination as much as possible
488
        if (isLargeHudson || p.getEstimatedDuration() > 15 * 60 * 1000) {
K
kohsuke 已提交
489 490
            // consider a long job to be > 15 mins
            for (JobOffer offer : parked.values()) {
491
                if (offer.isAvailable() && offer.getNode() instanceof Slave && offer.isNotExclusive())
K
kohsuke 已提交
492 493 494 495 496 497
                    return offer;
            }
        }

        // lastly, just look for any idle executor
        for (JobOffer offer : parked.values()) {
498
            if (offer.isAvailable() && offer.isNotExclusive())
K
kohsuke 已提交
499 500 501 502 503 504 505 506 507
                return offer;
        }

        // nothing available
        return null;
    }

    /**
     * Checks the queue and runs anything that can be run.
508 509
     *
     * <p>
K
kohsuke 已提交
510
     * When conditions are changed, this method should be invoked.
511
     * <p>
K
kohsuke 已提交
512 513 514 515 516 517 518
     * 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()) {
519
            if (av.getValue().item == null) {
K
kohsuke 已提交
520 521 522 523 524 525
                av.getValue().event.signal();
                return;
            }
        }
    }

526 527 528
    /**
     * Checks if the given task is blocked.
     */
529
    private boolean isBuildBlocked(Task t) {
530 531 532
        return t.isBuildBlocked() || !canRun(t.getResourceList());
    }

K
kohsuke 已提交
533 534

    /**
K
kohsuke 已提交
535
     * Queue maintenance.
536
     * <p>
537
     * Move projects between {@link #waitingList}, {@link #blockedProjects}, and {@link #buildables}
K
kohsuke 已提交
538 539
     * appropriately.
     */
540
    public synchronized void maintain() {
541 542
        if (LOGGER.isLoggable(Level.FINE))
            LOGGER.fine("Queue maintenance started " + this);
543

544
        Iterator<BlockedItem> itr = blockedProjects.values().iterator();
545
        while (itr.hasNext()) {
546 547
            BlockedItem p = itr.next();
            if (!isBuildBlocked(p.task)) {
K
kohsuke 已提交
548
                // ready to be executed
549
                LOGGER.fine(p.task.getName() + " no longer blocked");
K
kohsuke 已提交
550
                itr.remove();
551
                buildables.put(p.task,new BuildableItem(p));
K
kohsuke 已提交
552 553 554
            }
        }

555
        while (!waitingList.isEmpty()) {
556
            WaitingItem top = peek();
K
kohsuke 已提交
557

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

561
            Task p = top.task;
562
            if (!isBuildBlocked(p)) {
K
kohsuke 已提交
563
                // ready to be executed immediately
564
                waitingList.remove(top);
565
                LOGGER.fine(p.getName() + " ready to build");
566
                buildables.put(p,new BuildableItem(top));
K
kohsuke 已提交
567
            } else {
568
                // this can't be built now because another build is in progress
K
kohsuke 已提交
569
                // set this project aside.
570
                waitingList.remove(top);
571
                LOGGER.fine(p.getName() + " is blocked");
572
                blockedProjects.put(p,new BlockedItem(top));
K
kohsuke 已提交
573 574 575 576
            }
        }
    }

577 578 579 580
    public Api getApi() {
        return new Api(this);
    }

K
kohsuke 已提交
581 582
    /**
     * Task whose execution is controlled by the queue.
583
     * <p>
K
kohsuke 已提交
584 585 586 587
     * {@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.
     */
588
    public interface Task extends ModelObject, ResourceActivity {
589
        /**
590 591 592
         * 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.
593
         */
594
        Label getAssignedLabel();
595 596 597 598 599 600 601 602 603 604 605

        /**
         * 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.
606 607
         *
         * <p>
K
kohsuke 已提交
608 609
         * This can be used to define mutual exclusion that goes beyond
         * {@link #getResourceList()}.
610 611 612 613 614 615 616 617 618 619 620
         */
        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 已提交
621
         * Unique name of this task.
K
kohsuke 已提交
622
         *
623 624
         * @see hudson.model.Item#getName()
         *      TODO: this doesn't make sense anymore. remove it.
625 626 627
         */
        String getName();

628 629 630 631 632
        /**
         * @see hudson.model.Item#getFullDisplayName()
         */
        String getFullDisplayName();

633 634 635 636
        /**
         * Estimate of how long will it take to execute this task.
         * Measured in milliseconds.
         *
637
         * @return -1 if it's impossible to estimate.
638 639 640
         */
        long getEstimatedDuration();

K
kohsuke 已提交
641
        /**
642
         * Creates {@link Executable}, which performs the actual execution of the task.
K
kohsuke 已提交
643
         */
644
        Executable createExecutable() throws IOException;
645 646 647 648 649 650 651 652

        /**
         * 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 已提交
653 654 655 656 657 658

        /**
         * Works just like {@link #checkAbortPermission()} except it indicates the status by a return value,
         * instead of exception.
         */
        boolean hasAbortPermission();
659 660 661 662 663
    }

    public interface Executable extends Runnable {
        /**
         * Task from which this executable was created.
K
kohsuke 已提交
664
         * Never null.
665 666 667 668 669 670 671
         */
        Task getParent();

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

K
kohsuke 已提交
674 675 676
    /**
     * Item in a queue.
     */
677
    @ExportedBean(defaultVisibility = 999)
678
    public abstract class Item {
K
kohsuke 已提交
679 680 681
        /**
         * Project to be built.
         */
682
        @Exported
683
        public final Task task;
K
kohsuke 已提交
684

685
        /**
686 687 688
         * Build is blocked because another build is in progress,
         * required {@link Resource}s are not available, or otherwise blocked
         * by {@link Task#isBuildBlocked()}.
689
         */
K
kohsuke 已提交
690
        @Exported
691
        public boolean isBlocked() { return this instanceof BlockedItem; }
692 693 694 695 696 697

        /**
         * 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 已提交
698
        @Exported
699
        public boolean isBuildable() { return this instanceof BuildableItem; }
700

701
        protected Item(Task project) {
702
            this.task = project;
K
kohsuke 已提交
703 704
        }

705 706 707
        /**
         * Gets a human-readable status message describing why it's in the queu.
         */
K
kohsuke 已提交
708
        @Exported
709
        public abstract String getWhy();
K
kohsuke 已提交
710

711 712 713 714
        public boolean hasCancelPermission() {
            return task.hasAbortPermission();
        }
    }
715

716 717 718 719 720 721 722 723 724
    /**
     * {@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;
725

K
kohsuke 已提交
726 727 728 729 730 731
        /**
         * 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;

732 733 734
        WaitingItem(Calendar timestamp, Task project) {
            super(project);
            this.timestamp = timestamp;
K
kohsuke 已提交
735 736 737
            synchronized (Queue.this) {
                this.id = iota++;
            }
738 739 740 741 742 743 744 745 746 747 748
        }

        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() {
749
            long diff = timestamp.getTimeInMillis() - System.currentTimeMillis();
750
            if (diff > 0)
K
i18n  
kohsuke 已提交
751
                return Messages.Queue_InQuietPeriod(Util.getTimeSpanString(diff));
752 753 754 755
            else
                return Messages.Queue_Unknown();
        }
    }
K
kohsuke 已提交
756

757 758 759 760 761 762 763 764 765 766 767 768 769
    /**
     * 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 已提交
770
        }
771

772 773 774
        protected NotWaitingItem(NotWaitingItem ni) {
            super(ni.task);
            buildableStartMilliseconds = ni.buildableStartMilliseconds;
775
        }
776
    }
777

778 779 780 781 782 783 784
    /**
     * {@link Item} in the {@link Queue#blockedProjects} stage.
     */
    public final class BlockedItem extends NotWaitingItem {
        public BlockedItem(WaitingItem wi) {
            super(wi);
        }
785

786 787 788 789 790 791 792 793 794 795 796 797 798
        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();
799
        }
800
    }
801

802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833
    /**
     * {@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() {
            Label node = task.getAssignedLabel();
            Hudson hudson = Hudson.getInstance();
            if (hudson.getSlaves().isEmpty())
                node = null;    // no master/slave. pointless to talk about nodes

            String name = null;
            if (node != null) {
                name = node.getName();
                if (node.isOffline()) {
                    if (node.getNodes().size() > 1)
                        return "All nodes of label '" + name + "' is offline";
                    else
                        return name + " is offline";
                }
            }

            return "Waiting for next available executor" + (name == null ? "" : " on " + name);
        }
K
kohsuke 已提交
834 835 836 837 838
    }

    /**
     * Unique number generator
     */
839
    private int iota = 0;
K
kohsuke 已提交
840 841

    private static final Logger LOGGER = Logger.getLogger(Queue.class.getName());
842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858

    /**
     * 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();
859
            if (q != null)
860 861 862 863 864
                q.maintain();
            else
                cancel();
        }
    }
K
kohsuke 已提交
865
}