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

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

11 12 13 14 15
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
16
import java.lang.ref.WeakReference;
K
kohsuke 已提交
17 18 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;
import java.util.Set;
import java.util.TreeSet;
S
stephenconnolly 已提交
27
import java.util.Map.Entry;
K
kohsuke 已提交
28 29 30
import java.util.logging.Level;
import java.util.logging.Logger;

K
kohsuke 已提交
31 32 33 34 35 36 37 38 39
import javax.management.timer.Timer;

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

    /**
     * {@link Project}s that can be built immediately
77 78 79
     * but blocked because another build is in progress,
     * required {@link Resource}s are not available, or otherwise blocked
     * by {@link Task#isBuildBlocked()}.
80 81
     *
     * <p>
82 83
     * 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 已提交
84
     */
85
    private final Map<Task,BlockedItem> blockedProjects = new HashMap<Task,BlockedItem>();
K
kohsuke 已提交
86 87 88 89

    /**
     * {@link Project}s that can be built immediately
     * that are waiting for available {@link Executor}.
90 91
     *
     * <p>
92 93 94
     * 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 已提交
95
     */
96
    private final LinkedHashMap<Task,BuildableItem> buildables = new LinkedHashMap<Task,BuildableItem>();
97

K
kohsuke 已提交
98 99 100
    /**
     * Data structure created for each idle {@link Executor}.
     * This is an offer from the queue to an executor.
101 102
     *
     * <p>
103
     * It eventually receives a {@link #item} to build.
K
kohsuke 已提交
104 105 106 107 108 109 110 111 112 113 114 115 116
     */
    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.)
         */
117
        BuildableItem item;
K
kohsuke 已提交
118 119 120 121 122

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

123 124 125
        public void set(BuildableItem p) {
            assert this.item == null;
            this.item = p;
K
kohsuke 已提交
126 127 128 129
            event.signal();
        }

        public boolean isAvailable() {
130
            return item == null && !executor.getOwner().isOffline() && executor.getOwner().isAcceptingTasks();
K
kohsuke 已提交
131 132 133 134 135 136 137
        }

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

        public boolean isNotExclusive() {
138
            return getNode().getMode() == Mode.NORMAL;
K
kohsuke 已提交
139 140 141
        }
    }

S
stephenconnolly 已提交
142 143 144
    /**
     * The executors that are currently parked while waiting for a job to run.
     */
145
    private final Map<Executor, JobOffer> parked = new HashMap<Executor, JobOffer>();
K
kohsuke 已提交
146

K
kohsuke 已提交
147 148
    private final XStream xstream = new XStream2();
    
149
    public Queue() {
K
kohsuke 已提交
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
        xstream.registerConverter(new AbstractSingleValueConverter() {

			@Override
			@SuppressWarnings("unchecked")
			public boolean canConvert(Class klazz) {
				return TopLevelItem.class.isAssignableFrom(klazz);
			}

			@Override
			public Object fromString(String string) {
				return Hudson.getInstance().getItem(string);
			}

			@Override
			public String toString(Object item) {
				return ((TopLevelItem) item).getName();
			}
        });
        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().getItem(projectName);
				Run<?,?> run = job.getBuildByNumber(buildNumber);
				return run;
			}

			@Override
			public String toString(Object object) {
				Run<?,?> run = (Run<?,?>) object;
				return run.getParent().getName() + "#" + run.getNumber();
			}
        });
 
193 194 195 196 197
        // 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 已提交
198 199 200 201 202
    /**
     * Loads the queue contents that was {@link #save() saved}.
     */
    public synchronized void load() {
        try {
K
TAB->WS  
kohsuke 已提交
203
            // first try the old format
K
kohsuke 已提交
204
            File queueFile = getQueueFile();
K
kohsuke 已提交
205 206
            if (queueFile.exists()) {
                BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(queueFile)));
K
TAB->WS  
kohsuke 已提交
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
                String line;
                while ((line = in.readLine()) != null) {
                    AbstractProject j = Hudson.getInstance().getItemByFullName(line, AbstractProject.class);
                    if (j != null)
                        j.scheduleBuild();
                }
                in.close();
                // discard the queue file now that we are done
                queueFile.delete();
            } else {
                queueFile = getXMLQueueFile();
                if (queueFile.exists()) {
                    List<Task> tasks = (List<Task>) new XmlFile(xstream, queueFile).read();
                    for (Task task : tasks) {
                        add(task, 0);
                    }
223 224 225 226 227 228 229 230 231

                    // 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 已提交
232 233 234
                    queueFile.delete();
                }
            }
