Queue.java 26.5 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;
K
kohsuke 已提交
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;
K
kohsuke 已提交
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.
K
kohsuke 已提交
33 34 35
 *
 * @author Kohsuke Kawaguchi
 */
36
public class Queue extends ResourceController {
K
kohsuke 已提交
37 38
    /**
     * Items in the queue ordered by {@link Item#timestamp}.
39 40
     *
     * <p>
K
kohsuke 已提交
41 42 43
     * This consists of {@link Item}s that cannot be run yet
     * because its time has not yet come.
     */
44
    private final Set<Item> queue = new TreeSet<Item>();
K
kohsuke 已提交
45 46 47

    /**
     * {@link Project}s that can be built immediately
48 49 50
     * but blocked because another build is in progress,
     * required {@link Resource}s are not available, or otherwise blocked
     * by {@link Task#isBuildBlocked()}.
K
kohsuke 已提交
51
     */
52
    private final Set<Task> blockedProjects = new HashSet<Task>();
K
kohsuke 已提交
53 54 55 56 57

    /**
     * {@link Project}s that can be built immediately
     * that are waiting for available {@link Executor}.
     */
58
    private final List<Task> buildables = new LinkedList<Task>();
K
kohsuke 已提交
59

60 61
    private final Map<Task, Long> enterBuildables = new WeakHashMap<Task, Long>();

K
kohsuke 已提交
62 63 64
    /**
     * Data structure created for each idle {@link Executor}.
     * This is an offer from the queue to an executor.
65 66
     *
     * <p>
67
     * It eventually receives a {@link #task} to build.
K
kohsuke 已提交
68 69 70 71 72 73 74 75 76 77 78 79 80
     */
    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.)
         */
81
        Task task;
K
kohsuke 已提交
82 83 84 85 86

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

87
        public void set(Task p) {
88
            assert this.task == null;
89
            this.task = p;
K
kohsuke 已提交
90 91 92 93
            event.signal();
        }

        public boolean isAvailable() {
94
            return task == null && !executor.getOwner().isOffline();
K
kohsuke 已提交
95 96 97 98 99 100 101
        }

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

        public boolean isNotExclusive() {
102
            return getNode().getMode() == Mode.NORMAL;
K
kohsuke 已提交
103 104 105
        }
    }

106
    private final Map<Executor, JobOffer> parked = new HashMap<Executor, JobOffer>();
K
kohsuke 已提交
107

108 109 110 111 112 113
    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 已提交
114 115 116 117 118 119 120
    /**
     * Loads the queue contents that was {@link #save() saved}.
     */
    public synchronized void load() {
        // write out the contents of the queue
        try {
            File queueFile = getQueueFile();
121
            if (!queueFile.exists())
K
kohsuke 已提交
122 123 124 125
                return;

            BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(queueFile)));
            String line;
126 127 128
            while ((line = in.readLine()) != null) {
                AbstractProject j = Hudson.getInstance().getItemByFullName(line, AbstractProject.class);
                if (j != null)
129
                    j.scheduleBuild();
K
kohsuke 已提交
130 131 132 133
            }
            in.close();
            // discard the queue file now that we are done
            queueFile.delete();
134 135
        } catch (IOException e) {
            LOGGER.log(Level.WARNING, "Failed to load the queue file " + getQueueFile(), e);
K
kohsuke 已提交
136 137 138 139 140 141 142 143 144 145
        }
    }

    /**
     * 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(
146
                    getQueueFile()));
K
kohsuke 已提交
147
            for (Item i : getItems())
148
                w.println(i.task.getName());
K
kohsuke 已提交
149
            w.close();
150 151
        } catch (IOException e) {
            LOGGER.log(Level.WARNING, "Failed to write out the queue file " + getQueueFile(), e);
K
kohsuke 已提交
152 153 154 155
        }
    }

    private File getQueueFile() {
156
        return new File(Hudson.getInstance().getRootDir(), "queue.txt");
K
kohsuke 已提交
157 158 159 160
    }

    /**
     * Schedule a new build for this project.
161
     *
162 163 164
     * @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 已提交
165
     */
