提交 c4e2e597 编写于 作者: A Andrew Kiellor

Merge branch 'master' of github.com:jenkinsci/jenkins into JENKINS-18434

......@@ -54,6 +54,14 @@ Upcoming changes</a>
<!-- Record your changes in the trunk here. -->
<div id="trunk" style="display:none"><!--=TRUNK-BEGIN=-->
<ul class=image>
<li class=>
</ul>
</div><!--=TRUNK-END=-->
<!-- these changes are controlled by the release process. DO NOT MODIFY -->
<div id="rc" style="display:none;"><!--=BEGIN=-->
<h3><a name=v1.535>What's new in 1.535</a> <!--=DATE=--></h3>
<ul class=image>
<li class=bug>
Windows JDK installer failed in a path with spaces.
......@@ -68,6 +76,9 @@ Upcoming changes</a>
Split matrix authorization strategies into an independent plugin.
<li class='rfe'>
UI Samples plugin fully separated from core. To view samples during plugin development or at any other time, just install from the update center.
<li class=bug>
View description should be clearly separated from the Jenkins system message.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18633">issue 18633</a>)
<li class=bug>
SCM polling sometimes broken since 1.527 due to a change in how environment variables are calculated.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-19307">issue 19307</a>)
......@@ -86,11 +97,8 @@ Upcoming changes</a>
Visualize queued jobs in view.
(<a href="https://github.com/jenkinsci/jenkins/pull/531">pull request 531</a>)
</ul>
</div><!--=TRUNK-END=-->
<!-- these changes are controlled by the release process. DO NOT MODIFY -->
<div id="rc" style="display:none;"><!--=BEGIN=-->
<h3><a name=v1.534>What's new in 1.534</a> <!--=DATE=--></h3>
</div><!--=END=-->
<h3><a name=v1.534>What's new in 1.534</a> (2013/10/07)</h3>
<ul class=image>
<li class='major bug'>
Default crumb issuer configurations saved in older releases did not load as of Jenkins 1.531.
......@@ -108,7 +116,6 @@ Upcoming changes</a>
Added postCheckout method for SCMs
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-19740">issue 19740</a>)
</ul>
</div><!--=END=-->
<h3><a name=v1.533>What's new in 1.533</a> (2013/09/29)</h3>
<ul class=image>
<li class=rfe>
......
......@@ -5,7 +5,7 @@
<parent>
<artifactId>pom</artifactId>
<groupId>org.jenkins-ci.main</groupId>
<version>1.535-SNAPSHOT</version>
<version>1.536-SNAPSHOT</version>
</parent>
<artifactId>cli</artifactId>
......
......@@ -29,7 +29,7 @@ THE SOFTWARE.
<parent>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>pom</artifactId>
<version>1.535-SNAPSHOT</version>
<version>1.536-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
......
......@@ -86,11 +86,8 @@ public class BuildCommand extends CLICommand {
@Option(name="-v",usage="Prints out the console output of the build. Use with -s")
public boolean consoleOutput = false;
@Option(name="-r", usage="Number of times to retry reading of the output log if it does not exists on first attempt. Defaults to 0. Use with -v.")
public String retryCntStr = "0";
// hold parsed retryCnt;
private int retryCnt = 0;
@Option(name="-r") @Deprecated
public int retryCnt = 10;
protected int run() throws Exception {
job.checkPermission(Item.BUILD);
......@@ -124,8 +121,6 @@ public class BuildCommand extends CLICommand {
a = new ParametersAction(values);
}
retryCnt = Integer.parseInt(retryCntStr);
if (checkSCM) {
if (job.poll(new StreamTaskListener(stdout, getClientCharset())).change == Change.NONE) {
return 0;
......
......@@ -23,184 +23,50 @@
*/
package hudson.init;
import hudson.model.Hudson;
import jenkins.model.Jenkins;
import org.jvnet.hudson.annotation_indexer.Index;
import org.jvnet.hudson.reactor.Milestone;
import org.jvnet.hudson.reactor.Task;
import org.jvnet.hudson.reactor.TaskBuilder;
import org.jvnet.hudson.reactor.MilestoneImpl;
import org.jvnet.hudson.reactor.Reactor;
import org.jvnet.localizer.ResourceBundleHolder;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.MissingResourceException;
import java.util.Set;
import java.util.logging.Logger;
import static java.util.logging.Level.WARNING;
/**
* Discovers initialization tasks from {@link Initializer}.
*
* @author Kohsuke Kawaguchi
*/
public class InitializerFinder extends TaskBuilder {
private final ClassLoader cl;
private final Set<Method> discovered = new HashSet<Method>();
public class InitializerFinder extends TaskMethodFinder<Initializer> {
public InitializerFinder(ClassLoader cl) {
this.cl = cl;
super(Initializer.class,InitMilestone.class,cl);
}
public InitializerFinder() {
this(Thread.currentThread().getContextClassLoader());
}
public Collection<Task> discoverTasks(Reactor session) throws IOException {
List<Task> result = new ArrayList<Task>();
for (Method e : Index.list(Initializer.class,cl,Method.class)) {
if (filter(e)) continue; // already reported once
if (!Modifier.isStatic(e.getModifiers()))
throw new IOException(e+" is not a static method");
Initializer i = e.getAnnotation(Initializer.class);
if (i==null) continue; // stale index
result.add(new TaskImpl(i, e));
}
return result;
@Override
protected String displayNameOf(Initializer i) {
return i.displayName();
}
/**
* Return true to ignore this method.
*/
protected boolean filter(Method e) {
return !discovered.add(e);
@Override
protected String[] requiresOf(Initializer i) {
return i.requires();
}
/**
* Obtains the display name of the given initialization task
*/
protected String getDisplayNameOf(Method e, Initializer i) {
Class<?> c = e.getDeclaringClass();
String key = i.displayName();
if (key.length()==0) return c.getSimpleName()+"."+e.getName();
try {
ResourceBundleHolder rb = ResourceBundleHolder.get(
c.getClassLoader().loadClass(c.getPackage().getName() + ".Messages"));
return rb.format(key);
} catch (ClassNotFoundException x) {
LOGGER.log(WARNING, "Failed to load "+x.getMessage()+" for "+e.toString(),x);
return key;
} catch (MissingResourceException x) {
LOGGER.log(WARNING, "Could not find key '" + key + "' in " + c.getPackage().getName() + ".Messages", x);
return key;
}
@Override
protected String[] attainsOf(Initializer i) {
return i.attains();
}
/**
* Invokes the given initialization method.
*/
protected void invoke(Method e) {
try {
Class<?>[] pt = e.getParameterTypes();
Object[] args = new Object[pt.length];
for (int i=0; i<args.length; i++)
args[i] = lookUp(pt[i]);
e.invoke(null,args);
} catch (IllegalAccessException x) {
throw (Error)new IllegalAccessError().initCause(x);
} catch (InvocationTargetException x) {
throw new Error(x);
}
@Override
protected Milestone afterOf(Initializer i) {
return i.after();
}
/**
* Determines the parameter injection of the initialization method.
*/
private Object lookUp(Class<?> type) {
if (type==Jenkins.class || type==Hudson.class)
return Jenkins.getInstance();
throw new IllegalArgumentException("Unable to inject "+type);
@Override
protected Milestone beforeOf(Initializer i) {
return i.before();
}
/**
* Task implementation.
*/
public class TaskImpl implements Task {
final Collection<Milestone> requires;
final Collection<Milestone> attains;
private final Initializer i;
private final Method e;
private TaskImpl(Initializer i, Method e) {
this.i = i;
this.e = e;
requires = toMilestones(i.requires(), i.after());
attains = toMilestones(i.attains(), i.before());
}
/**
* {@link Initializer} annotaion on the {@linkplain #getMethod() method}
*/
public Initializer getAnnotation() {
return i;
}
/**
* Static method that runs the initialization, that this task wraps.
*/
public Method getMethod() {
return e;
}
public Collection<Milestone> requires() {
return requires;
}
public Collection<Milestone> attains() {
return attains;
}
public String getDisplayName() {
return getDisplayNameOf(e, i);
}
public boolean failureIsFatal() {
return i.fatal();
}
public void run(Reactor session) {
invoke(e);
}
public String toString() {
return e.toString();
}
private Collection<Milestone> toMilestones(String[] tokens, InitMilestone m) {
List<Milestone> r = new ArrayList<Milestone>();
for (String s : tokens) {
try {
r.add(InitMilestone.valueOf(s));
} catch (IllegalArgumentException x) {
r.add(new MilestoneImpl(s));
}
}
r.add(m);
return r;
}
@Override
protected boolean fatalOf(Initializer i) {
return i.fatal();
}
private static final Logger LOGGER = Logger.getLogger(InitializerFinder.class.getName());
}
package hudson.init;
import hudson.model.Hudson;
import jenkins.model.Jenkins;
import org.jvnet.hudson.annotation_indexer.Index;
import org.jvnet.hudson.reactor.Milestone;
import org.jvnet.hudson.reactor.MilestoneImpl;
import org.jvnet.hudson.reactor.Reactor;
import org.jvnet.hudson.reactor.Task;
import org.jvnet.hudson.reactor.TaskBuilder;
import org.jvnet.localizer.ResourceBundleHolder;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.MissingResourceException;
import java.util.Set;
import java.util.logging.Logger;
import static java.util.logging.Level.WARNING;
/**
* @author Kohsuke Kawaguchi
*/
abstract class TaskMethodFinder<T extends Annotation> extends TaskBuilder {
private static final Logger LOGGER = Logger.getLogger(TaskMethodFinder.class.getName());
protected final ClassLoader cl;
private final Set<Method> discovered = new HashSet<Method>();
private final Class<T> type;
private final Class<? extends Enum> milestoneType;
public TaskMethodFinder(Class<T> type, Class<? extends Enum> milestoneType, ClassLoader cl) {
this.type = type;
this.milestoneType = milestoneType;
this.cl = cl;
}
// working around the restriction that Java doesn't allow annotation types to extend interfaces
protected abstract String displayNameOf(T i);
protected abstract String[] requiresOf(T i);
protected abstract String[] attainsOf(T i);
protected abstract Milestone afterOf(T i);
protected abstract Milestone beforeOf(T i);
protected abstract boolean fatalOf(T i);
public Collection<Task> discoverTasks(Reactor session) throws IOException {
List<Task> result = new ArrayList<Task>();
for (Method e : Index.list(type, cl, Method.class)) {
if (filter(e)) continue; // already reported once
if (!Modifier.isStatic(e.getModifiers()))
throw new IOException(e+" is not a static method");
T i = e.getAnnotation(type);
if (i==null) continue; // stale index
result.add(new TaskImpl(i, e));
}
return result;
}
/**
* Return true to ignore this method.
*/
protected boolean filter(Method e) {
return !discovered.add(e);
}
/**
* Obtains the display name of the given initialization task
*/
protected String getDisplayNameOf(Method e, T i) {
Class<?> c = e.getDeclaringClass();
String key = displayNameOf(i);
if (key.length()==0) return c.getSimpleName()+"."+e.getName();
try {
ResourceBundleHolder rb = ResourceBundleHolder.get(
c.getClassLoader().loadClass(c.getPackage().getName() + ".Messages"));
return rb.format(key);
} catch (ClassNotFoundException x) {
LOGGER.log(WARNING, "Failed to load "+x.getMessage()+" for "+e.toString(),x);
return key;
} catch (MissingResourceException x) {
LOGGER.log(WARNING, "Could not find key '" + key + "' in " + c.getPackage().getName() + ".Messages", x);
return key;
}
}
/**
* Invokes the given initialization method.
*/
protected void invoke(Method e) {
try {
Class<?>[] pt = e.getParameterTypes();
Object[] args = new Object[pt.length];
for (int i=0; i<args.length; i++)
args[i] = lookUp(pt[i]);
e.invoke(null,args);
} catch (IllegalAccessException x) {
throw (Error)new IllegalAccessError().initCause(x);
} catch (InvocationTargetException x) {
throw new Error(x);
}
}
/**
* Determines the parameter injection of the initialization method.
*/
private Object lookUp(Class<?> type) {
if (type==Jenkins.class || type==Hudson.class)
return Jenkins.getInstance();
throw new IllegalArgumentException("Unable to inject "+type);
}
/**
* Task implementation.
*/
public class TaskImpl implements Task {
final Collection<Milestone> requires;
final Collection<Milestone> attains;
private final T i;
private final Method e;
private TaskImpl(T i, Method e) {
this.i = i;
this.e = e;
requires = toMilestones(requiresOf(i), afterOf(i));
attains = toMilestones(attainsOf(i), beforeOf(i));
}
/**
* The annotation on the {@linkplain #getMethod() method}
*/
public T getAnnotation() {
return i;
}
/**
* Static method that runs the initialization, that this task wraps.
*/
public Method getMethod() {
return e;
}
public Collection<Milestone> requires() {
return requires;
}
public Collection<Milestone> attains() {
return attains;
}
public String getDisplayName() {
return getDisplayNameOf(e, i);
}
public boolean failureIsFatal() {
return fatalOf(i);
}
public void run(Reactor session) {
invoke(e);
}
public String toString() {
return e.toString();
}
private Collection<Milestone> toMilestones(String[] tokens, Milestone m) {
List<Milestone> r = new ArrayList<Milestone>();
for (String s : tokens) {
try {
r.add((Milestone)Enum.valueOf(milestoneType,s));
} catch (IllegalArgumentException x) {
r.add(new MilestoneImpl(s));
}
}
r.add(m);
return r;
}
}
}
package hudson.init;
import org.jvnet.hudson.reactor.Executable;
import org.jvnet.hudson.reactor.Milestone;
import org.jvnet.hudson.reactor.TaskBuilder;
import org.jvnet.hudson.reactor.TaskGraphBuilder;
/**
* Various key milestone in the termination process of Jenkins.
*
* <p>
* Plugins can use these milestones to execute their tear down processing at the right moment
* (in addition to defining their own milestones by implementing {@link Milestone}.
*
* <p>
* These milestones are achieve in this order.
*
* @author Kohsuke Kawaguchi
*/
public enum TermMilestone implements Milestone {
/**
* The very first milestone that gets achieved without doing anything.
*
* This is used in {@link Initializer#after()} since annotations cannot have null as the default value.
*/
STARTED("Started termination"),
/**
* The very last milestone
*
* This is used in {@link Initializer#before()} since annotations cannot have null as the default value.
*/
COMPLETED("Completed termination");
private final String message;
TermMilestone(String message) {
this.message = message;
}
/**
* Creates a set of dummy tasks to enforce ordering among {@link TermMilestone}s.
*/
public static TaskBuilder ordering() {
TaskGraphBuilder b = new TaskGraphBuilder();
TermMilestone[] v = values();
for (int i=0; i<v.length-1; i++)
b.add(null, Executable.NOOP).requires(v[i]).attains(v[i+1]);
return b;
}
@Override
public String toString() {
return message;
}
}
package hudson.init;
import org.jvnet.hudson.annotation_indexer.Indexed;
import org.jvnet.hudson.reactor.Task;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static hudson.init.TermMilestone.*;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* @author Kohsuke Kawaguchi
*/
@Indexed
@Documented
@Retention(RUNTIME)
@Target(METHOD)
public @interface Terminator {
/**
* Indicates that the specified milestone is necessary before executing this terminator.
*
* <p>
* This has the identical purpose as {@link #requires()}, but it's separated to allow better type-safety
* when using {@link TermMilestone} as a requirement (since enum member definitions need to be constant.)
*/
TermMilestone after() default STARTED;
/**
* Indicates that this terminator is a necessary step before achieving the specified milestone.
*
* <p>
* This has the identical purpose as {@link #attains()}. See {@link #after()} for why there are two things
* to achieve the same goal.
*/
TermMilestone before() default COMPLETED;
/**
* Indicates the milestones necessary before executing this terminator.
*/
String[] requires() default {};
/**
* Indicates the milestones that this terminator contributes to.
*
* A milestone is considered attained if all the terminators that attains the given milestone
* completes. So it works as a kind of join.
*/
String[] attains() default {};
/**
* Key in <tt>Messages.properties</tt> that represents what this task is about. Used for rendering the progress.
* Defaults to "${short class name}.${method Name}".
*/
String displayName() default "";
}
package hudson.init;
import org.jvnet.hudson.reactor.Milestone;
/**
* @author Kohsuke Kawaguchi
*/
public class TerminatorFinder extends TaskMethodFinder<Terminator> {
public TerminatorFinder(ClassLoader cl) {
super(Terminator.class, TermMilestone.class, cl);
}
@Override
protected String displayNameOf(Terminator i) {
return i.displayName();
}
@Override
protected String[] requiresOf(Terminator i) {
return i.requires();
}
@Override
protected String[] attainsOf(Terminator i) {
return i.attains();
}
@Override
protected Milestone afterOf(Terminator i) {
return i.after();
}
@Override
protected Milestone beforeOf(Terminator i) {
return i.before();
}
/**
* Termination code is never fatal.
*/
@Override
protected boolean fatalOf(Terminator i) {
return false;
}
}
......@@ -23,7 +23,6 @@
*/
package hudson.model;
import jenkins.model.Jenkins;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;
......@@ -53,11 +52,6 @@ public class AllView extends View {
this.owner = owner;
}
@Override
public String getDescription() {
return Jenkins.getInstance().getDescription();
}
@Override
public boolean isEditable() {
return false;
......@@ -82,14 +76,6 @@ public class AllView extends View {
return (Collection)getOwnerItemGroup().getItems();
}
@Override
public synchronized void doSubmitDescription( StaplerRequest req, StaplerResponse rsp ) throws IOException, ServletException {
checkPermission(Jenkins.ADMINISTER);
Jenkins.getInstance().setSystemMessage(req.getParameter("description"));
rsp.sendRedirect(".");
}
@Override
public String getPostConstructLandingPage() {
return ""; // there's no configuration page
......
......@@ -718,7 +718,6 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
for (Integer number : availableNumbers) {
Executor e = new Executor(this, number);
e.start();
executors.add(e);
}
}
......@@ -847,7 +846,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
*/
protected boolean isAlive() {
for (Executor e : executors)
if (e.isAlive())
if (e.isActive())
return true;
return false;
}
......@@ -1015,8 +1014,8 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
* Starts executing a fly-weight task.
*/
/*package*/ final void startFlyWeightTask(WorkUnit p) {
OneOffExecutor e = new OneOffExecutor(this, p);
e.start();
OneOffExecutor e = new OneOffExecutor(this);
e.start(p);
oneOffExecutors.add(e);
}
......
......@@ -23,43 +23,41 @@
*/
package hudson.model;
import hudson.model.Queue.Executable;
import hudson.Util;
import hudson.FilePath;
import jenkins.model.CauseOfInterruption;
import jenkins.model.CauseOfInterruption.UserInterruption;
import hudson.Util;
import hudson.model.Queue.Executable;
import hudson.model.queue.Executables;
import hudson.model.queue.SubTask;
import hudson.model.queue.Tasks;
import hudson.model.queue.WorkUnit;
import hudson.util.TimeUnit2;
import hudson.util.InterceptingProxy;
import hudson.security.ACL;
import hudson.util.InterceptingProxy;
import hudson.util.TimeUnit2;
import jenkins.model.CauseOfInterruption;
import jenkins.model.CauseOfInterruption.UserInterruption;
import jenkins.model.InterruptedBuildAction;
import jenkins.model.Jenkins;
import org.acegisecurity.Authentication;
import org.acegisecurity.context.SecurityContext;
import org.acegisecurity.context.SecurityContextHolder;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpResponses;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.ExportedBean;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
import org.kohsuke.stapler.interceptor.RequirePOST;
import javax.servlet.ServletException;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Vector;
import java.util.logging.Logger;
import java.util.logging.Level;
import java.lang.reflect.Method;
import java.util.logging.Logger;
import static hudson.model.queue.Executables.*;
import static java.util.logging.Level.FINE;
import org.kohsuke.stapler.interceptor.RequirePOST;
import static java.util.logging.Level.*;
/**
......@@ -76,7 +74,7 @@ public class Executor extends Thread implements ModelObject {
/**
* Used to track when a job was last executed.
*/
private long finishTime;
private final long creationTime = System.currentTimeMillis();
/**
* Executor number that identifies it among other executors for the same {@link Computer}.
......@@ -87,12 +85,18 @@ public class Executor extends Thread implements ModelObject {
*/
private volatile Queue.Executable executable;
/**
* When {@link Queue} allocates a work for this executor, this field is set
* and the executor is {@linkplain Thread#start() started}.
*/
private volatile WorkUnit workUnit;
private Throwable causeOfDeath;
private boolean induceDeath;
private volatile boolean started;
/**
* When the executor is interrupted, we allow the code that interrupted the thread to override the
* result code it prefers.
......@@ -140,6 +144,14 @@ public class Executor extends Thread implements ModelObject {
if (LOGGER.isLoggable(FINE))
LOGGER.log(FINE, String.format("%s is interrupted(%s): %s", getDisplayName(), result, Util.join(Arrays.asList(causes),",")), new InterruptedException());
synchronized (this) {
if (!started) {
// not yet started, so simply dispose this
owner.removeExecutor(this);
return;
}
}
interruptStatus = result;
synchronized (this.causes) {
for (CauseOfInterruption c : causes) {
......@@ -178,103 +190,78 @@ public class Executor extends Thread implements ModelObject {
@Override
public void run() {
startTime = System.currentTimeMillis();
// run as the system user. see ACL.SYSTEM for more discussion about why this is somewhat broken
ACL.impersonate(ACL.SYSTEM);
try {
finishTime = System.currentTimeMillis();
while(shouldRun()) {
executable = null;
workUnit = null;
interruptStatus = null;
causes.clear();
synchronized(owner) {
if(owner.getNumExecutors()<owner.getExecutors().size()) {
// we've got too many executors.
owner.removeExecutor(this);
return;
}
}
if (induceDeath) throw new ThreadDeath();
SubTask task;
// transition from idle to building.
// perform this state change as an atomic operation wrt other queue operations
synchronized (queue) {
workUnit.setExecutor(this);
queue.onStartExecuting(this);
if (LOGGER.isLoggable(FINE))
LOGGER.log(FINE, getName()+" grabbed "+workUnit+" from queue");
task = workUnit.work;
executable = task.createExecutable();
workUnit.setExecutable(executable);
}
if (LOGGER.isLoggable(FINE))
LOGGER.log(FINE, getName()+" is going to execute "+executable);
// clear the interrupt flag as a precaution.
// sometime an interrupt aborts a build but without clearing the flag.
// see issue #1583
if (Thread.interrupted()) continue;
if (induceDeath) throw new ThreadDeath();
Throwable problems = null;
try {
workUnit.context.synchronizeStart();
SubTask task;
try {
// transition from idle to building.
// perform this state change as an atomic operation wrt other queue operations
synchronized (queue) {
workUnit = grabJob();
workUnit.setExecutor(this);
if (LOGGER.isLoggable(FINE))
LOGGER.log(FINE, getName()+" grabbed "+workUnit+" from queue");
task = workUnit.work;
startTime = System.currentTimeMillis();
executable = task.createExecutable();
workUnit.setExecutable(executable);
if (executable instanceof Actionable) {
for (Action action: workUnit.context.actions) {
((Actionable) executable).addAction(action);
}
if (LOGGER.isLoggable(FINE))
LOGGER.log(FINE, getName()+" is going to execute "+executable);
} catch (IOException e) {
LOGGER.log(Level.SEVERE, "Executor threw an exception", e);
continue;
} catch (InterruptedException e) {
LOGGER.log(FINE, getName()+" interrupted",e);
continue;
}
Throwable problems = null;
final String threadName = getName();
ACL.impersonate(workUnit.context.item.authenticate());
setName(getName() + " : executing " + executable.toString());
if (LOGGER.isLoggable(FINE))
LOGGER.log(FINE, getName()+" is now executing "+executable);
queue.execute(executable, task);
} catch (Throwable e) {
// for some reason the executor died. this is really
// a bug in the code, but we don't want the executor to die,
// so just leave some info and go on to build other things
LOGGER.log(Level.SEVERE, "Executor threw an exception", e);
workUnit.context.abort(e);
problems = e;
} finally {
long time = System.currentTimeMillis()-startTime;
if (LOGGER.isLoggable(FINE))
LOGGER.log(FINE, getName()+" completed "+executable+" in "+time+"ms");
try {
workUnit.context.synchronizeStart();
if (executable instanceof Actionable) {
for (Action action: workUnit.context.actions) {
((Actionable) executable).addAction(action);
}
}
final SecurityContext savedContext = ACL.impersonate(workUnit.context.item.authenticate());
try {
setName(threadName + " : executing " + executable.toString());
if (LOGGER.isLoggable(FINE))
LOGGER.log(FINE, getName()+" is now executing "+executable);
queue.execute(executable, task);
} finally {
SecurityContextHolder.setContext(savedContext);
}
} catch (Throwable e) {
// for some reason the executor died. this is really
// a bug in the code, but we don't want the executor to die,
// so just leave some info and go on to build other things
LOGGER.log(Level.SEVERE, "Executor threw an exception", e);
workUnit.context.synchronizeEnd(executable,problems,time);
} catch (InterruptedException e) {
workUnit.context.abort(e);
problems = e;
} finally {
setName(threadName);
finishTime = System.currentTimeMillis();
if (LOGGER.isLoggable(FINE))
LOGGER.log(FINE, getName()+" completed "+executable+" in "+(finishTime-startTime)+"ms");
try {
workUnit.context.synchronizeEnd(executable,problems,finishTime - startTime);
} catch (InterruptedException e) {
workUnit.context.abort(e);
continue;
} finally {
workUnit.setExecutor(null);
}
workUnit.setExecutor(null);
}
}
} catch(RuntimeException e) {
} catch (InterruptedException e) {
LOGGER.log(FINE, getName()+" interrupted",e);
// die peacefully
} catch(Exception e) {
causeOfDeath = e;
throw e;
LOGGER.log(SEVERE, "Unexpected executor death", e);
} catch (Error e) {
causeOfDeath = e;
throw e;
LOGGER.log(SEVERE, "Unexpected executor death", e);
} finally {
if (causeOfDeath==null)
// let this thread die and be replaced by a fresh unstarted instance
owner.removeExecutor(this);
queue.scheduleMaintenance();
}
}
......@@ -283,18 +270,6 @@ public class Executor extends Thread implements ModelObject {
*/
public void killHard() {
induceDeath = true;
interrupt();
}
/**
* Returns true if we should keep going.
*/
protected boolean shouldRun() {
return Jenkins.getInstance() != null && !Jenkins.getInstance().isTerminating();
}
protected WorkUnit grabJob() throws InterruptedException {
return queue.pop();
}
/**
......@@ -369,6 +344,17 @@ public class Executor extends Thread implements ModelObject {
return executable!=null;
}
public boolean isActive() {
return !started || isAlive();
}
/**
* Returns true if this executor is waiting for a task to execute.
*/
public boolean isParking() {
return !started;
}
/**
* If this thread dies unexpectedly, obtain the cause of the failure.
*
......@@ -477,6 +463,23 @@ public class Executor extends Thread implements ModelObject {
return eta;
}
/**
* Can't start executor like you normally start a thread.
*
* @see #start(WorkUnit)
*/
@Override
public synchronized void start() {
throw new UnsupportedOperationException();
}
/*protected*/ synchronized void start(WorkUnit task) {
this.workUnit = task;
super.start();
started = true;
}
/**
* @deprecated as of 1.489
* Use {@link #doStop()}.
......@@ -528,7 +531,7 @@ public class Executor extends Thread implements ModelObject {
*/
public long getIdleStartMilliseconds() {
if (isIdle())
return Math.max(finishTime, owner.getConnectTime());
return Math.max(creationTime, owner.getConnectTime());
else {
return Math.max(startTime + Math.max(0, Executables.getEstimatedDurationFor(executable)),
System.currentTimeMillis() + 15000);
......@@ -588,5 +591,4 @@ public class Executor extends Thread implements ModelObject {
private static final ThreadLocal<Executor> IMPERSONATION = new ThreadLocal<Executor>();
private static final Logger LOGGER = Logger.getLogger(Executor.class.getName());
}
......@@ -24,7 +24,6 @@
package hudson.model;
import hudson.model.Queue.FlyweightTask;
import hudson.model.queue.WorkUnit;
/**
* {@link Executor} that's temporarily added to carry out tasks that doesn't consume
......@@ -34,30 +33,8 @@ import hudson.model.queue.WorkUnit;
* @see FlyweightTask
*/
public class OneOffExecutor extends Executor {
private WorkUnit work;
public OneOffExecutor(Computer owner, WorkUnit work) {
public OneOffExecutor(Computer owner) {
super(owner,-1);
this.work = work;
}
@Override
protected boolean shouldRun() {
// TODO: consulting super.shouldRun() here means we'll lose the work if it gets scheduled
// when super.shouldRun() returns false.
return super.shouldRun() && work !=null;
}
public WorkUnit getAssignedWorkUnit() {
return work;
}
@Override
protected WorkUnit grabJob() throws InterruptedException {
WorkUnit r = super.grabJob();
assert r==work;
work = null;
return r;
}
@Override
......
......@@ -28,7 +28,6 @@ import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.ImmutableList;
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
import hudson.AbortException;
import hudson.BulkChange;
import hudson.CopyOnWrite;
import hudson.ExtensionList;
......@@ -68,7 +67,6 @@ import hudson.model.queue.WorkUnitContext;
import hudson.security.ACL;
import hudson.triggers.SafeTimerTask;
import hudson.triggers.Trigger;
import hudson.util.OneShotEvent;
import hudson.util.TimeUnit2;
import hudson.util.XStream2;
import hudson.util.ConsistentHash;
......@@ -95,7 +93,7 @@ import java.util.NoSuchElementException;
import java.util.Set;
import java.util.Timer;
import java.util.TreeSet;
import java.util.Map.Entry;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.Future;
......@@ -109,6 +107,7 @@ import javax.servlet.ServletException;
import jenkins.model.Jenkins;
import jenkins.security.QueueItemAuthenticator;
import jenkins.security.QueueItemAuthenticatorConfiguration;
import jenkins.util.AtmostOneTaskExecutor;
import org.acegisecurity.AccessDeniedException;
import org.acegisecurity.Authentication;
import org.kohsuke.stapler.HttpResponse;
......@@ -227,22 +226,15 @@ public class Queue extends ResourceController implements Saveable {
* This is a job offer from the queue to an executor.
*
* <p>
* An idle executor (that calls {@link Queue#pop()} creates
* a new {@link JobOffer} and gets itself {@linkplain Queue#parked parked},
* and we'll eventually hand out an {@link #workUnit} to build.
* For each idle executor, this gets created to allow the scheduling logic
* to assign a work. Once a work is assigned, the executor actually gets
* started to carry out the task in question.
*/
public class JobOffer extends MappingWorksheet.ExecutorSlot {
public final Executor executor;
/**
* Used to wake up an executor, when it has an offered
* {@link Project} to build.
*/
private final OneShotEvent event = new OneShotEvent(Queue.this);
/**
* The work unit that this {@link Executor} is going to handle.
* (Or null, in which case event is used to trigger a queue maintenance.)
*/
private WorkUnit workUnit;
......@@ -254,7 +246,9 @@ public class Queue extends ResourceController implements Saveable {
protected void set(WorkUnit p) {
assert this.workUnit == null;
this.workUnit = p;
event.signal();
assert executor.isParking();
executor.start(workUnit);
// LOGGER.info("Starting "+executor.getName());
}
@Override
......@@ -300,20 +294,23 @@ public class Queue extends ResourceController implements Saveable {
}
}
/**
* The executors that are currently waiting for a job to run.
*/
private final Map<Executor,JobOffer> parked = new HashMap<Executor,JobOffer>();
private volatile transient LoadBalancer loadBalancer;
private volatile transient QueueSorter sorter;
private transient final AtmostOneTaskExecutor<Void> maintainerThread = new AtmostOneTaskExecutor<Void>(new Callable<Void>() {
@Override
public Void call() throws Exception {
maintain();
return null;
}
});
public Queue(LoadBalancer loadBalancer) {
this.loadBalancer = loadBalancer.sanitize();
// 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);
new MaintainTask(this).periodic();
}
public LoadBalancer getLoadBalancer() {
......@@ -903,94 +900,16 @@ public class Queue extends ResourceController implements Saveable {
}
/**
* Called by the executor to fetch something to build next.
* <p>
* This method blocks until a next project becomes buildable.
* Called when the executor actually starts executing the assigned work unit.
*
* This moves the task from the pending state to the "left the queue" state.
*/
public synchronized WorkUnit pop() throws InterruptedException {
final Executor exec = Executor.currentExecutor();
if (exec instanceof OneOffExecutor) {
OneOffExecutor ooe = (OneOffExecutor) exec;
final WorkUnit wu = ooe.getAssignedWorkUnit();
pendings.remove(wu.context.item);
LeftItem li = new LeftItem(wu.context);
li.enter(this);
return wu;
}
try {
while (true) {
final JobOffer offer = new JobOffer(exec);
long sleep = -1;
// 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();
// 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.
if (!waitingList.isEmpty()) {
// wait until the first item in the queue is due
sleep = peek().timestamp.getTimeInMillis() - new GregorianCalendar().getTimeInMillis();
if (sleep < 100) sleep = 100; // avoid wait(0)
}
if (sleep == -1)
offer.event.block();
else
offer.event.block(sleep);
// retract the offer object
assert parked.get(exec) == offer;
parked.remove(exec);
// am I woken up because I have a project to build?
if (offer.workUnit != null) {
// if so, just build it
LOGGER.log(Level.FINE, "Pop returning {0} for {1}", new Object[] {offer.workUnit, exec.getName()});
// TODO: I think this has to be done by the last executor that leaves the pop(), not by main executor
if (offer.workUnit.isMainWork()) {
pendings.remove(offer.workUnit.context.item);
LeftItem li = new LeftItem(offer.workUnit.context);
li.enter(this);
}
return offer.workUnit;
}
// otherwise run a queue maintenance
}
} finally {
// remove myself from the parked list
JobOffer offer = parked.remove(exec);
if (offer != null && offer.workUnit != null) {
// we are already assigned a project, but now we can't handle it.
offer.workUnit.context.abort(new AbortException());
if(offer.workUnit.context.item!=null && pendings.contains(offer.workUnit.context.item)){
//we are already assigned a project and moved it into pendings, but something wrong had happened before an executor could take it.
pendings.remove(offer.workUnit.context.item);
//return it into queue, it does not have to cause this problem, it can be caused by another item.
buildables.add(offer.workUnit.context.item);
}
}
/*package*/ synchronized void onStartExecuting(Executor exec) throws InterruptedException {
final WorkUnit wu = exec.getCurrentWorkUnit();
pendings.remove(wu.context.item);
// 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();
}
LeftItem li = new LeftItem(wu.context);
li.enter(this);
}
/**
......@@ -1001,16 +920,9 @@ public class Queue extends ResourceController implements Saveable {
* <p>
* 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()) {
if (av.getValue().workUnit == null) {
av.getValue().event.signal();
return;
}
}
public void scheduleMaintenance() {
// LOGGER.info("Scheduling maintenance");
maintainerThread.submit();
}
/**
......@@ -1058,6 +970,20 @@ public class Queue extends ResourceController implements Saveable {
public synchronized void maintain() {
LOGGER.log(Level.FINE, "Queue maintenance started {0}", this);
// The executors that are currently waiting for a job to run.
Map<Executor,JobOffer> parked = new HashMap<Executor,JobOffer>();
{// update parked
for (Computer c : Jenkins.getInstance().getComputers()) {
for (Executor e : c.getExecutors()) {
if (e.isParking()) {
parked.put(e,new JobOffer(e));
}
}
}
}
{// blocked -> buildable
for (BlockedItem p : new ArrayList<BlockedItem>(blockedProjects.values())) {// copy as we'll mutate the list
if (!isBuildBlocked(p) && allowNewBuildableTask(p.task)) {
......@@ -1159,6 +1085,7 @@ public class Queue extends ResourceController implements Saveable {
}
private boolean makePending(BuildableItem p) {
// LOGGER.info("Making "+p.task+" pending"); // REMOVE
p.isPending = true;
return pendings.add(p);
}
......@@ -2070,7 +1997,9 @@ public class Queue extends ResourceController implements Saveable {
MaintainTask(Queue queue) {
this.queue = new WeakReference<Queue>(queue);
}
private void periodic() {
long interval = 5000;
Timer timer = Trigger.timer;
if (timer != null) {
......
package hudson.node_monitors;
import hudson.Extension;
import hudson.init.*;
import hudson.model.Computer;
import hudson.model.TaskListener;
import hudson.slaves.ComputerListener;
......@@ -42,4 +43,9 @@ public class NodeMonitorUpdater extends ComputerListener {
}
}, 5, TimeUnit.SECONDS);
}
@Terminator
public static void shutdownTimer() {
ComputerListener.all().get(NodeMonitorUpdater.class).timer.shutdown();
}
}
......@@ -182,7 +182,7 @@ public class NodeProvisioner {
cl.onComplete(f,node);
hudson.addNode(node);
LOGGER.info(f.displayName+" provisioning successfully completed. We have now "+hudson.getComputers().length+" computer(s)");
LOGGER.info(f.displayName+" provisioningE successfully completed. We have now "+hudson.getComputers().length+" computer(s)");
} catch (InterruptedException e) {
throw new AssertionError(e); // since we confirmed that the future is already done
} catch (ExecutionException e) {
......
......@@ -30,6 +30,7 @@ import com.google.common.collect.Lists;
import com.google.inject.Injector;
import hudson.ExtensionComponent;
import hudson.ExtensionFinder;
import hudson.init.*;
import hudson.model.LoadStatistics;
import hudson.model.Messages;
import hudson.model.Node;
......@@ -66,7 +67,6 @@ import hudson.model.ManagementLink;
import hudson.model.NoFingerprintMatch;
import hudson.model.OverallLoadStatistics;
import hudson.model.Project;
import hudson.model.Queue.BuildableItem;
import hudson.model.Queue.FlyweightTask;
import hudson.model.RestartListener;
import hudson.model.RootAction;
......@@ -120,8 +120,6 @@ import hudson.cli.CliEntryPoint;
import hudson.cli.CliManagerImpl;
import hudson.cli.declarative.CLIMethod;
import hudson.cli.declarative.CLIResolver;
import hudson.init.InitMilestone;
import hudson.init.InitStrategy;
import hudson.lifecycle.Lifecycle;
import hudson.logging.LogRecorderManager;
import hudson.lifecycle.RestartNotSupportedException;
......@@ -281,7 +279,6 @@ import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.Stack;
import java.util.StringTokenizer;
import java.util.Timer;
import java.util.TreeSet;
......@@ -289,6 +286,7 @@ import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
......@@ -2670,6 +2668,24 @@ public class Jenkins extends AbstractCIBase implements ModifiableTopLevelItemGro
for (ItemListener l : ItemListener.all())
l.onBeforeShutdown();
try {
final TerminatorFinder tf = new TerminatorFinder(
pluginManager != null ? pluginManager.uberClassLoader : Thread.currentThread().getContextClassLoader());
new Reactor(tf).execute(new Executor() {
@Override
public void execute(Runnable command) {
command.run();
}
});
} catch (InterruptedException e) {
LOGGER.log(SEVERE, "Failed to execute termination",e);
e.printStackTrace();
} catch (ReactorException e) {
LOGGER.log(SEVERE, "Failed to execute termination",e);
} catch (IOException e) {
LOGGER.log(SEVERE, "Failed to execute termination",e);
}
Set<Future<?>> pending = new HashSet<Future<?>>();
terminating = true;
for( Computer c : computers.values() ) {
......
package jenkins.util;
import hudson.remoting.AtmostOneThreadExecutor;
import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
/**
* {@link Executor}-like class that executes a single task repeatedly, in such a way that a single execution
* can cover multiple pending queued requests.
*
* <p>
* This is akin to doing laundry &mdash; when you put a dirty cloth into the laundry box, you mentally "schedule"
* a laundry task, regardless of whether there already is some cloths in the box or not. When you later actually get around
* doing laundry, you wash all the dirty cloths in the box, not just your cloths. And if someone brings
* more dirty cloths while a washer and dryer are in operation, the person has to mentally "schedule" the task
* and run the machines another time later, as the current batch is already in progress.
*
* <p>
* Since this class collapses multiple submitted tasks into just one run, it only makes sense when everyone
* submits the same task. Thus {@link #submit()} method does not take {@link Callable} as a parameter,
* instead you pass that in the constructor.
*
* @author Kohsuke Kawaguchi
* @see AtmostOneThreadExecutor
*/
public class AtmostOneTaskExecutor<V> {
/**
* The actual executor that executes {@link #task}
*/
private final ExecutorService base;
/**
* Task to be executed.
*/
private final Callable<V> task;
/**
* If a task is already submitted and pending execution, non-null.
* Guarded by "synchronized(this)"
*/
private Future<V> pending;
public AtmostOneTaskExecutor(ExecutorService base, Callable<V> task) {
this.base = base;
this.task = task;
}
public AtmostOneTaskExecutor(Callable<V> task) {
this(new AtmostOneThreadExecutor(),task);
}
public synchronized Future<V> submit() {
if (pending!=null)
// if a task is already pending, just join that
return pending;
pending = base.submit(new Callable<V>() {
@Override
public V call() throws Exception {
// before we get going, everyone who submits after this
// should form a next batch
synchronized (AtmostOneTaskExecutor.this) {
pending = null;
}
return task.call();
}
});
return pending;
}
}
......@@ -27,7 +27,7 @@ THE SOFTWARE.
-->
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:f="/lib/form" xmlns:l="/lib/layout">
<j:if test="${it.class.name=='hudson.model.Hudson'}">
<j:if test="${it.primaryView != null}">
<j:set var="it" value="${it.primaryView}"/>
</j:if>
<l:ajax>
......
......@@ -31,12 +31,10 @@ THE SOFTWARE.
<st:include page="view-index-top.jelly" it="${it.owner}" optional="true">
<!-- allow the owner to take over the top section, but we also need the default to be backward compatible -->
<div id="view-message">
<j:if test="${it.class.name!='hudson.model.AllView'}">
<div id="systemmessage">
<j:out value="${app.systemMessage!=null ? app.markupFormatter.translate(app.systemMessage) : ''}" />
</div>
</j:if>
<t:editableDescription permission="${app.ADMINISTER}"/>
<t:editableDescription permission="${it.CONFIGURE}"/>
</div>
</st:include>
......
......@@ -45,7 +45,7 @@ THE SOFTWARE.
${name}
</td>
<j:choose>
<j:when test="${!e.alive}">
<j:when test="${!e.active}">
<td class="pane">
<a href="${rootURL}/${c.url}${url}/causeOfDeath">
<div class="error">${%Dead} (!)</div>
......
jenkins (1.534) unstable; urgency=low
* See http://jenkins-ci.org/changelog for more details.
-- Kohsuke Kawaguchi <kk@kohsuke.org> Mon, 07 Oct 2013 08:18:22 -0700
jenkins (1.533) unstable; urgency=low
* See http://jenkins-ci.org/changelog for more details.
......
......@@ -11,7 +11,7 @@
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>plugin</artifactId>
<name>Jenkins plugin POM</name>
<version>1.535-SNAPSHOT</version>
<version>1.536-SNAPSHOT</version>
<packaging>pom</packaging>
<!--
......@@ -39,19 +39,19 @@
<groupId>org.jenkins-ci.main</groupId>
<artifactId>jenkins-war</artifactId>
<type>war</type>
<version>1.535-SNAPSHOT</version>
<version>1.536-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>jenkins-core</artifactId>
<version>1.535-SNAPSHOT</version>
<version>1.536-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>jenkins-test-harness</artifactId>
<version>1.535-SNAPSHOT</version>
<version>1.536-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<!--
......
......@@ -33,7 +33,7 @@ THE SOFTWARE.
<groupId>org.jenkins-ci.main</groupId>
<artifactId>pom</artifactId>
<version>1.535-SNAPSHOT</version>
<version>1.536-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Jenkins main module</name>
......
......@@ -29,7 +29,7 @@ THE SOFTWARE.
<parent>
<artifactId>pom</artifactId>
<groupId>org.jenkins-ci.main</groupId>
<version>1.535-SNAPSHOT</version>
<version>1.536-SNAPSHOT</version>
</parent>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>jenkins-test-harness</artifactId>
......
package org.jvnet.hudson.test;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Date;
import java.util.logging.Formatter;
import java.util.logging.LogRecord;
/**
* {@link Formatter} that prints out milliseconds.
*
* Useful when one needs to understand sub-second timing issues.
*
* @author Kohsuke Kawaguchi
*/
public class MilliSecLogFormatter extends Formatter {
private final Date dat = new Date();
public synchronized String format(LogRecord record) {
dat.setTime(record.getMillis());
String source;
if (record.getSourceClassName() != null) {
source = record.getSourceClassName();
if (record.getSourceMethodName() != null) {
source += " " + record.getSourceMethodName();
}
} else {
source = record.getLoggerName();
}
String message = formatMessage(record);
String throwable = "";
if (record.getThrown() != null) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
pw.println();
record.getThrown().printStackTrace(pw);
pw.close();
throwable = sw.toString();
}
return String.format("%1$tl:%1$tM:%1$tS.%1$tL %1$Tp %2$s%n%4$s: %5$s%6$s%n",
dat,
source,
record.getLoggerName(),
record.getLevel().getLocalizedName(),
message,
throwable);
}
}
......@@ -138,7 +138,7 @@ class MatrixProjectCustomWorkspaceTest extends HudsonTestCase {
List<MatrixBuild> runTwoConcurrentBuilds(MatrixProject p) {
def f1 = p.scheduleBuild2(0)
// get one going
Thread.sleep(1000)
f1.waitForStart()
def f2 = p.scheduleBuild2(0)
def bs = [f1, f2]*.get().each { assertBuildStatusSuccess(it) }
......
......@@ -394,7 +394,7 @@ public class MatrixProjectTest {
// should have gotten all unique names
def f1 = p.scheduleBuild2(0)
// get one going
Thread.sleep(1000)
f1.waitForStart()
def f2 = p.scheduleBuild2(0)
[f1,f2]*.get().each{ j.assertBuildStatusSuccess(it)}
......
......@@ -18,8 +18,10 @@ import java.util.concurrent.Future;
*/
public class ExecutorTest extends HudsonTestCase {
public void testYank() throws Exception {
Computer c = Jenkins.getInstance().toComputer();
Executor e = c.getExecutors().get(0);
jenkins.setNumExecutors(1);
jenkins.updateComputerList(true);
Computer c = jenkins.toComputer();
final Executor e = c.getExecutors().get(0);
// kill an executor
kill(e);
......@@ -35,21 +37,23 @@ public class ExecutorTest extends HudsonTestCase {
submit(p.getFormByName("yank"));
assertFalse(c.getExecutors().contains(e));
waitUntilExecutorSizeIs(c, 2);
waitUntilExecutorSizeIs(c, 1);
}
@Bug(4756)
public void testWhenAnExecuterIsYankedANewExecuterTakesItsPlace() throws Exception {
jenkins.setNumExecutors(1);
jenkins.updateComputerList(true);
Computer c = jenkins.toComputer();
Executor e = getExecutorByNumber(c, 0);
kill(e);
e.doYank();
waitUntilExecutorSizeIs(c, 2);
waitUntilExecutorSizeIs(c, 1);
assertNotNull(getExecutorByNumber(c, 0));
assertNotNull(getExecutorByNumber(c, 1));
}
private void waitUntilExecutorSizeIs(Computer c, int executorCollectionSize) throws InterruptedException {
......@@ -60,9 +64,11 @@ public class ExecutorTest extends HudsonTestCase {
}
}
private void kill(Executor e) throws InterruptedException {
private void kill(Executor e) throws InterruptedException, IOException {
e.killHard();
while (e.isAlive())
// trigger a new build which causes the forced death of the executor
createFreeStyleProject().scheduleBuild2(0);
while (e.isActive())
Thread.sleep(10);
}
......
......@@ -23,6 +23,7 @@
*/
package hudson.model;
import hudson.model.queue.QueueTaskFuture;
import hudson.security.AccessDeniedException2;
import org.acegisecurity.context.SecurityContextHolder;
import hudson.security.HudsonPrivateSecurityRealm;
......@@ -43,8 +44,6 @@ import hudson.model.queue.SubTask;
import hudson.model.AbstractProject.BecauseOfUpstreamBuildInProgress;
import hudson.model.AbstractProject.BecauseOfDownstreamBuildInProgress;
import jenkins.model.Jenkins;
import java.util.HashSet;
import java.util.Set;
import hudson.model.AbstractProject.BecauseOfBuildInProgress;
import antlr.ANTLRException;
import hudson.triggers.SCMTrigger;
......@@ -60,6 +59,7 @@ import hudson.FilePath;
import hudson.slaves.EnvironmentVariablesNodeProperty;
import hudson.EnvVars;
import hudson.tasks.Shell;
import org.jvnet.hudson.test.MilliSecLogFormatter;
import org.jvnet.hudson.test.TestExtension;
import java.util.List;
import java.util.ArrayList;
......@@ -74,7 +74,10 @@ import static org.junit.Assert.*;
import hudson.tasks.Fingerprinter;
import hudson.tasks.ArtifactArchiver;
import java.util.Map;
import hudson.Functions;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import org.junit.Ignore;
/**
......@@ -330,29 +333,51 @@ public class ProjectTest {
assertNotNull("Action should contain transient actions too.", p.getAction(TransientAction.class));
createAction = false;
}
// for debugging
// static {
// Logger.getLogger("").getHandlers()[0].setFormatter(new MilliSecLogFormatter());
// }
@Test
public void testGetCauseOfBlockage() throws IOException, InterruptedException{
public void testGetCauseOfBlockage() throws Exception {
FreeStyleProject p = j.createFreeStyleProject("project");
p.getBuildersList().add(new Shell("sleep 10"));
p.scheduleBuild2(0);
Thread.sleep(1000);//wait until it starts
assertTrue("Build can not start because previous build has not finished.", p.getCauseOfBlockage() instanceof BecauseOfBuildInProgress);
QueueTaskFuture<FreeStyleBuild> b1 = waitForStart(p);
assertInstanceOf("Build can not start because previous build has not finished: " + p.getCauseOfBlockage(), p.getCauseOfBlockage(), BecauseOfBuildInProgress.class);
p.getLastBuild().getExecutor().interrupt();
b1.get(); // wait for it to finish
FreeStyleProject downstream = j.createFreeStyleProject("project-downstream");
downstream.getBuildersList().add(new Shell("sleep 10"));
Set<AbstractProject> upstream = new HashSet<AbstractProject>(Items.fromNameList(p.getParent(),"project",AbstractProject.class));
downstream.convertUpstreamBuildTrigger(upstream);
downstream.convertUpstreamBuildTrigger(Collections.<AbstractProject>singleton(p));
Jenkins.getInstance().rebuildDependencyGraph();
p.setBlockBuildWhenDownstreamBuilding(true);
downstream.scheduleBuild2(0);
Thread.sleep(1000);//wait until it starts
assertTrue("Build can not start because build of downstream project has not finished.", p.getCauseOfBlockage() instanceof BecauseOfDownstreamBuildInProgress);
QueueTaskFuture<FreeStyleBuild> b2 = waitForStart(downstream);
assertInstanceOf("Build can not start because build of downstream project has not finished.", p.getCauseOfBlockage(), BecauseOfDownstreamBuildInProgress.class);
downstream.getLastBuild().getExecutor().interrupt();
b2.get();
downstream.setBlockBuildWhenUpstreamBuilding(true);
p.scheduleBuild2(0);
Thread.sleep(1000);//wait until it starts
assertTrue("Build can not start because build of upstream project has not finished.", downstream.getCauseOfBlockage() instanceof BecauseOfUpstreamBuildInProgress);
waitForStart(p);
assertInstanceOf("Build can not start because build of upstream project has not finished.", downstream.getCauseOfBlockage(), BecauseOfUpstreamBuildInProgress.class);
}
private static final Logger LOGGER = Logger.getLogger(ProjectTest.class.getName());
private QueueTaskFuture<FreeStyleBuild> waitForStart(FreeStyleProject p) throws InterruptedException, ExecutionException {
long start = System.nanoTime();
LOGGER.info("Scheduling "+p);
QueueTaskFuture<FreeStyleBuild> f = p.scheduleBuild2(0);
f.waitForStart();
LOGGER.info("Wait:"+ TimeUnit.NANOSECONDS.toMillis(System.nanoTime()-start));
return f;
}
private void assertInstanceOf(String msg, Object o, Class t) {
if (t.isInstance(o))
return;
fail(msg + ": " + o);
}
@Test
......
......@@ -169,7 +169,7 @@ public class QueueTest extends HudsonTestCase {
Thread.sleep(1000);
Queue.Item[] items = q.getItems();
assertEquals(1,items.length);
assertTrue(items[0] instanceof BlockedItem);
assertTrue("Got "+items[0], items[0] instanceof BlockedItem);
q.save();
}
......
......@@ -172,7 +172,7 @@ public class ArtifactArchiverTest {
int buildNumber = project.getNextBuildNumber();
project.scheduleBuild2(0);
int count = 0;
while(project.getBuildByNumber(buildNumber)==null && count<30){
while(project.getBuildByNumber(buildNumber)==null && count<50){
Thread.sleep(100);
count ++;
}
......
......@@ -22,13 +22,10 @@ public class ProcessTreeKillerTest extends HudsonTestCase {
project.getBuildersList().add(new Maven("install", "maven"));
// build the project, wait until tests are running, then cancel.
project.scheduleBuild(0);
Thread.sleep(2000);
project.scheduleBuild2(0).waitForStart();
FreeStyleBuild b = project.getLastBuild();
b.doStop(
EasyMock.createNiceMock(StaplerRequest.class),
EasyMock.createNiceMock(StaplerResponse.class));
b.doStop();
Thread.sleep(1000);
......
......@@ -28,7 +28,7 @@ THE SOFTWARE.
<parent>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>pom</artifactId>
<version>1.535-SNAPSHOT</version>
<version>1.536-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册