235 236
        } catch (IOException e) {
            LOGGER.log(Level.WARNING, "Failed to load the queue file " + getQueueFile(), e);
K
kohsuke 已提交
237 238 239 240 241 242 243
        }
    }

    /**
     * Persists the queue contents to the disk.
     */
    public synchronized void save() {
K
kohsuke 已提交
244 245 246
        // write out the tasks on the queue
    	ArrayList<Task> tasks = new ArrayList<Task>();
    	for (Item item: getItems()) {
K
TAB->WS  
kohsuke 已提交
247
    	    tasks.add(item.task);
K
kohsuke 已提交
248 249
    	}
    	
K
kohsuke 已提交
250
        try {
K
TAB->WS  
kohsuke 已提交
251
            new XmlFile(xstream, getXMLQueueFile()).write(tasks);
252 253
        } catch (IOException e) {
            LOGGER.log(Level.WARNING, "Failed to write out the queue file " + getQueueFile(), e);
K
kohsuke 已提交
254 255 256 257
        }
    }

    private File getQueueFile() {
K
TAB->WS  
kohsuke 已提交
258 259
        return new File(Hudson.getInstance().getRootDir(), "queue.txt");
    }
K
kohsuke 已提交
260 261

    private File getXMLQueueFile() {
K
TAB->WS  
kohsuke 已提交
262 263
        return new File(Hudson.getInstance().getRootDir(), "queue.xml");
    }
K
kohsuke 已提交
264 265 266

    /**
     * Schedule a new build for this project.
267
     *
268 269 270
     * @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 已提交
271
     */
272 273
    public boolean add(AbstractProject p) {
        return add(p, p.getQuietPeriod());
274 275 276 277
    }

    /**
     * Schedules a new build with a custom quiet period.
278 279
     *
     * <p>
K
kohsuke 已提交
280 281
     * Left for backward compatibility with &lt;1.114.
     *
282 283
     * @since 1.105
     */
284 285
    public synchronized boolean add(AbstractProject p, int quietPeriod) {
        return add((Task) p, quietPeriod);
K
kohsuke 已提交
286 287 288 289 290
    }

    /**
     * Schedules an execution of a task.
     *
291 292 293
     * @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 已提交
294 295
     * @since 1.114
     */
296 297 298 299
    public synchronized boolean add(Task p, int quietPeriod) {
        Item item = getItem(p);
        Calendar due = new GregorianCalendar();
        due.add(Calendar.SECOND, quietPeriod);
300
        if (item != null) {
301 302 303 304 305 306
            if (!(item instanceof WaitingItem))
                // already in the blocked or buildable stage
                // no need to requeue
                return false;

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

308 309 310
            if(quietPeriod<=0) {
                // the user really wants to build now, and they mean NOW.
                // so let's pull in the timestamp if we can.
311
                if (wi.timestamp.before(due))
312 313 314
                    return false;
            } else {
                // otherwise we do the normal quiet period implementation
315
                if (wi.timestamp.after(due))
316
                    return false;
317
                // quiet period timer reset. start the period over again
318
            }
319 320 321 322 323

            // waitingList is sorted, so when we change a timestamp we need to maintain order
            waitingList.remove(wi);
            wi.timestamp = due;
            waitingList.add(wi);
324 325
        } else {
            LOGGER.fine(p.getName() + " added to queue");
326

327
            // put the item in the queue
328
            waitingList.add(new WaitingItem(due,p));
K
kohsuke 已提交
329

330
        }
K
kohsuke 已提交
331
        scheduleMaintenance();   // let an executor know that a new item is in the queue.
332
        return true;
K
kohsuke 已提交
333 334
    }

K
kohsuke 已提交
335 336 337
    /**
     * Cancels the item in the queue.
     *
338 339
     * @return true if the project was indeed in the queue and was removed.
     *         false if this was no-op.
K
kohsuke 已提交
340
     */
