package hudson.model; import hudson.Util; import org.kohsuke.stapler.export.ExportedBean; import org.kohsuke.stapler.export.Exported; import org.acegisecurity.AccessDeniedException; import hudson.model.Node.Mode; import hudson.util.OneShotEvent; 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; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeSet; import java.util.logging.Level; import java.util.logging.Logger; /** * Build queue. * *
* 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} * so that we can keep track of additional data used for deciding what to exeucte when. * * @author Kohsuke Kawaguchi */ public class Queue extends ResourceController { /** * Items in the queue ordered by {@link Item#timestamp}. * *
* This consists of {@link Item}s that cannot be run yet
* because its time has not yet come.
*/
private final Set
* It eventually receives a {@link #task} to build.
*/
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.)
*/
Task task;
public JobOffer(Executor executor) {
this.executor = executor;
}
public void set(Task p) {
assert this.task ==null;
this.task = p;
event.signal();
}
public boolean isAvailable() {
return task ==null && !executor.getOwner().isOffline();
}
public Node getNode() {
return executor.getOwner().getNode();
}
public boolean isNotExclusive() {
return getNode().getMode()== Mode.NORMAL;
}
}
private final Map
* Left for backward compatibility with <1.114.
*
* @since 1.105
*/
public synchronized boolean add( AbstractProject p, int quietPeriod ) {
return add((Task)p,quietPeriod);
}
/**
* Schedules an execution of a task.
*
* @param quietPeriod
* Number of seconds that the task will be placed in queue.
* Useful when the same task is likely scheduled for multiple
* times.
* @since 1.114
*/
public synchronized boolean add( Task p, int quietPeriod ) {
if(contains(p))
return false; // no double queueing
LOGGER.fine(p.getName()+" added to queue");
// put the item in the queue
Calendar due = new GregorianCalendar();
due.add(Calendar.SECOND, quietPeriod);
queue.add(new Item(due,p));
scheduleMaintenance(); // let an executor know that a new item is in the queue.
return true;
}
/**
* Cancels the item in the queue.
*
* @return
* true if the project was indeed in the queue and was removed.
* false if this was no-op.
*/
public synchronized boolean cancel( AbstractProject,?> p ) {
LOGGER.fine("Cancelling "+p.getName());
for (Iterator itr = queue.iterator(); itr.hasNext();) {
Item item = (Item) itr.next();
if(item.task ==p) {
itr.remove();
return true;
}
}
// use bitwise-OR to make sure that both branches get evaluated all the time
return blockedProjects.remove(p)|buildables.remove(p);
}
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() {
Item[] r = new Item[queue.size()+blockedProjects.size()+buildables.size()];
queue.toArray(r);
int idx=queue.size();
Calendar now = new GregorianCalendar();
for (Task p : blockedProjects) {
r[idx++] = new Item(now, p, true, false);
}
for (Task p : buildables) {
r[idx++] = new Item(now, p, false, true);
}
return r;
}
/**
* Gets the information about the queue item for the given project.
*
* @return null if the project is not in the queue.
*/
public synchronized Item getItem(Task p) {
if(blockedProjects.contains(p))
return new Item(new GregorianCalendar(),p,true,false);
if(buildables.contains(p))
return new Item(new GregorianCalendar(),p,false,true);
for (Item item : queue) {
if (item.task == p)
return item;
}
return null;
}
/**
* Left for backward compatibility.
*
* @see #getItem(Task)
*/
public synchronized Item getItem(AbstractProject p) {
return getItem((Task)p);
}
/**
* Returns true if this queue contains the said project.
*/
public synchronized boolean contains(Task p) {
if(blockedProjects.contains(p) || buildables.contains(p))
return true;
for (Item item : queue) {
if (item.task == p)
return true;
}
return false;
}
/**
* Called by the executor to fetch something to build next.
*
* This method blocks until a next project becomes buildable.
*/
public Task pop() throws InterruptedException {
final Executor exec = Executor.currentExecutor();
try {
while(true) {
final JobOffer offer = new JobOffer(exec);
long sleep = -1;
synchronized(this) {
// consider myself parked
assert !parked.containsKey(exec);
parked.put(exec,offer);
// reuse executor thread to do a queue maintenance.
// at the end of this we get all the buildable jobs
// in the buildables field.
maintain();
// allocate buildable jobs to executors
Iterator
* When conditions are changed, this method should be invoked.
*
* 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
* {@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.
*/
public interface Task extends ModelObject, ResourceActivity {
/**
* 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.
*/
Label getAssignedLabel();
/**
* 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.
*
*
* This can be used to define mutual exclusion that goes beyond
* {@link #getResourceList()}.
*/
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();
/**
* Unique name of this task.
* @see hudson.model.Item#getName()
*
* TODO: this doesn't make sense anymore. remove it.
*/
String getName();
/**
* @see hudson.model.Item#getFullDisplayName()
*/
String getFullDisplayName();
/**
* Estimate of how long will it take to execute this task.
* Measured in milliseconds.
*
* @return
* -1 if it's impossible to estimate.
*/
long getEstimatedDuration();
/**
* Creates {@link Executable}, which performs the actual execution of the task.
*/
Executable createExecutable() throws IOException;
/**
* 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();
/**
* Works just like {@link #checkAbortPermission()} except it indicates the status by a return value,
* instead of exception.
*/
boolean hasAbortPermission();
}
public interface Executable extends Runnable {
/**
* Task from which this executable was created.
* Never null.
*/
Task getParent();
/**
* Called by {@link Executor} to perform the task
*/
void run();
}
/**
* Item in a queue.
*/
@ExportedBean(defaultVisibility=999)
public final class Item implements Comparable