166 167
    public boolean add(AbstractProject p) {
        return add(p, p.getQuietPeriod());
168 169 170 171
    }

    /**
     * Schedules a new build with a custom quiet period.
172 173
     *
     * <p>
K
kohsuke 已提交
174 175
     * Left for backward compatibility with &lt;1.114.
     *
176 177
     * @since 1.105
     */
178 179
    public synchronized boolean add(AbstractProject p, int quietPeriod) {
        return add((Task) p, quietPeriod);
K
kohsuke 已提交
180 181 182 183 184
    }

    /**
     * Schedules an execution of a task.
     *
185 186 187
     * @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 已提交
188 189
     * @since 1.114
     */
190 191 192 193
    public synchronized boolean add(Task p, int quietPeriod) {
        Item item = getItem(p);
        Calendar due = new GregorianCalendar();
        due.add(Calendar.SECOND, quietPeriod);
194 195 196
        if (item != null) {
            if (item.timestamp.before(due))
                return false; // no double queueing
K
kohsuke 已提交
197

198 199 200 201
            // allow the due date to be pulled in
            item.timestamp = due;
        } else {
            LOGGER.fine(p.getName() + " added to queue");
202

203 204
            // put the item in the queue
            queue.add(new Item(due, p));
K
kohsuke 已提交
205

206
        }
K
kohsuke 已提交
207
        scheduleMaintenance();   // let an executor know that a new item is in the queue.
208
        return true;
K
kohsuke 已提交
209 210
    }

K
kohsuke 已提交
211 212 213
    /**
     * Cancels the item in the queue.
     *
214 215
     * @return true if the project was indeed in the queue and was removed.
     *         false if this was no-op.
K
kohsuke 已提交
216
     */
217 218
    public synchronized boolean cancel(AbstractProject<?, ?> p) {
        LOGGER.fine("Cancelling " + p.getName());
K
kohsuke 已提交
219 220
        for (Iterator itr = queue.iterator(); itr.hasNext();) {
            Item item = (Item) itr.next();
221
            if (item.task == p) {
K
kohsuke 已提交
222
                itr.remove();
K
kohsuke 已提交
223
                return true;
K
kohsuke 已提交
224 225
            }
        }
K
kohsuke 已提交
226
        // use bitwise-OR to make sure that both branches get evaluated all the time
227 228
        enterBuildables.remove(p);
        return blockedProjects.remove(p) | buildables.remove(p);
K
kohsuke 已提交
229 230 231 232 233 234 235 236 237 238 239 240 241 242
    }

    public synchronized boolean isEmpty() {
        return queue.isEmpty() && blockedProjects.isEmpty() && buildables.isEmpty();
    }

    private synchronized Item peek() {
        return queue.iterator().next();
    }

    /**
     * Gets a snapshot of items in the queue.
     */
    public synchronized Item[] getItems() {
243
        Item[] r = new Item[queue.size() + blockedProjects.size() + buildables.size()];
K
kohsuke 已提交
244
        queue.toArray(r);
245
        int idx = queue.size();
K
kohsuke 已提交
246
        Calendar now = new GregorianCalendar();
247
        for (Task p : blockedProjects) {
248
            r[idx++] = new Item(now, p, true, false);
K
kohsuke 已提交
249
        }
250
        for (Task p : buildables) {
251 252 253 254
            if (!enterBuildables.containsKey(p)) {
                enterBuildables.put(p, System.currentTimeMillis());
            }
            r[idx++] = new Item(now, p, false, true, enterBuildables.get(p));
K
kohsuke 已提交
255 256 257 258
        }
        return r;
    }

259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
    public synchronized Item[] getBuildableItems(Computer c) {
        List<Item> result = new ArrayList<Item>();
        Calendar now = new GregorianCalendar();
        for (Task p : buildables) {
            Label l = p.getAssignedLabel();
            if (l != null) {
                // if a project has assigned label, it can be only built on it
                if (!l.contains(c.getNode()))
                    continue;
            }
            if (!enterBuildables.containsKey(p)) {
                enterBuildables.put(p, System.currentTimeMillis());
            }
            result.add(new Item(now, p, false, true, enterBuildables.get(p)));
        }
        return result.toArray(new Item[result.size()]);
    }