K
kohsuke 已提交
341
    public synchronized boolean cancel(Task p) {
342
        LOGGER.fine("Cancelling " + p.getName());
K
kohsuke 已提交
343 344
        for (Iterator<WaitingItem> itr = waitingList.iterator(); itr.hasNext();) {
            Item item = itr.next();
345
            if (item.task == p) {
K
kohsuke 已提交
346
                itr.remove();
K
kohsuke 已提交
347
                return true;
K
kohsuke 已提交
348 349
            }
        }
K
kohsuke 已提交
350
        // use bitwise-OR to make sure that both branches get evaluated all the time
351
        return blockedProjects.remove(p)!=null | buildables.remove(p)!=null;
K
kohsuke 已提交
352 353 354
    }

    public synchronized boolean isEmpty() {
355
        return waitingList.isEmpty() && blockedProjects.isEmpty() && buildables.isEmpty();
K
kohsuke 已提交
356 357
    }

358
    private synchronized WaitingItem peek() {
359
        return waitingList.iterator().next();
K
kohsuke 已提交
360 361 362 363 364
    }

    /**
     * Gets a snapshot of items in the queue.
     */
365
    @Exported(inline=true)
K
kohsuke 已提交
366
    public synchronized Item[] getItems() {
367 368 369
        Item[] r = new Item[waitingList.size() + blockedProjects.size() + buildables.size()];
        waitingList.toArray(r);
        int idx = waitingList.size();
370 371 372 373
        for (BlockedItem p : blockedProjects.values())
            r[idx++] = p;
        for (BuildableItem p : buildables.values())
            r[idx++] = p;
K
kohsuke 已提交
374 375 376
        return r;
    }

377 378 379 380
    public synchronized List<BuildableItem> getBuildableItems(Computer c) {
        List<BuildableItem> result = new ArrayList<BuildableItem>();
        for (BuildableItem p : buildables.values()) {
            Label l = p.task.getAssignedLabel();
381 382 383 384 385
            if (l != null) {
                // if a project has assigned label, it can be only built on it
                if (!l.contains(c.getNode()))
                    continue;
            }
386
            result.add(p);
387
        }
388
        return result;
389 390
    }

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

404
        for (Item item : waitingList) {
405
            if (item.task == t)
K
kohsuke 已提交
406 407 408 409 410
                return item;
        }
        return null;
    }

411 412
    /**
     * Left for backward compatibility.
413
     *
414 415
     * @see #getItem(Task)
    public synchronized Item getItem(AbstractProject p) {
416
        return getItem((Task) p);
417
    }
K
kohsuke 已提交
418
     */
419

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

    /**
     * Called by the executor to fetch something to build next.
435
     * <p>
K
kohsuke 已提交
436 437
     * This method blocks until a next project becomes buildable.
     */
