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

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

12 13 14 15 16
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
17
import java.lang.ref.WeakReference;
K
kohsuke 已提交
18 19 20 21 22 23 24 25 26 27
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;
28
import java.util.NoSuchElementException;
S
stephenconnolly 已提交
29
import java.util.Map.Entry;
K
kohsuke 已提交
30 31 32
import java.util.logging.Level;
import java.util.logging.Logger;

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

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

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

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

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

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

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

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

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

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

149 150 151 152 153 154
    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 已提交
155 156 157 158 159
    /**
     * Loads the queue contents that was {@link #save() saved}.
     */
    public synchronized void load() {
        try {
K
TAB->WS  
kohsuke 已提交
160
            // first try the old format
K
kohsuke 已提交
161
            File queueFile = getQueueFile();
K
kohsuke 已提交
162 163
            if (queueFile.exists()) {
                BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(queueFile)));
K
TAB->WS  
kohsuke 已提交
164 165 166 167 168 169 170 171 172 173 174 175
                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()) {
176
                    List<Task> tasks = (List<Task>) new XmlFile(XSTREAM, queueFile).read();
K
TAB->WS  
kohsuke 已提交
177 178 179
                    for (Task task : tasks) {
                        add(task, 0);
                    }
180 181 182 183 184 185 186 187 188

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

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

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

K
kohsuke 已提交
226
    private File getQueueFile() {
K
TAB->WS  
kohsuke 已提交
227 228
        return new File(Hudson.getInstance().getRootDir(), "queue.txt");
    }
K
kohsuke 已提交
229

230
    /*package*/ File getXMLQueueFile() {
K
TAB->WS  
kohsuke 已提交
231 232
        return new File(Hudson.getInstance().getRootDir(), "queue.xml");
    }
K
kohsuke 已提交
233 234 235

    /**
     * Schedule a new build for this project.
236
     *
237 238 239
     * @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 已提交
240
     */
241 242
    public boolean add(AbstractProject p) {
        return add(p, p.getQuietPeriod());
243 244 245 246
    }

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

    /**
     * Schedules an execution of a task.
     *
260 261 262
     * @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 已提交
263 264
     * @since 1.114
     */
265 266 267 268
    public synchronized boolean add(Task p, int quietPeriod) {
        Item item = getItem(p);
        Calendar due = new GregorianCalendar();
        due.add(Calendar.SECOND, quietPeriod);
269
        if (item != null) {
270 271 272 273 274 275
            if (!(item instanceof WaitingItem))
                // already in the blocked or buildable stage
                // no need to requeue
                return false;

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

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

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

296
            // put the item in the queue
297
            waitingList.add(new WaitingItem(due,p));
K
kohsuke 已提交
298

299
        }
K
kohsuke 已提交
300
        scheduleMaintenance();   // let an executor know that a new item is in the queue.
301
        return true;
K
kohsuke 已提交
302 303
    }

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

    public synchronized boolean isEmpty() {
324
        return waitingList.isEmpty() && blockedProjects.isEmpty() && buildables.isEmpty();
K
kohsuke 已提交
325 326
    }

327
    private synchronized WaitingItem peek() {
328
        return waitingList.iterator().next();
K
kohsuke 已提交
329 330 331 332 333
    }

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

346 347 348 349
    public synchronized List<BuildableItem> getBuildableItems(Computer c) {
        List<BuildableItem> result = new ArrayList<BuildableItem>();
        for (BuildableItem p : buildables.values()) {
            Label l = p.task.getAssignedLabel();
350 351 352 353 354
            if (l != null) {
                // if a project has assigned label, it can be only built on it
                if (!l.contains(c.getNode()))
                    continue;
            }
355
            result.add(p);
356
        }
357
        return result;
358 359
    }

K
kohsuke 已提交
360 361 362 363 364
    /**
     * Gets the information about the queue item for the given project.
     *
     * @return null if the project is not in the queue.
     */
365 366
    public synchronized Item getItem(Task t) {
        BlockedItem bp = blockedProjects.get(t);
367
        if (bp!=null)
368 369
            return bp;
        BuildableItem bi = buildables.get(t);
370
        if(bi!=null)
371 372
            return bi;

373
        for (Item item : waitingList) {
374
            if (item.task == t)
K
kohsuke 已提交
375 376 377 378 379
                return item;
        }
        return null;
    }

380 381
    /**
     * Left for backward compatibility.
382
     *
383 384
     * @see #getItem(Task)
    public synchronized Item getItem(AbstractProject p) {
385
        return getItem((Task) p);
386
    }
K
kohsuke 已提交
387
     */
388

K
kohsuke 已提交
389
    /**
K
kohsuke 已提交
390
     * Returns true if this queue contains the said project.
K
kohsuke 已提交
391
     */
392 393
    public synchronized boolean contains(Task t) {
        if (blockedProjects.containsKey(t) || buildables.containsKey(t))
K
kohsuke 已提交
394
            return true;
395
        for (Item item : waitingList) {
396
            if (item.task == t)
K
kohsuke 已提交
397 398 399 400 401 402 403
                return true;
        }
        return false;
    }

    /**
     * Called by the executor to fetch something to build next.
404
     * <p>
K
kohsuke 已提交
405 406
     * This method blocks until a next project becomes buildable.
     */
407
    public Task pop() throws InterruptedException {
K
kohsuke 已提交
408
        final Executor exec = Executor.currentExecutor();
409

K
kohsuke 已提交
410
        try {
411
            while (true) {
K
kohsuke 已提交
412 413 414
                final JobOffer offer = new JobOffer(exec);
                long sleep = -1;

415
                synchronized (this) {
K
kohsuke 已提交
416 417
                    // consider myself parked
                    assert !parked.containsKey(exec);
418
                    parked.put(exec, offer);
K
kohsuke 已提交
419

K
kohsuke 已提交
420
                    // reuse executor thread to do a queue maintenance.
K
kohsuke 已提交
421 422 423 424 425
                    // at the end of this we get all the buildable jobs
                    // in the buildables field.
                    maintain();

                    // allocate buildable jobs to executors
426
                    Iterator<BuildableItem> itr = buildables.values().iterator();
427
                    while (itr.hasNext()) {
428
                        BuildableItem p = itr.next();
429 430

                        // one last check to make sure this build is not blocked.
431
                        if (isBuildBlocked(p.task)) {
432
                            itr.remove();
433
                            blockedProjects.put(p.task,new BlockedItem(p));
434 435
                            continue;
                        }
436

437
                        JobOffer runner = choose(p.task);
438
                        if (runner == null)
K
kohsuke 已提交
439 440 441 442 443 444 445 446 447 448 449 450 451 452 453
                            // 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.

454
                    if (!waitingList.isEmpty()) {
K
kohsuke 已提交
455
                        // wait until the first item in the queue is due
456 457
                        sleep = peek().timestamp.getTimeInMillis() - new GregorianCalendar().getTimeInMillis();
                        if (sleep < 100) sleep = 100;    // avoid wait(0)
K
kohsuke 已提交
458 459 460 461 462
                    }
                }

                // this needs to be done outside synchronized block,
                // so that executors can maintain a queue while others are sleeping
463
                if (sleep == -1)
K
kohsuke 已提交
464 465 466 467
                    offer.event.block();
                else
                    offer.event.block(sleep);

468
                synchronized (this) {
469
                    // retract the offer object
470
                    assert parked.get(exec) == offer;
471 472
                    parked.remove(exec);

K
kohsuke 已提交
473
                    // am I woken up because I have a project to build?
474 475
                    if (offer.item != null) {
                        LOGGER.fine("Pop returning " + offer.item + " for " + exec.getName());
K
kohsuke 已提交
476
                        // if so, just build it
477
                        return offer.item.task;
K
kohsuke 已提交
478 479 480 481 482
                    }
                    // otherwise run a queue maintenance
                }
            }
        } finally {
483
            synchronized (this) {
K
kohsuke 已提交
484
                // remove myself from the parked list
485
                JobOffer offer = parked.remove(exec);
486
                if (offer != null && offer.item != null) {
487 488 489 490 491
                    // 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.
492
                    if (!contains(offer.item.task))
493
                        buildables.put(offer.item.task,offer.item);
K
kohsuke 已提交
494
                }
495 496 497 498 499 500

                // 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 已提交
501 502 503 504 505
            }
        }
    }

    /**
K
kohsuke 已提交
506
     * Chooses the executor to carry out the build for the given project.
K
kohsuke 已提交
507
     *
508
     * @return null if no {@link Executor} can run it.
K
kohsuke 已提交
509
     */
510
    private JobOffer choose(Task p) {
511
        if (Hudson.getInstance().isQuietingDown()) {
K
kohsuke 已提交
512 513 514 515 516
            // if we are quieting down, don't run anything so that
            // all executors will be free.
            return null;
        }

517
        Label l = p.getAssignedLabel();
518
        if (l != null) {
519
            // if a project has assigned label, it can be only built on it
K
kohsuke 已提交
520
            for (JobOffer offer : parked.values()) {
521
                if (offer.isAvailable() && l.contains(offer.getNode()))
K
kohsuke 已提交
522 523 524 525 526
                    return offer;
            }
            return null;
        }

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

530
        // otherwise let's see if the last node where this project was built is available
K
kohsuke 已提交
531 532
        // it has up-to-date workspace, so that's usually preferable.
        // (but we can't use an exclusive node)
533
        Node n = p.getLastBuiltOn();
534
        if (n != null && n.getMode() == Mode.NORMAL) {
K
kohsuke 已提交
535
            for (JobOffer offer : parked.values()) {
536 537
                if (offer.isAvailable() && offer.getNode() == n) {
                    if (isLargeHudson && offer.getNode() instanceof Slave)
S
stephenconnolly 已提交
538
                        // but if we are a large Hudson, then we really do want to keep the master free from builds
539
                        continue;
K
kohsuke 已提交
540
                    return offer;
541
                }
K
kohsuke 已提交
542 543 544 545 546 547
            }
        }

        // 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.
548 549
        // Similarly if we have many slaves, master should be made available
        // for HTTP requests and coordination as much as possible
550
        if (isLargeHudson || p.getEstimatedDuration() > 15 * 60 * 1000) {
K
kohsuke 已提交
551 552
            // consider a long job to be > 15 mins
            for (JobOffer offer : parked.values()) {
553
                if (offer.isAvailable() && offer.getNode() instanceof Slave && offer.isNotExclusive())
K
kohsuke 已提交
554 555 556 557 558 559
                    return offer;
            }
        }

        // lastly, just look for any idle executor
        for (JobOffer offer : parked.values()) {
560
            if (offer.isAvailable() && offer.isNotExclusive())
K
kohsuke 已提交
561 562 563 564 565 566 567 568 569
                return offer;
        }

        // nothing available
        return null;
    }

    /**
     * Checks the queue and runs anything that can be run.
570 571
     *
     * <p>
K
kohsuke 已提交
572
     * When conditions are changed, this method should be invoked.
573
     * <p>
K
kohsuke 已提交
574 575 576 577 578 579 580
     * 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()) {
581
            if (av.getValue().item == null) {
K
kohsuke 已提交
582 583 584 585 586 587
                av.getValue().event.signal();
                return;
            }
        }
    }

588 589 590
    /**
     * Checks if the given task is blocked.
     */
591
    private boolean isBuildBlocked(Task t) {
592 593 594
        return t.isBuildBlocked() || !canRun(t.getResourceList());
    }

K
kohsuke 已提交
595 596

    /**
K
kohsuke 已提交
597
     * Queue maintenance.
598
     * <p>
599
     * Move projects between {@link #waitingList}, {@link #blockedProjects}, and {@link #buildables}
K
kohsuke 已提交
600 601
     * appropriately.
     */
602
    public synchronized void maintain() {
603 604
        if (LOGGER.isLoggable(Level.FINE))
            LOGGER.fine("Queue maintenance started " + this);
605

606
        Iterator<BlockedItem> itr = blockedProjects.values().iterator();
607
        while (itr.hasNext()) {
608 609
            BlockedItem p = itr.next();
            if (!isBuildBlocked(p.task)) {
K
kohsuke 已提交
610
                // ready to be executed
611
                LOGGER.fine(p.task.getName() + " no longer blocked");
K
kohsuke 已提交
612
                itr.remove();
613
                buildables.put(p.task,new BuildableItem(p));
K
kohsuke 已提交
614 615 616
            }
        }

617
        while (!waitingList.isEmpty()) {
618
            WaitingItem top = peek();
K
kohsuke 已提交
619

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

623
            Task p = top.task;
624
            if (!isBuildBlocked(p)) {
K
kohsuke 已提交
625
                // ready to be executed immediately
626
                waitingList.remove(top);
627
                LOGGER.fine(p.getName() + " ready to build");
628
                buildables.put(p,new BuildableItem(top));
K
kohsuke 已提交
629
            } else {
630
                // this can't be built now because another build is in progress
K
kohsuke 已提交
631
                // set this project aside.
632
                waitingList.remove(top);
633
                LOGGER.fine(p.getName() + " is blocked");
634
                blockedProjects.put(p,new BlockedItem(top));
K
kohsuke 已提交
635 636 637 638
            }
        }
    }

639 640 641 642
    public Api getApi() {
        return new Api(this);
    }

K
kohsuke 已提交
643 644
    /**
     * Task whose execution is controlled by the queue.
645
     *
646
     * <p>
K
kohsuke 已提交
647 648 649
     * {@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.
650 651 652 653
     *
     * <p>
     * Pending {@link Task}s are persisted when Hudson shuts down, so
     * it needs to be persistable.
K
kohsuke 已提交
654
     */
655
    public interface Task extends ModelObject, ResourceActivity {
656
        /**
657 658 659
         * 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.
660
         */
661
        Label getAssignedLabel();
662 663 664 665 666 667 668 669 670 671 672

        /**
         * 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.
673 674
         *
         * <p>
K
kohsuke 已提交
675 676
         * This can be used to define mutual exclusion that goes beyond
         * {@link #getResourceList()}.
677 678 679 680 681 682 683 684 685 686 687
         */
        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 已提交
688
         * Unique name of this task.
K
kohsuke 已提交
689
         *
690 691
         * @see hudson.model.Item#getName()
         *      TODO: this doesn't make sense anymore. remove it.
692 693 694
         */
        String getName();

695 696 697 698 699
        /**
         * @see hudson.model.Item#getFullDisplayName()
         */
        String getFullDisplayName();

700 701 702 703
        /**
         * Estimate of how long will it take to execute this task.
         * Measured in milliseconds.
         *
704
         * @return -1 if it's impossible to estimate.
705 706 707
         */
        long getEstimatedDuration();

K
kohsuke 已提交
708
        /**
709
         * Creates {@link Executable}, which performs the actual execution of the task.
K
kohsuke 已提交
710
         */
711
        Executable createExecutable() throws IOException;
712 713 714 715 716 717 718 719

        /**
         * 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 已提交
720 721 722 723 724 725

        /**
         * Works just like {@link #checkAbortPermission()} except it indicates the status by a return value,
         * instead of exception.
         */
        boolean hasAbortPermission();
726 727 728 729 730
    }

    public interface Executable extends Runnable {
        /**
         * Task from which this executable was created.
K
kohsuke 已提交
731
         * Never null.
732 733 734 735 736 737 738
         */
        Task getParent();

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

K
kohsuke 已提交
741 742 743
    /**
     * Item in a queue.
     */
744
    @ExportedBean(defaultVisibility = 999)
745
    public abstract class Item {
K
kohsuke 已提交
746 747 748
        /**
         * Project to be built.
         */
749
        @Exported
750
        public final Task task;
K
kohsuke 已提交
751

752
        /**
753 754 755
         * Build is blocked because another build is in progress,
         * required {@link Resource}s are not available, or otherwise blocked
         * by {@link Task#isBuildBlocked()}.
756
         */
K
kohsuke 已提交
757
        @Exported
758
        public boolean isBlocked() { return this instanceof BlockedItem; }
759 760 761 762 763 764

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

768
        protected Item(Task project) {
769
            this.task = project;
K
kohsuke 已提交
770 771
        }

772 773 774
        /**
         * Gets a human-readable status message describing why it's in the queu.
         */
K
kohsuke 已提交
775
        @Exported
776
        public abstract String getWhy();
K
kohsuke 已提交
777

778 779 780 781
        public boolean hasCancelPermission() {
            return task.hasAbortPermission();
        }
    }
782

783 784 785 786 787 788 789 790 791
    /**
     * {@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;
792

K
kohsuke 已提交
793 794 795 796 797 798
        /**
         * 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;

799 800 801
        WaitingItem(Calendar timestamp, Task project) {
            super(project);
            this.timestamp = timestamp;
K
kohsuke 已提交
802 803 804
            synchronized (Queue.this) {
                this.id = iota++;
            }
805 806 807 808 809 810 811 812 813 814 815
        }

        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() {
816
            long diff = timestamp.getTimeInMillis() - System.currentTimeMillis();
817
            if (diff > 0)
K
i18n  
kohsuke 已提交
818
                return Messages.Queue_InQuietPeriod(Util.getTimeSpanString(diff));
819 820 821 822
            else
                return Messages.Queue_Unknown();
        }
    }
K
kohsuke 已提交
823

824 825 826 827 828 829 830 831 832 833 834 835 836
    /**
     * 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 已提交
837
        }
838

839 840 841
        protected NotWaitingItem(NotWaitingItem ni) {
            super(ni.task);
            buildableStartMilliseconds = ni.buildableStartMilliseconds;
842
        }
843
    }
844

845 846 847 848 849 850 851
    /**
     * {@link Item} in the {@link Queue#blockedProjects} stage.
     */
    public final class BlockedItem extends NotWaitingItem {
        public BlockedItem(WaitingItem wi) {
            super(wi);
        }
852

853 854 855 856 857 858 859 860 861 862 863 864 865
        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();
866
        }
867
    }
868

869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900
    /**
     * {@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 已提交
901 902 903 904 905
    }

    /**
     * Unique number generator
     */
906
    private int iota = 0;
K
kohsuke 已提交
907 908

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

910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963
    /**
     * This {@link XStream} instance is used to persist {@link Task}s.
     */
    public static final XStream XSTREAM = new XStream2();

    static {
        XSTREAM.registerConverter(new AbstractSingleValueConverter() {

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

			@Override
			public Object fromString(String string) {
                Object item = Hudson.getInstance().getItemByFullName(string);
                if(item==null)  throw new NoSuchElementException("No such job exists: "+string);
                return item;
			}

			@Override
			public String toString(Object item) {
				return ((hudson.model.Item) item).getFullName();
			}
        });
        XSTREAM.registerConverter(new AbstractSingleValueConverter() {

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

			@Override
			public Object fromString(String string) {
				String[] split = string.split("#");
				String projectName = split[0];
				int buildNumber = Integer.parseInt(split[1]);
				Job<?,?> job = (Job<?,?>) Hudson.getInstance().getItemByFullName(projectName);
                if(job==null)  throw new NoSuchElementException("No such job exists: "+projectName);
				Run<?,?> run = job.getBuildByNumber(buildNumber);
                if(job==null)  throw new NoSuchElementException("No such build: "+string);
				return run;
			}

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

964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979
    /**
     * 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();
980
            if (q != null)
981 982 983 984 985
                q.maintain();
            else
                cancel();
        }
    }
K
kohsuke 已提交
986
}