K
kohsuke 已提交
277 278 279 280 281
    /**
     * Gets the information about the queue item for the given project.
     *
     * @return null if the project is not in the queue.
     */
282
    public synchronized Item getItem(Task p) {
283 284 285 286 287 288 289 290
        if (blockedProjects.contains(p))
            return new Item(new GregorianCalendar(), p, true, false);
        if (buildables.contains(p)) {
            if (!enterBuildables.containsKey(p)) {
                enterBuildables.put(p, System.currentTimeMillis());
            }
            return new Item(new GregorianCalendar(), p, false, true, enterBuildables.get(p));
        }
K
kohsuke 已提交
291
        for (Item item : queue) {
292
            if (item.task == p)
K
kohsuke 已提交
293 294 295 296 297
                return item;
        }
        return null;
    }

298 299
    /**
     * Left for backward compatibility.
300
     *
301 302 303
     * @see #getItem(Task)
     */
    public synchronized Item getItem(AbstractProject p) {
304
        return getItem((Task) p);
305 306
    }

K
kohsuke 已提交
307
    /**
K
kohsuke 已提交
308
     * Returns true if this queue contains the said project.
K
kohsuke 已提交
309
     */
310
    public synchronized boolean contains(Task p) {
311
        if (blockedProjects.contains(p) || buildables.contains(p))
K
kohsuke 已提交
312 313
            return true;
        for (Item item : queue) {
314
            if (item.task == p)
K
kohsuke 已提交
315 316 317 318 319 320 321
                return true;
        }
        return false;
    }

    /**
     * Called by the executor to fetch something to build next.
322
     * <p>
K
kohsuke 已提交
323 324
     * This method blocks until a next project becomes buildable.
     */