438
    public Task pop() throws InterruptedException {
K
kohsuke 已提交
439
        final Executor exec = Executor.currentExecutor();
440

K
kohsuke 已提交
441
        try {
442
            while (true) {
K
kohsuke 已提交
443 444 445
                final JobOffer offer = new JobOffer(exec);
                long sleep = -1;

446
                synchronized (this) {
K
kohsuke 已提交
447 448
                    // consider myself parked
                    assert !parked.containsKey(exec);
449
                    parked.put(exec, offer);
K
kohsuke 已提交
450

K
kohsuke 已提交
451
                    // reuse executor thread to do a queue maintenance.
K
kohsuke 已提交
452 453 454 455 456
                    // at the end of this we get all the buildable jobs
                    // in the buildables field.
                    maintain();

                    // allocate buildable jobs to executors
457
                    Iterator<BuildableItem> itr = buildables.values().iterator();
458
                    while (itr.hasNext()) {
459
                        BuildableItem p = itr.next();
460 461

                        // one last check to make sure this build is not blocked.
462
                        if (isBuildBlocked(p.task)) {
463
                            itr.remove();
464
                            blockedProjects.put(p.task,new BlockedItem(p));
465 466
                            continue;
                        }
467

468
                        JobOffer runner = choose(p.task);
469
                        if (runner == null)
K
kohsuke 已提交
470 471 472 473 474 475 476 477 478 479 480 481 482 483 484
                            // 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.

485
                    if (!waitingList.isEmpty()) {
K
kohsuke 已提交
486
                        // wait until the first item in the queue is due
487 488
                        sleep = peek().timestamp.getTimeInMillis() - new GregorianCalendar().getTimeInMillis();
                        if (sleep < 100) sleep = 100;    // avoid wait(0)
K
kohsuke 已提交
489 490 491 492 493
                    }
                }

                // this needs to be done outside synchronized block,
                // so that executors can maintain a queue while others are sleeping
494
                if (sleep == -1)
K
kohsuke 已提交
495 496 497 498
                    offer.event.block();
                else
                    offer.event.block(sleep);

499
                synchronized (this) {
500
                    // retract the offer object
501
                    assert parked.get(exec) == offer;
502 503
                    parked.remove(exec);

K
kohsuke 已提交
504
                    // am I woken up because I have a project to build?
505 506
                    if (offer.item != null) {
                        LOGGER.fine("Pop returning " + offer.item + " for " + exec.getName());
K
kohsuke 已提交
507
                        // if so, just build it
508
                        return offer.item.task;
K
kohsuke 已提交
509 510 511 512 513
                    }
                    // otherwise run a queue maintenance
                }
            }
        } finally {
514
            synchronized (this) {
K
kohsuke 已提交
515
                // remove myself from the parked list
516
                JobOffer offer = parked.remove(exec);
517
                if (offer != null && offer.item != null) {
518 519 520 521 522
                    // 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.
523
                    if (!contains(offer.item.task))
524
                        buildables.put(offer.item.task,offer.item);
K
kohsuke 已提交
525
                }
526 527 528 529 530 531

                // 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 已提交
532 533 534 535 536
            }
        }
    }

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

548
        Label l = p.getAssignedLabel();
549
        if (l != null) {
550
            // if a project has assigned label, it can be only built on it
K
kohsuke 已提交
551
            for (JobOffer offer : parked.values()) {
552
                if (offer.isAvailable() && l.contains(offer.getNode()))
K
kohsuke 已提交
553 554 555 556 557
                    return offer;
            }
            return null;
        }

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

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

        // 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.
579 580
        // Similarly if we have many slaves, master should be made available
        // for HTTP requests and coordination as much as possible
581
        if (isLargeHudson || p.getEstimatedDuration() > 15 * 60 * 1000) {
K
kohsuke 已提交
582 583
            // consider a long job to be > 15 mins
            for (JobOffer offer : parked.values()) {
584
                if (offer.isAvailable() && offer.getNode() instanceof Slave && offer.isNotExclusive())
K
kohsuke 已提交
585 586 587 588 589 590
                    return offer;
            }
        }

        // lastly, just look for any idle executor
        for (JobOffer offer : parked.values()) {
591
            if (offer.isAvailable() && offer.isNotExclusive())
K
kohsuke 已提交
592 593 594 595 596 597 598 599 600
                return offer;
        }

        // nothing available
        return null;
    }

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

619 620 621
    /**
     * Checks if the given task is blocked.
     */
622
    private boolean isBuildBlocked(Task t) {
623 624 625
        return t.isBuildBlocked() || !canRun(t.getResourceList());
    }

K
kohsuke 已提交
626 627

    /**
K
kohsuke 已提交
628
     * Queue maintenance.
629
     * <p>
630
     * Move projects between {@link #waitingList}, {@link #blockedProjects}, and {@link #buildables}
K
kohsuke 已提交
631 632
     * appropriately.
     */
