提交 c397933f 编写于 作者: H huybrechts

[FIXED HUDSON-2997] introducing QueueAction to pass actions from scheduling to the build it self

git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@15291 71c3de6d-444a-0410-be80-ed276b4c234a
上级 583b3d37
......@@ -26,6 +26,7 @@ package hudson.matrix;
import hudson.FilePath;
import hudson.model.AbstractBuild;
import hudson.model.Cause;
import hudson.model.CauseAction;
import hudson.model.DependencyGraph;
import hudson.model.Descriptor;
import hudson.model.Hudson;
......@@ -33,13 +34,11 @@ import hudson.model.Item;
import hudson.model.ItemGroup;
import hudson.model.JDK;
import hudson.model.Label;
import hudson.model.Cause.LegacyCodeCause;
import hudson.model.Node;
import hudson.model.ParameterValue;
import hudson.model.ParameterizedProjectTask;
import hudson.model.ParametersAction;
import hudson.model.Project;
import hudson.model.SCMedItem;
import hudson.model.Cause.LegacyCodeCause;
import hudson.scm.SCM;
import hudson.tasks.BuildWrapper;
import hudson.tasks.Builder;
......@@ -47,8 +46,8 @@ import hudson.tasks.LogRotator;
import hudson.tasks.Publisher;
import java.io.IOException;
import java.util.Map;
import java.util.List;
import java.util.Map;
/**
* One configuration of {@link MatrixProject}.
......@@ -293,8 +292,7 @@ public class MatrixConfiguration extends Project<MatrixConfiguration,MatrixRun>
}
public boolean scheduleBuild(ParametersAction parameters, Cause c) {
return Hudson.getInstance().getQueue().add(
new ParameterizedProjectTask(this, parameters.getParameters(), c), getQuietPeriod());
return Hudson.getInstance().getQueue().add(this, getQuietPeriod(), parameters, new CauseAction(c));
}
}
......@@ -64,6 +64,7 @@ import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
......@@ -450,10 +451,10 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
return false;
if (isParameterized())
return Hudson.getInstance().getQueue().add(
new ParameterizedProjectTask(this, getDefaultParametersValues(), c), quietPeriod);
return Hudson.getInstance().getQueue().add(
this, quietPeriod, new ParametersAction(getDefaultParametersValues()), new CauseAction(c));
else
return Hudson.getInstance().getQueue().add(new ParameterizedProjectTask(this, c), quietPeriod);
return Hudson.getInstance().getQueue().add(this, quietPeriod, new CauseAction(c));
}
private List<ParameterValue> getDefaultParametersValues() {
......@@ -1016,10 +1017,8 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
// TODO: more unit handling
if(delay.endsWith("sec")) delay=delay.substring(0,delay.length()-3);
if(delay.endsWith("secs")) delay=delay.substring(0,delay.length()-4);
Hudson.getInstance().getQueue().add(
new ParameterizedProjectTask(this, new UserCause()),
Integer.parseInt(delay)
);
Hudson.getInstance().getQueue().add(this, Integer.parseInt(delay),
new CauseAction(new UserCause()));
} catch (NumberFormatException e) {
throw new ServletException("Invalid delay parameter value: "+delay);
}
......
......@@ -92,13 +92,14 @@ public class Executor extends Thread implements ModelObject {
}
}
Queue.Task task;
Queue.Item queueItem;
try {
task = queue.pop();
queueItem = queue.pop();
} catch (InterruptedException e) {
continue;
}
Queue.Task task = queueItem.task;
Throwable problems = null;
owner.taskAccepted(this, task);
try {
......@@ -110,6 +111,11 @@ public class Executor extends Thread implements ModelObject {
startTime = System.currentTimeMillis();
executable = task.createExecutable();
if (executable instanceof Actionable) {
for (Action action: queueItem.getActions()) {
((Actionable) executable).addAction(action);
}
}
queue.execute(executable, task);
} catch (Throwable e) {
// for some reason the executor died. this is really
......
......@@ -73,4 +73,34 @@ public class FileParameterValue extends ParameterValue {
}
};
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result
+ ((location == null) ? 0 : location.hashCode());
return result;
}
/**
* In practice this will always be false, since location should be unique.
*/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
return false;
if (getClass() != obj.getClass())
return false;
FileParameterValue other = (FileParameterValue) obj;
if (location == null) {
if (other.location != null)
return false;
} else if (!location.equals(other.location))
return false;
return true;
}
}
......@@ -62,7 +62,7 @@ import net.sf.json.JSONObject;
*/
@ExportedBean
public abstract class ParameterValue {
protected final String name;
protected final String name;
protected ParameterValue(String name) {
this.name = name;
......@@ -153,4 +153,30 @@ public abstract class ParameterValue {
public ParameterDefinition getDefinition() {
throw new UnsupportedOperationException();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ParameterValue other = (ParameterValue) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi, Tom Huybrechts
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.model;
import hudson.model.Queue.Executable;
import hudson.util.QueueTaskFilter;
import java.io.IOException;
import java.util.List;
import javax.servlet.ServletException;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
/**
* A task representing a project that should be built with a certain set of
* parameter values
*/
public class ParameterizedProjectTask extends QueueTaskFilter {
private final AbstractProject<?,?> project;
private final List<ParameterValue> parameters;
private final Cause cause;
private static long COUNTER = System.currentTimeMillis();
/**
* Used for identifying the task in the queue
*/
private final String key = Long.toString(COUNTER++);
/**
* @deprecated
* Use {@link #ParameterizedProjectTask(AbstractProject, List, Cause)}. Since 1.283
*/
public ParameterizedProjectTask(AbstractProject<?,?> project, List<ParameterValue> parameters) {
this(project, parameters, new Cause.LegacyCodeCause());
}
public ParameterizedProjectTask(AbstractProject<?,?> project, List<ParameterValue> parameters, Cause c) {
super(project);
this.project = project;
this.parameters = parameters;
this.cause = c;
}
public ParameterizedProjectTask(AbstractProject<?,?> project, Cause c) {
this(project, null, c);
}
public AbstractProject<?, ?> getProject() {
return project;
}
public List<ParameterValue> getParameters() {
return parameters;
}
@Override
public Executable createExecutable() throws IOException {
AbstractBuild<?, ?> build = project.createExecutable();
if(parameters != null)
build.addAction(new ParametersAction(parameters, build));
build.addAction(new CauseAction(cause));
return build;
}
/**
* Cancels a scheduled build.
*/
public void doCancelQueue( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
project.checkPermission(AbstractProject.BUILD);
Hudson.getInstance().getQueue().cancel(this);
rsp.forwardToPreviousPage(req);
}
@Override
public int hashCode() {
// cause is NOT included so distinct causes won't schedule duplicate builds
final int prime = 31;
int result = 1;
result = prime * result
+ ((parameters == null) ? 0 : parameters.hashCode());
result = prime * result + ((project == null) ? 0 : project.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
// cause is NOT included so distinct causes won't schedule duplicate builds
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ParameterizedProjectTask other = (ParameterizedProjectTask) obj;
if (parameters == null) {
if (other.parameters != null)
return false;
} else if (!parameters.equals(other.parameters)) {
return false;
}
if (project != other.project) {
return false;
}
return true;
}
public String getUrl() {
return getProject().getUrl() + "/parameters/queued/" + key + "/";
}
public String getQueueKey() {
return key;
}
}
......@@ -24,33 +24,41 @@
package hudson.model;
import hudson.Util;
import hudson.tasks.BuildWrapper;
import hudson.model.Queue.QueueAction;
import hudson.tasks.BuildStep;
import hudson.tasks.BuildWrapper;
import hudson.util.VariableResolver;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Records the parameter values used for a build.
*
* <P>
* This object is associated with the build record so that we remember what parameters
* were used for what build.
* were used for what build. It is also attached to the queue item to remember parameter
* that were specified when scheduling.
*/
@ExportedBean
public class ParametersAction implements Action, Iterable<ParameterValue> {
public class ParametersAction implements Action, Iterable<ParameterValue>, QueueAction {
private final List<ParameterValue> parameters;
private final AbstractBuild<?, ?> build;
public ParametersAction(List<ParameterValue> parameters, AbstractBuild<?, ?> build) {
public ParametersAction(List<ParameterValue> parameters) {
this.parameters = parameters;
this.build = build;
}
public ParametersAction(ParameterValue... parameters) {
this(Arrays.asList(parameters));
}
public void createBuildWrappers(AbstractBuild<?,?> build, Collection<? super BuildWrapper> result) {
......@@ -89,17 +97,13 @@ public class ParametersAction implements Action, Iterable<ParameterValue> {
return new VariableResolver.Union<String>(resolvers);
}
public AbstractBuild<?, ?> getBuild() {
return build;
}
public Iterator<ParameterValue> iterator() {
return parameters.iterator();
}
@Exported(visibility=2)
public List<ParameterValue> getParameters() {
return parameters;
return Collections.unmodifiableList(parameters);
}
public String getDisplayName() {
......@@ -113,4 +117,22 @@ public class ParametersAction implements Action, Iterable<ParameterValue> {
public String getUrlName() {
return "parameters";
}
/**
* Allow an other build of the same project to be scheduled, if it has other parameters parameters.
*/
public boolean shouldSchedule(List<Action> actions) {
List<ParametersAction> others = Util.filter(actions, ParametersAction.class);
if (others.isEmpty()) {
return parameters.isEmpty();
} else {
// I don't think we need multiple ParametersActions, but let's be defensive
Set<ParameterValue> parameters = new HashSet<ParameterValue>();
for (ParametersAction other: others) {
parameters.addAll(other.parameters);
}
return !parameters.equals(new HashSet<ParameterValue>(this.parameters));
}
}
}
......@@ -26,7 +26,6 @@ package hudson.model;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletException;
......@@ -97,8 +96,8 @@ public class ParametersDefinitionProperty extends JobProperty<AbstractProject<?,
values.add(d.createValue(req, jo));
}
Hudson.getInstance().getQueue().add(
new ParameterizedProjectTask(owner, values, new Cause.UserCause()), 0);
Hudson.getInstance().getQueue().add(
owner, 0, new ParametersAction(values), new CauseAction(new Cause.UserCause()));
// send the user back to the job top page.
rsp.sendRedirect(".");
......@@ -116,7 +115,7 @@ public class ParametersDefinitionProperty extends JobProperty<AbstractProject<?,
}
Hudson.getInstance().getQueue().add(
new ParameterizedProjectTask(owner, values, new Cause.UserCause()), 0);
owner, 0, new ParametersAction(values), new CauseAction(new Cause.UserCause()));
// send the user back to the job top page.
rsp.sendRedirect(".");
......@@ -182,19 +181,6 @@ public class ParametersDefinitionProperty extends JobProperty<AbstractProject<?,
return "parameters";
}
public ParameterizedProjectTask getQueued(String key) {
Queue.Item[] items = Hudson.getInstance().getQueue().getItems();
for (Queue.Item item: items) {
if (item.task instanceof ParameterizedProjectTask) {
ParameterizedProjectTask ppt = (ParameterizedProjectTask) item.task;
if (ppt.getProject() == owner && ppt.getQueueKey().equals(key)) {
return ppt;
}
}
}
return null;
}
static {
ParameterDefinition.LIST.add(StringParameterDefinition.DESCRIPTOR);
ParameterDefinition.LIST.add(FileParameterDefinition.DESCRIPTOR);
......
......@@ -40,17 +40,18 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
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.NoSuchElementException;
import java.util.Set;
import java.util.TreeSet;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
......@@ -102,27 +103,18 @@ public class Queue extends ResourceController implements Saveable {
private final Set<WaitingItem> waitingList = new TreeSet<WaitingItem>();
/**
* {@link Project}s that can be built immediately
* {@link Task}s that can be built immediately
* but blocked because another build is in progress,
* required {@link Resource}s are not available, or otherwise blocked
* by {@link Task#isBuildBlocked()}.
*
* <p>
* Conceptually a set of {@link BlockedItem}, but we often need to look up
* {@link BlockedItem} from {@link Task}, so organized as a map.
*/
private final Map<Task,BlockedItem> blockedProjects = new HashMap<Task,BlockedItem>();
private final ItemList<BlockedItem> blockedProjects = new ItemList<BlockedItem>();
/**
* {@link Project}s that can be built immediately
* {@link Task}s that can be built immediately
* that are waiting for available {@link Executor}.
*
* <p>
* 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}.
*/
private final LinkedHashMap<Task,BuildableItem> buildables = new LinkedHashMap<Task,BuildableItem>();
private final ItemList<BuildableItem> buildables = new ItemList<BuildableItem>();
/**
* Data structure created for each idle {@link Executor}.
......@@ -200,9 +192,29 @@ public class Queue extends ResourceController implements Saveable {
} else {
queueFile = getXMLQueueFile();
if (queueFile.exists()) {
List<Task> tasks = (List<Task>) new XmlFile(XSTREAM, queueFile).read();
for (Task task : tasks) {
add(task, 0);
List list = (List) new XmlFile(XSTREAM, queueFile).read();
if (!list.isEmpty()) {
if (list.get(0) instanceof Queue.Task) {
// backward compatiblity
for (Task task : (List<Task>) list) {
add(task, 0);
}
} else if (list.get(0) instanceof Item) {
int maxId = 0;
for (Item item: (List<Item>) list) {
maxId = Math.max(maxId, item.id);
if (item instanceof WaitingItem) {
waitingList.add((WaitingItem) item);
} else if (item instanceof BlockedItem) {
blockedProjects.put(item.task, (BlockedItem) item);
} else if (item instanceof BuildableItem) {
buildables.add((BuildableItem) item);
} else {
throw new IllegalStateException("Unknown item type! " + item);
}
}
WaitingItem.COUNTER.set(maxId);
}
}
// I just had an incident where all the executors are dead at AbstractProject._getRuns()
......@@ -228,13 +240,13 @@ public class Queue extends ResourceController implements Saveable {
if(BulkChange.contains(this)) return;
// write out the tasks on the queue
ArrayList<Task> tasks = new ArrayList<Task>();
ArrayList<Queue.Item> items = new ArrayList<Queue.Item>();
for (Item item: getItems()) {
tasks.add(item.task);
items.add(item);
}
try {
new XmlFile(XSTREAM, getXMLQueueFile()).write(tasks);
new XmlFile(XSTREAM, getXMLQueueFile()).write(items);
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Failed to write out the queue file " + getQueueFile(), e);
}
......@@ -289,47 +301,74 @@ public class Queue extends ResourceController implements Saveable {
* times.
* @since 1.114
*/
public synchronized boolean add(Task p, int quietPeriod) {
public synchronized boolean add(Task p, int quietPeriod, List<Action> actions) {
Item item = getItem(p);
Calendar due = new GregorianCalendar();
due.add(Calendar.SECOND, quietPeriod);
if (item != null) {
if (!(item instanceof WaitingItem))
// already in the blocked or buildable stage
// no need to requeue
return false;
WaitingItem wi = (WaitingItem) item;
if(quietPeriod<=0) {
// the user really wants to build now, and they mean NOW.
// so let's pull in the timestamp if we can.
if (wi.timestamp.before(due))
return false;
} else {
// otherwise we do the normal quiet period implementation
if (wi.timestamp.after(due))
return false;
// quiet period timer reset. start the period over again
}
// waitingList is sorted, so when we change a timestamp we need to maintain order
waitingList.remove(wi);
wi.timestamp = due;
waitingList.add(wi);
boolean shouldSchedule = false;
for (Action action: item.getActions()) {
if (action instanceof QueueAction)
shouldSchedule |= ((QueueAction) action).shouldSchedule(actions);
}
for (Action action: actions) {
if (action instanceof QueueAction) {
shouldSchedule |= ((QueueAction) action).shouldSchedule(item.getActions());
}
}
if (shouldSchedule) {
//TODO rework the if-s to only have this once
LOGGER.fine(p.getFullDisplayName() + " added to queue");
// put the item in the queue
waitingList.add(new WaitingItem(due,p,actions));
} else {
if (!(item instanceof WaitingItem))
// already in the blocked or buildable stage
// no need to requeue
return false;
WaitingItem wi = (WaitingItem) item;
if(quietPeriod<=0) {
// the user really wants to build now, and they mean NOW.
// so let's pull in the timestamp if we can.
if (wi.timestamp.before(due))
return false;
} else {
// otherwise we do the normal quiet period implementation
if (wi.timestamp.after(due))
return false;
// quiet period timer reset. start the period over again
}
// waitingList is sorted, so when we change a timestamp we need to maintain order
waitingList.remove(wi);
wi.timestamp = due;
waitingList.add(wi);
}
} else {
LOGGER.fine(p.getFullDisplayName() + " added to queue");
// put the item in the queue
waitingList.add(new WaitingItem(due,p));
waitingList.add(new WaitingItem(due,p,actions));
}
scheduleMaintenance(); // let an executor know that a new item is in the queue.
return true;
}
public synchronized boolean add(Task p, int quietPeriod) {
return add(p, quietPeriod, new Action[0]);
}
public synchronized boolean add(Task p, int quietPeriod, Action... actions) {
return add(p, quietPeriod, Arrays.asList(actions));
}
/**
* Cancels the item in the queue.
* Cancels the item in the queue. If the item is scheduled more than once, cancels the first occurrence.
*
* @return true if the project was indeed in the queue and was removed.
* false if this was no-op.
......@@ -346,6 +385,12 @@ public class Queue extends ResourceController implements Saveable {
// use bitwise-OR to make sure that both branches get evaluated all the time
return blockedProjects.remove(p)!=null | buildables.remove(p)!=null;
}
public synchronized boolean cancel(Item item) {
LOGGER.fine("Cancelling " + item.task.getFullDisplayName() + " item#" + item.id);
// use bitwise-OR to make sure that both branches get evaluated all the time
return (item instanceof WaitingItem && waitingList.remove(item)) | blockedProjects.remove(item) | buildables.remove(item);
}
public synchronized boolean isEmpty() {
return waitingList.isEmpty() && blockedProjects.isEmpty() && buildables.isEmpty();
......@@ -369,6 +414,13 @@ public class Queue extends ResourceController implements Saveable {
r[idx++] = p;
return r;
}
public synchronized Item getItem(int id) {
for (Item item: waitingList) if (item.id == id) return item;
for (Item item: blockedProjects) if (item.id == id) return item;
for (Item item: buildables) if (item.id == id) return item;
return null;
}
/**
* Gets all the {@link BuildableItem}s that are waiting for an executor in the given {@link Computer}.
......@@ -425,6 +477,22 @@ public class Queue extends ResourceController implements Saveable {
return null;
}
/**
* Gets the information about the queue item for the given project.
*
* @return null if the project is not in the queue.
*/
public synchronized List<Item> getItems(Task t) {
List<Item> result =new ArrayList<Item>();
result.addAll(blockedProjects.getAll(t));
result.addAll(buildables.getAll(t));
for (Item item : waitingList) {
if (item.task == t)
result.add(item);
}
return result;
}
/**
* Left for backward compatibility.
*
......@@ -452,7 +520,7 @@ public class Queue extends ResourceController implements Saveable {
* <p>
* This method blocks until a next project becomes buildable.
*/
public Task pop() throws InterruptedException {
public Queue.Item pop() throws InterruptedException {
final Executor exec = Executor.currentExecutor();
try {
......@@ -471,7 +539,7 @@ public class Queue extends ResourceController implements Saveable {
maintain();
// allocate buildable jobs to executors
Iterator<BuildableItem> itr = buildables.values().iterator();
Iterator<BuildableItem> itr = buildables.iterator();
while (itr.hasNext()) {
BuildableItem p = itr.next();
......@@ -522,7 +590,7 @@ public class Queue extends ResourceController implements Saveable {
if (offer.item != null) {
LOGGER.fine("Pop returning " + offer.item + " for " + exec.getName());
// if so, just build it
return offer.item.task;
return offer.item;
}
// otherwise run a queue maintenance
}
......@@ -784,10 +852,6 @@ public class Queue extends ResourceController implements Saveable {
*/
String getUrl();
/**
* Called from queue.jelly.
*/
void doCancelQueue( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException;
}
public interface Executable extends Runnable {
......@@ -807,13 +871,16 @@ public class Queue extends ResourceController implements Saveable {
* Item in a queue.
*/
@ExportedBean(defaultVisibility = 999)
public abstract class Item {
/**
public static abstract class Item extends Actionable {
public final int id;
/**
* Project to be built.
*/
@Exported
public final Task task;
/**
* Build is blocked because another build is in progress,
* required {@link Resource}s are not available, or otherwise blocked
......@@ -836,12 +903,18 @@ public class Queue extends ResourceController implements Saveable {
@Exported
public boolean isStuck() { return false; }
protected Item(Task project) {
this.task = project;
protected Item(Task task, List<Action> actions, int id) {
this.task = task;
this.id = id;
for (Action action: actions) addAction(action);
}
protected Item(Item item) {
this(item.task, item.getActions(), item.id);
}
/**
* Gets a human-readable status message describing why it's in the queu.
* Gets a human-readable status message describing why it's in the queue.
*/
@Exported
public abstract String getWhy();
......@@ -849,32 +922,55 @@ public class Queue extends ResourceController implements Saveable {
public boolean hasCancelPermission() {
return task.hasAbortPermission();
}
public String getDisplayName() {
// TODO Auto-generated method stub
return null;
}
public String getSearchUrl() {
// TODO Auto-generated method stub
return null;
}
/**
* Called from queue.jelly.
*/
public void doCancelQueue( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
Hudson.getInstance().getQueue().cancel(this);
rsp.forwardToPreviousPage(req);
}
}
/**
* An optional interface for actions on Queue.Item.
* Lets the action cooperate in queue management.
*/
public interface QueueAction extends Action {
/**
* Returns whether the new item should be scheduled.
* An action should return true if the associated task is 'different enough' to warrant a separate execution.
*/
public boolean shouldSchedule(List<Action> actions);
}
/**
* {@link Item} in the {@link Queue#waitingList} stage.
*/
public final class WaitingItem extends Item implements Comparable<WaitingItem> {
public static final class WaitingItem extends Item implements Comparable<WaitingItem> {
private static final AtomicInteger COUNTER = new AtomicInteger(0);
/**
* This item can be run after this time.
*/
@Exported
public Calendar timestamp;
/**
* 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;
WaitingItem(Calendar timestamp, Task project) {
super(project);
WaitingItem(Calendar timestamp, Task project, List<Action> actions) {
super(project, actions, COUNTER.incrementAndGet());
this.timestamp = timestamp;
synchronized (Queue.this) {
this.id = iota++;
}
}
public int compareTo(WaitingItem that) {
int r = this.timestamp.getTime().compareTo(that.timestamp.getTime());
if (r != 0) return r;
......@@ -895,7 +991,7 @@ public class Queue extends ResourceController implements Saveable {
/**
* Common part between {@link BlockedItem} and {@link BuildableItem}.
*/
public abstract class NotWaitingItem extends Item {
public static abstract class NotWaitingItem extends Item {
/**
* When did this job exit the {@link Queue#waitingList} phase?
*/
......@@ -903,12 +999,12 @@ public class Queue extends ResourceController implements Saveable {
public final long buildableStartMilliseconds;
protected NotWaitingItem(WaitingItem wi) {
super(wi.task);
super(wi);
buildableStartMilliseconds = System.currentTimeMillis();
}
protected NotWaitingItem(NotWaitingItem ni) {
super(ni.task);
super(ni);
buildableStartMilliseconds = ni.buildableStartMilliseconds;
}
}
......@@ -940,7 +1036,7 @@ public class Queue extends ResourceController implements Saveable {
/**
* {@link Item} in the {@link Queue#buildables} stage.
*/
public final class BuildableItem extends NotWaitingItem {
public final static class BuildableItem extends NotWaitingItem {
public BuildableItem(WaitingItem wi) {
super(wi);
}
......@@ -1075,4 +1171,54 @@ public class Queue extends ResourceController implements Saveable {
cancel();
}
}
/**
* A MultiMap - LinkedMap crossover as a drop-in replacement for the previously used LinkedHashMap
* And no, I don't care about performance ;)
*/
private static class ItemList<T extends Item> extends ArrayList<T> {
public T get(Task task) {
for (T item: this) {
if (item.task == task) {
return item;
}
}
return null;
}
public List<T> getAll(Task task) {
List<T> result = new ArrayList<T>();
for (T item: this) {
if (item.task == task) {
result.add(item);
}
}
return result;
}
public boolean containsKey(Task task) {
return get(task) != null;
}
public T remove(Task task) {
Iterator<T> it = iterator();
while (it.hasNext()) {
T t = it.next();
if (t.task == task) {
it.remove();
return t;
}
}
return null;
}
public void put(Task task, T item) {
assert item.task == task;
add(item);
}
public ItemList<T> values() {
return this;
}
}
}
......@@ -60,7 +60,33 @@ public class StringParameterValue extends ParameterValue {
};
}
@Override
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + ((value == null) ? 0 : value.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
return false;
if (getClass() != obj.getClass())
return false;
StringParameterValue other = (StringParameterValue) obj;
if (value == null) {
if (other.value != null)
return false;
} else if (!value.equals(other.value))
return false;
return true;
}
@Override
public String toString() {
return "(StringParameterValue) " + getName() + "='" + value + "'";
}
......
/*
* The MIT License
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.util;
import hudson.model.Queue;
import hudson.model.Label;
import hudson.model.Node;
import hudson.model.ResourceList;
import java.io.IOException;
/**
* Convenient base class for {@link Queue.Task} decorators.
*
* @author Kohsuke Kawaguchi
*/
public abstract class QueueTaskFilter implements Queue.Task {
/**
* Delegation target.
*/
protected volatile Queue.Task delegate;
protected QueueTaskFilter(Queue.Task delegate) {
this.delegate = delegate;
}
protected QueueTaskFilter() {
}
public Label getAssignedLabel() {
return delegate.getAssignedLabel();
}
public Node getLastBuiltOn() {
return delegate.getLastBuiltOn();
}
public boolean isBuildBlocked() {
return delegate.isBuildBlocked();
}
public String getWhyBlocked() {
return delegate.getWhyBlocked();
}
public String getName() {
return delegate.getName();
}
public String getFullDisplayName() {
return delegate.getFullDisplayName();
}
public long getEstimatedDuration() {
return delegate.getEstimatedDuration();
}
public Queue.Executable createExecutable() throws IOException {
return delegate.createExecutable();
}
public void checkAbortPermission() {
delegate.checkAbortPermission();
}
public boolean hasAbortPermission() {
return delegate.hasAbortPermission();
}
public String getDisplayName() {
return delegate.getDisplayName();
}
public ResourceList getResourceList() {
return delegate.getResourceList();
}
// Queue.Task hashCode and equals need to be implemented to provide value equality semantics
public abstract int hashCode();
public abstract boolean equals(Object obj);
}
......@@ -27,6 +27,10 @@ import hudson.model.Hudson;
import hudson.model.Queue.Item;
import hudson.model.Queue.Task;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Displays the build history on the side panel.
*
......@@ -46,9 +50,18 @@ public class BuildHistoryWidget<T> extends HistoryWidget<Task,T> {
}
/**
* Returns the queue item if the owner is scheduled for execution in the queue.
* Returns the first queue item if the owner is scheduled for execution in the queue.
*/
public Item getQueuedItem() {
return Hudson.getInstance().getQueue().getItem(owner);
}
/**
* Returns the queue item if the owner is scheduled for execution in the queue, in REVERSE ORDER
*/
public List<Item> getQueuedItems() {
List<Item> list = new ArrayList<Item>(Hudson.getInstance().getQueue().getItems(owner));
Collections.reverse(list);
return list;
}
}
......@@ -27,23 +27,25 @@ THE SOFTWARE.
-->
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:i="jelly:fmt">
<!-- pending build -->
<j:set var="pending" value="${it.queuedItem}" />
<j:if test="${pending!=null}">
<j:set var="queuedItems" value="${it.queuedItems}" />
<j:if test="${!pending.isEmpty()}">
<j:forEach var="item" items="${queuedItems}" indexVar="i">
<j:set var="id" value="${h.generateId()}"/>
<tr class="build-row transitive" id="${id}">
<td nowrap="nowrap">
<img width="16" height="16" src="${imagesURL}/16x16/grey.gif" alt="${%pending}" /><st:nbsp/>
#${it.owner.nextBuildNumber}
#${it.owner.nextBuildNumber+queuedItems.size()-i-1}
</td>
<td nowrap="nowrap" tooltip="${pending.why}">
<td nowrap="nowrap" tooltip="${item.task.why}">
<div style="float:right">
<j:if test="${pending.hasCancelPermission()}">
<a href="${rootURL}/${pending.task.url}cancelQueue"><img src="${imagesURL}/16x16/stop.gif" alt="${%cancel this build}" /></a>
<j:if test="${item.hasCancelPermission()}">
<a href="${rootURL}/queue/item/${item.id}/cancelQueue"><img src="${imagesURL}/16x16/stop.gif" alt="cancel this build" /></a>
</j:if>
</div>
(${%pending})
</td>
</tr>
</j:forEach>
</j:if>
<st:include page="/hudson/widgets/HistoryWidget/entries.jelly" />
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册