325
    public Task pop() throws InterruptedException {
K
kohsuke 已提交
326
        final Executor exec = Executor.currentExecutor();
327

K
kohsuke 已提交
328
        try {
329
            while (true) {
K
kohsuke 已提交
330 331 332
                final JobOffer offer = new JobOffer(exec);
                long sleep = -1;

333
                synchronized (this) {
K
kohsuke 已提交
334 335
                    // consider myself parked
                    assert !parked.containsKey(exec);
336
                    parked.put(exec, offer);
K
kohsuke 已提交
337

K
kohsuke 已提交
338
                    // reuse executor thread to do a queue maintenance.
K
kohsuke 已提交
339 340 341 342 343
                    // at the end of this we get all the buildable jobs
                    // in the buildables field.
                    maintain();

                    // allocate buildable jobs to executors
344
                    Iterator<Task> itr = buildables.iterator();
345
                    while (itr.hasNext()) {
346
                        Task p = itr.next();
347 348

                        // one last check to make sure this build is not blocked.
349
                        if (isBuildBlocked(p)) {
350 351 352 353
                            itr.remove();
                            blockedProjects.add(p);
                            continue;
                        }
354

K
kohsuke 已提交
355
                        JobOffer runner = choose(p);
356
                        if (runner == null)
K
kohsuke 已提交
357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
                            // 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.

372
                    if (!queue.isEmpty()) {
K
kohsuke 已提交
373
                        // wait until the first item in the queue is due
374 375
                        sleep = peek().timestamp.getTimeInMillis() - new GregorianCalendar().getTimeInMillis();
                        if (sleep < 100) sleep = 100;    // avoid wait(0)
K
kohsuke 已提交
376 377 378 379 380
                    }
                }

                // this needs to be done outside synchronized block,
                // so that executors can maintain a queue while others are sleeping
381
                if (sleep == -1)
K
kohsuke 已提交
382 383 384 385
                    offer.event.block();
                else
                    offer.event.block(sleep);

386
                synchronized (this) {
387
                    // retract the offer object
388
                    assert parked.get(exec) == offer;
389 390
                    parked.remove(exec);

K
kohsuke 已提交
391
                    // am I woken up because I have a project to build?
392 393
                    if (offer.task != null) {
                        LOGGER.fine("Pop returning " + offer.task + " for " + exec.getName());
K
kohsuke 已提交
394
                        // if so, just build it
395
                        return offer.task;
K
kohsuke 已提交
396 397 398 399 400
                    }
                    // otherwise run a queue maintenance
                }
            }
        } finally {
401
            synchronized (this) {
K
kohsuke 已提交
402
                // remove myself from the parked list
403
                JobOffer offer = parked.remove(exec);
404
                if (offer != null && offer.task != null) {
405 406 407 408 409
                    // 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.
410
                    if (!contains(offer.task))
411
                        buildables.add(offer.task);
K
kohsuke 已提交
412
                }
413 414 415 416 417 418

                // 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 已提交
419 420 421 422 423
            }
        }
    }

    /**
K
kohsuke 已提交
424
     * Chooses the executor to carry out the build for the given project.
K
kohsuke 已提交
425
     *
426
     * @return null if no {@link Executor} can run it.
K
kohsuke 已提交
427
     */
428
    private JobOffer choose(Task p) {
429
        if (Hudson.getInstance().isQuietingDown()) {
K
kohsuke 已提交
430 431 432 433 434
            // if we are quieting down, don't run anything so that
            // all executors will be free.
            return null;
        }

435
        Label l = p.getAssignedLabel();
436
        if (l != null) {
437
            // if a project has assigned label, it can be only built on it
K
kohsuke 已提交
438
            for (JobOffer offer : parked.values()) {
439
                if (offer.isAvailable() && l.contains(offer.getNode()))
K
kohsuke 已提交
440 441 442 443 444
                    return offer;
            }
            return null;
        }

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

448
        // otherwise let's see if the last node where this project was built is available
K
kohsuke 已提交
449 450
        // it has up-to-date workspace, so that's usually preferable.
        // (but we can't use an exclusive node)
451
        Node n = p.getLastBuiltOn();
452
        if (n != null && n.getMode() == Mode.NORMAL) {
K
kohsuke 已提交
453
            for (JobOffer offer : parked.values()) {
454 455
                if (offer.isAvailable() && offer.getNode() == n) {
                    if (isLargeHudson && offer.getNode() instanceof Slave)
456 457
                        // but if we are a large Hudson, then we really do want to keep the master free from builds 
                        continue;
K
kohsuke 已提交
458
                    return offer;
459
                }
K
kohsuke 已提交
460 461 462 463 464 465
            }
        }

        // 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.
466 467
        // Similarly if we have many slaves, master should be made available
        // for HTTP requests and coordination as much as possible
468
        if (isLargeHudson || p.getEstimatedDuration() > 15 * 60 * 1000) {
K
kohsuke 已提交
469 470
            // consider a long job to be > 15 mins
            for (JobOffer offer : parked.values()) {
471
                if (offer.isAvailable() && offer.getNode() instanceof Slave && offer.isNotExclusive())
K
kohsuke 已提交
472 473 474 475 476 477
                    return offer;
            }
        }

        // lastly, just look for any idle executor
        for (JobOffer offer : parked.values()) {
478
            if (offer.isAvailable() && offer.isNotExclusive())
K
kohsuke 已提交
479 480 481 482 483 484 485 486 487
                return offer;
        }

        // nothing available
        return null;
    }

    /**
     * Checks the queue and runs anything that can be run.
488 489
     *
     * <p>
K
kohsuke 已提交
490
     * When conditions are changed, this method should be invoked.
491
     * <p>
K
kohsuke 已提交
492 493 494 495 496 497 498
     * 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()) {
499
            if (av.getValue().task == null) {
K
kohsuke 已提交
500 501 502 503 504 505
                av.getValue().event.signal();
                return;
            }
        }
    }

506 507 508
    /**
     * Checks if the given task is blocked.
     */
509
    private boolean isBuildBlocked(Task t) {
510 511 512
        return t.isBuildBlocked() || !canRun(t.getResourceList());
    }

K
kohsuke 已提交
513 514

    /**
K
kohsuke 已提交
515
     * Queue maintenance.
516
     * <p>
K
kohsuke 已提交
517 518 519 520
     * Move projects between {@link #queue}, {@link #blockedProjects}, and {@link #buildables}
     * appropriately.
     */
    private synchronized void maintain() {
521 522
        if (LOGGER.isLoggable(Level.FINE))
            LOGGER.fine("Queue maintenance started " + this);
523

524
        Iterator<Task> itr = blockedProjects.iterator();
525
        while (itr.hasNext()) {
526
            Task p = itr.next();
527
            if (!isBuildBlocked(p)) {
K
kohsuke 已提交
528
                // ready to be executed
529
                LOGGER.fine(p.getName() + " no longer blocked");
K
kohsuke 已提交
530 531
                itr.remove();
                buildables.add(p);
532
                enterBuildables.put(p, System.currentTimeMillis());
K
kohsuke 已提交
533 534 535
            }
        }

536
        while (!queue.isEmpty()) {
K
kohsuke 已提交
537 538
            Item top = peek();

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

542
            Task p = top.task;
543
            if (!isBuildBlocked(p)) {
K
kohsuke 已提交
544 545
                // ready to be executed immediately
                queue.remove(top);
546
                LOGGER.fine(p.getName() + " ready to build");
547
                buildables.add(p);
548
                enterBuildables.put(p, System.currentTimeMillis());
K
kohsuke 已提交
549
            } else {
550
                // this can't be built now because another build is in progress
K
kohsuke 已提交
551 552
                // set this project aside.
                queue.remove(top);
553
                LOGGER.fine(p.getName() + " is blocked");
554
                blockedProjects.add(p);
K
kohsuke 已提交
555 556 557 558
            }
        }
    }

K
kohsuke 已提交
559 560
    /**
     * Task whose execution is controlled by the queue.
561
     * <p>
K
kohsuke 已提交
562 563 564 565
     * {@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.
     */
566
    public interface Task extends ModelObject, ResourceActivity {
567
        /**
568 569 570
         * 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.
571
         */
572
        Label getAssignedLabel();
573 574 575 576 577 578 579 580 581 582 583

        /**
         * 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.
584 585
         *
         * <p>
K
kohsuke 已提交
586 587
         * This can be used to define mutual exclusion that goes beyond
         * {@link #getResourceList()}.
588 589 590 591 592 593 594 595 596 597 598
         */
        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 已提交
599
         * Unique name of this task.
K
kohsuke 已提交
600
         *
601 602
         * @see hudson.model.Item#getName()
         *      TODO: this doesn't make sense anymore. remove it.
603 604 605
         */
        String getName();

606 607 608 609 610
        /**
         * @see hudson.model.Item#getFullDisplayName()
         */
        String getFullDisplayName();

611 612 613 614
        /**
         * Estimate of how long will it take to execute this task.
         * Measured in milliseconds.
         *
615
         * @return -1 if it's impossible to estimate.
616 617 618
         */
        long getEstimatedDuration();

K
kohsuke 已提交
619
        /**
620
         * Creates {@link Executable}, which performs the actual execution of the task.
K
kohsuke 已提交
621
         */
622
        Executable createExecutable() throws IOException;
623 624 625 626 627 628 629 630

        /**
         * 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 已提交
631 632 633 634 635 636

        /**
         * Works just like {@link #checkAbortPermission()} except it indicates the status by a return value,
         * instead of exception.
         */
        boolean hasAbortPermission();
637 638 639 640 641
    }

    public interface Executable extends Runnable {
        /**
         * Task from which this executable was created.
K
kohsuke 已提交
642
         * Never null.
643 644 645 646 647 648 649
         */
        Task getParent();

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

K
kohsuke 已提交
652 653 654
    /**
     * Item in a queue.
     */
655
    @ExportedBean(defaultVisibility = 999)
656
    public final class Item implements Comparable<Item> {
K
kohsuke 已提交
657 658 659
        /**
         * This item can be run after this time.
         */
K
kohsuke 已提交
660
        @Exported
661
        public Calendar timestamp;
K
kohsuke 已提交
662 663 664 665

        /**
         * Project to be built.
         */
666
        public final Task task;
K
kohsuke 已提交
667

668 669
        public final long buildableStartMilliseconds;

K
kohsuke 已提交
670 671
        /**
         * Unique number of this {@link Item}.
K
kohsuke 已提交
672
         * Used to differentiate {@link Item}s with the same due date.
K
kohsuke 已提交
673
         */
674 675 676
        public final int id;

        /**
677 678 679
         * Build is blocked because another build is in progress,
         * required {@link Resource}s are not available, or otherwise blocked
         * by {@link Task#isBuildBlocked()}.
680
         * <p>
681 682 683
         * This flag is only used in {@link Queue#getItems()} for
         * 'pseudo' items that are actually not really in the queue.
         */
K
kohsuke 已提交
684
        @Exported
685 686 687 688 689 690 691
        public final boolean isBlocked;

        /**
         * 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 已提交
692
        @Exported
693
        public final boolean isBuildable;
K
kohsuke 已提交
694

695
        public Item(Calendar timestamp, Task project) {
696
            this(timestamp, project, false, false);
697 698
        }

699
        public Item(Calendar timestamp, Task project, boolean isBlocked, boolean isBuildable) {
700 701 702 703 704
            this(timestamp, project, isBlocked, isBuildable, System.currentTimeMillis() + 1000);
        }

        public Item(Calendar timestamp, Task project, boolean isBlocked, boolean isBuildable,
                    long buildableStartMilliseconds) {
K
kohsuke 已提交
705
            this.timestamp = timestamp;
706
            this.task = project;
707 708
            this.isBlocked = isBlocked;
            this.isBuildable = isBuildable;
709 710
            this.buildableStartMilliseconds = buildableStartMilliseconds;
            synchronized (Queue.this) {
K
kohsuke 已提交
711 712 713 714
                this.id = iota++;
            }
        }

715 716 717
        /**
         * Gets a human-readable status message describing why it's in the queu.
         */
K
kohsuke 已提交
718
        @Exported
719
        public String getWhy() {
720
            if (isBuildable) {
721
                Label node = task.getAssignedLabel();
722
                Hudson hudson = Hudson.getInstance();
723
                if (hudson.getSlaves().isEmpty())
724 725 726
                    node = null;    // no master/slave. pointless to talk about nodes

                String name = null;
727
                if (node != null) {
728
                    name = node.getName();
729 730 731
                    if (node.isOffline()) {
                        if (node.getNodes().size() > 1)
                            return "All nodes of label '" + name + "' is offline";
732
                        else
733
                            return name + " is offline";
734
                    }
735
                }
K
kohsuke 已提交
736

737
                return "Waiting for next available executor" + (name == null ? "" : " on " + name);
738 739
            }

740
            if (isBlocked) {
741
                ResourceActivity r = getBlockingActivity(task);
742 743
                if (r != null) {
                    if (r == task) // blocked by itself, meaning another build is in progress
K
i18n  
kohsuke 已提交
744 745
                        return Messages.Queue_InProgress();
                    return Messages.Queue_BlockedBy(r.getDisplayName());
746
                }
747
                return task.getWhyBlocked();
748 749 750
            }

            long diff = timestamp.getTimeInMillis() - System.currentTimeMillis();
751
            if (diff > 0) {
K
i18n  
kohsuke 已提交
752
                return Messages.Queue_InQuietPeriod(Util.getTimeSpanString(diff));
753
            }
K
kohsuke 已提交
754

K
i18n  
kohsuke 已提交
755
            return Messages.Queue_Unknown();
K
kohsuke 已提交
756
        }
757

758
        public boolean hasCancelPermission() {
K
kohsuke 已提交
759
            return task.hasAbortPermission();
760 761
        }

762 763
        public int compareTo(Item that) {
            int r = this.timestamp.getTime().compareTo(that.timestamp.getTime());
764
            if (r != 0) return r;
765

766
            return this.id - that.id;
767 768
        }

K
kohsuke 已提交
769 770 771 772 773
    }

    /**
     * Unique number generator
     */
774
    private int iota = 0;
K
kohsuke 已提交
775 776

    private static final Logger LOGGER = Logger.getLogger(Queue.class.getName());
777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793

    /**
     * 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();
794
            if (q != null)
795 796 797 798 799
                q.maintain();
            else
                cancel();
        }
    }
K
kohsuke 已提交
800
}