633
    public synchronized void maintain() {
634 635
        if (LOGGER.isLoggable(Level.FINE))
            LOGGER.fine("Queue maintenance started " + this);
636

637
        Iterator<BlockedItem> itr = blockedProjects.values().iterator();
638
        while (itr.hasNext()) {
639 640
            BlockedItem p = itr.next();
            if (!isBuildBlocked(p.task)) {
K
kohsuke 已提交
641
                // ready to be executed
642
                LOGGER.fine(p.task.getName() + " no longer blocked");
K
kohsuke 已提交
643
                itr.remove();
644
                buildables.put(p.task,new BuildableItem(p));
K
kohsuke 已提交
645 646 647
            }
        }

648
        while (!waitingList.isEmpty()) {
649
            WaitingItem top = peek();
K
kohsuke 已提交
650

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

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

670 671 672 673
    public Api getApi() {
        return new Api(this);
    }

K
kohsuke 已提交
674 675
    /**
     * Task whose execution is controlled by the queue.
676
     * <p>
K
kohsuke 已提交
677 678 679 680
     * {@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.
     */
681
    public interface Task extends ModelObject, ResourceActivity {
682
        /**
683 684 685
         * 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.
686
         */
687
        Label getAssignedLabel();
688 689 690 691 692 693 694 695 696 697 698

        /**
         * 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.
699 700
         *
         * <p>
K
kohsuke 已提交
701 702
         * This can be used to define mutual exclusion that goes beyond
         * {@link #getResourceList()}.
703 704 705 706 707 708 709 710 711 712 713
         */
        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 已提交
714
         * Unique name of this task.
K
kohsuke 已提交
715
         *
716 717
         * @see hudson.model.Item#getName()
         *      TODO: this doesn't make sense anymore. remove it.
718 719 720
         */
        String getName();

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

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

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

        /**
         * 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 已提交
746 747 748 749 750 751

        /**
         * Works just like {@link #checkAbortPermission()} except it indicates the status by a return value,
         * instead of exception.
         */
        boolean hasAbortPermission();
752 753 754 755 756
    }

    public interface Executable extends Runnable {
        /**
         * Task from which this executable was created.
K
kohsuke 已提交
757
         * Never null.
758 759 760 761 762 763 764
         */
        Task getParent();

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

K
kohsuke 已提交
767 768 769
    /**
     * Item in a queue.
     */
770
    @ExportedBean(defaultVisibility = 999)
771
    public abstract class Item {
K
kohsuke 已提交
772 773 774
        /**
         * Project to be built.
         */
775
        @Exported
776
        public final Task task;
K
kohsuke 已提交
777

778
        /**
779 780 781
         * Build is blocked because another build is in progress,
         * required {@link Resource}s are not available, or otherwise blocked
         * by {@link Task#isBuildBlocked()}.
782
         */
K
kohsuke 已提交
783
        @Exported
784
        public boolean isBlocked() { return this instanceof BlockedItem; }
785 786 787 788 789 790

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

794
        protected Item(Task project) {
795
            this.task = project;
K
kohsuke 已提交
796 797
        }

798 799 800
        /**
         * Gets a human-readable status message describing why it's in the queu.
         */
K
kohsuke 已提交
801
        @Exported
802
        public abstract String getWhy();
K
kohsuke 已提交
803

804 805 806 807
        public boolean hasCancelPermission() {
            return task.hasAbortPermission();
        }
    }
808

809 810 811 812 813 814 815 816 817
    /**
     * {@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;
818

K
kohsuke 已提交
819 820 821 822 823 824
        /**
         * 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;

825 826 827
        WaitingItem(Calendar timestamp, Task project) {
            super(project);
            this.timestamp = timestamp;
K
kohsuke 已提交
828 829 830
            synchronized (Queue.this) {
                this.id = iota++;
            }
831 832 833 834 835 836 837 838 839 840 841
        }

        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() {
842
            long diff = timestamp.getTimeInMillis() - System.currentTimeMillis();
843
            if (diff > 0)
K
i18n  
kohsuke 已提交
844
                return Messages.Queue_InQuietPeriod(Util.getTimeSpanString(diff));
845 846 847 848
            else
                return Messages.Queue_Unknown();
        }
    }
K
kohsuke 已提交
849

850 851 852 853 854 855 856 857 858 859 860 861 862
    /**
     * 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 已提交
863
        }
864

865 866 867
        protected NotWaitingItem(NotWaitingItem ni) {
            super(ni.task);
            buildableStartMilliseconds = ni.buildableStartMilliseconds;
868
        }
869
    }
870

871 872 873 874 875 876 877
    /**
     * {@link Item} in the {@link Queue#blockedProjects} stage.
     */
    public final class BlockedItem extends NotWaitingItem {
        public BlockedItem(WaitingItem wi) {
            super(wi);
        }
878

879 880 881 882 883 884 885 886 887 888 889 890 891
        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();
892
        }
893
    }
894

895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926
    /**
     * {@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 已提交
927 928 929 930 931
    }

    /**
     * Unique number generator
     */
932
    private int iota = 0;
K
kohsuke 已提交
933 934

    private static final Logger LOGGER = Logger.getLogger(Queue.class.getName());
935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951

    /**
     * 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();
952
            if (q != null)
953 954 955 956 957
                q.maintain();
            else
                cancel();
        }
    }
K
kohsuke 已提交
958
}