提交 12fd3e47 编写于 作者: J Jesse Glick

[FIXED JENKINS-27530] Jenkins.reload must also reload the Queue to ensure that...

[FIXED JENKINS-27530] Jenkins.reload must also reload the Queue to ensure that every Queue.Item.task corresponds to a live Job, lest nextBuildNumber be bogus.
上级 f9efe652
...@@ -23,8 +23,6 @@ ...@@ -23,8 +23,6 @@
*/ */
package hudson.model; package hudson.model;
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
import com.infradna.tool.bridge_method_injector.WithBridgeMethods; import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
import hudson.BulkChange; import hudson.BulkChange;
...@@ -126,6 +124,8 @@ import org.kohsuke.accmod.restrictions.NoExternalUse; ...@@ -126,6 +124,8 @@ import org.kohsuke.accmod.restrictions.NoExternalUse;
public abstract class Job<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, RunT>> public abstract class Job<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, RunT>>
extends AbstractItem implements ExtensionPoint, StaplerOverridable, ModelObjectWithChildren, OnMaster { extends AbstractItem implements ExtensionPoint, StaplerOverridable, ModelObjectWithChildren, OnMaster {
private static final Logger LOGGER = Logger.getLogger(Job.class.getName());
/** /**
* Next build number. Kept in a separate file because this is the only * Next build number. Kept in a separate file because this is the only
* information that gets updated often. This allows the rest of the * information that gets updated often. This allows the rest of the
...@@ -206,24 +206,16 @@ public abstract class Job<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R ...@@ -206,24 +206,16 @@ public abstract class Job<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R
this.nextBuildNumber = Integer.parseInt(f.readTrim()); this.nextBuildNumber = Integer.parseInt(f.readTrim());
} }
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
// try to infer the value of the next build number from the existing build records. See JENKINS-11563 LOGGER.log(Level.WARNING, "Corruption in {0}: {1}", new Object[] {f, e});
File[] folders = buildDir.listFiles(new FileFilter() { if (this instanceof LazyBuildMixIn.LazyLoadingJob) {
public boolean accept(File file) { // allow LazyBuildMixIn.onLoad to fix it
return file.isDirectory() && file.getName().matches("[0-9]+");
}
});
if (folders == null || folders.length == 0) {
this.nextBuildNumber = 1;
} else { } else {
Collection<Integer> foldersInt = Collections2.transform(Arrays.asList(folders), new Function<File, Integer>() { RunT lB = getLastBuild();
public Integer apply(File file) { synchronized (this) {
return Integer.parseInt(file.getName()); this.nextBuildNumber = lB != null ? lB.getNumber() + 1 : 1;
} }
}); saveNextBuildNumber();
this.nextBuildNumber = Collections.max(foldersInt) + 1;
} }
saveNextBuildNumber();
} }
} else { } else {
// From the old Hudson, or doCreateItem. Create this file now. // From the old Hudson, or doCreateItem. Create this file now.
...@@ -1247,7 +1239,7 @@ public abstract class Job<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R ...@@ -1247,7 +1239,7 @@ public abstract class Job<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R
FormApply.success(".").generateResponse(req, rsp, null); FormApply.success(".").generateResponse(req, rsp, null);
} }
} catch (JSONException e) { } catch (JSONException e) {
Logger.getLogger(Job.class.getName()).log(Level.WARNING, "failed to parse " + json, e); LOGGER.log(Level.WARNING, "failed to parse " + json, e);
sendError(e, req, rsp); sendError(e, req, rsp);
} }
} }
......
...@@ -342,6 +342,11 @@ public class Queue extends ResourceController implements Saveable { ...@@ -342,6 +342,11 @@ public class Queue extends ResourceController implements Saveable {
public void load() { public void load() {
lock.lock(); lock.lock();
try { try { try { try {
// Clear items, for the benefit of reloading.
waitingList.clear();
blockedProjects.clear();
buildables.clear();
pendings.clear();
// first try the old format // first try the old format
File queueFile = getQueueFile(); File queueFile = getQueueFile();
if (queueFile.exists()) { if (queueFile.exists()) {
...@@ -1375,7 +1380,7 @@ public class Queue extends ResourceController implements Saveable { ...@@ -1375,7 +1380,7 @@ public class Queue extends ResourceController implements Saveable {
lock.lock(); lock.lock();
try { try { try { try {
LOGGER.log(Level.FINE, "Queue maintenance started {0}", this); LOGGER.log(Level.FINE, "Queue maintenance started on {0} with {1}", new Object[] {this, snapshot});
// The executors that are currently waiting for a job to run. // The executors that are currently waiting for a job to run.
Map<Executor, JobOffer> parked = new HashMap<Executor, JobOffer>(); Map<Executor, JobOffer> parked = new HashMap<Executor, JobOffer>();
...@@ -2785,6 +2790,11 @@ public class Queue extends ResourceController implements Saveable { ...@@ -2785,6 +2790,11 @@ public class Queue extends ResourceController implements Saveable {
this.buildables = new ArrayList<BuildableItem>(buildables); this.buildables = new ArrayList<BuildableItem>(buildables);
this.pendings = new ArrayList<BuildableItem>(pendings); this.pendings = new ArrayList<BuildableItem>(pendings);
} }
@Override
public String toString() {
return "Queue.Snapshot{waitingList=" + waitingList + ";blockedProjects=" + blockedProjects + ";buildables=" + buildables + ";pendings=" + pendings + "}";
}
} }
private static class LockedRunnable implements Runnable { private static class LockedRunnable implements Runnable {
......
...@@ -185,7 +185,7 @@ public final class RunMap<R extends Run<?,R>> extends AbstractLazyLoadRunMap<R> ...@@ -185,7 +185,7 @@ public final class RunMap<R extends Run<?,R>> extends AbstractLazyLoadRunMap<R>
// Defense against JENKINS-23152 and its ilk. // Defense against JENKINS-23152 and its ilk.
File rootDir = r.getRootDir(); File rootDir = r.getRootDir();
if (rootDir.isDirectory()) { if (rootDir.isDirectory()) {
throw new IllegalStateException(rootDir + " already existed; will not overwrite with " + r); throw new IllegalStateException("JENKINS-23152: " + rootDir + " already existed; will not overwrite with " + r);
} }
if (!r.getClass().getName().equals("hudson.matrix.MatrixRun")) { // JENKINS-26739: grandfathered in if (!r.getClass().getName().equals("hudson.matrix.MatrixRun")) { // JENKINS-26739: grandfathered in
proposeNewNumber(r.getNumber()); proposeNewNumber(r.getNumber());
......
...@@ -44,7 +44,6 @@ import hudson.FilePath; ...@@ -44,7 +44,6 @@ import hudson.FilePath;
import hudson.Functions; import hudson.Functions;
import hudson.Launcher; import hudson.Launcher;
import hudson.Launcher.LocalLauncher; import hudson.Launcher.LocalLauncher;
import hudson.LocalPluginManager;
import hudson.Lookup; import hudson.Lookup;
import hudson.Main; import hudson.Main;
import hudson.Plugin; import hudson.Plugin;
...@@ -239,7 +238,6 @@ import org.kohsuke.accmod.Restricted; ...@@ -239,7 +238,6 @@ import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse; import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option; import org.kohsuke.args4j.Option;
import org.kohsuke.stapler.DataBoundSetter;
import org.kohsuke.stapler.HttpRedirect; import org.kohsuke.stapler.HttpRedirect;
import org.kohsuke.stapler.HttpResponse; import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpResponses; import org.kohsuke.stapler.HttpResponses;
...@@ -311,6 +309,7 @@ import java.util.logging.Logger; ...@@ -311,6 +309,7 @@ import java.util.logging.Logger;
import static hudson.Util.*; import static hudson.Util.*;
import static hudson.init.InitMilestone.*; import static hudson.init.InitMilestone.*;
import hudson.init.Initializer;
import hudson.util.LogTaskListener; import hudson.util.LogTaskListener;
import static java.util.logging.Level.*; import static java.util.logging.Level.*;
import static javax.servlet.http.HttpServletResponse.*; import static javax.servlet.http.HttpServletResponse.*;
...@@ -3714,10 +3713,13 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve ...@@ -3714,10 +3713,13 @@ public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLeve
/** /**
* Reloads the configuration synchronously. * Reloads the configuration synchronously.
* Beware that this calls neither {@link ItemListener#onLoaded} nor {@link Initializer}s.
*/ */
public void reload() throws IOException, InterruptedException, ReactorException { public void reload() throws IOException, InterruptedException, ReactorException {
queue.save();
executeReactor(null, loadTasks()); executeReactor(null, loadTasks());
User.reload(); User.reload();
queue.load();
servletContext.setAttribute("app", this); servletContext.setAttribute("app", this);
} }
......
...@@ -368,9 +368,17 @@ public abstract class AbstractLazyLoadRunMap<R> extends AbstractMap<Integer,R> i ...@@ -368,9 +368,17 @@ public abstract class AbstractLazyLoadRunMap<R> extends AbstractMap<Integer,R> i
} }
} }
/**
* @return the highest recorded build number, or 0 if there are none
*/
@Restricted(NoExternalUse.class)
public synchronized int maxNumberOnDisk() {
return numberOnDisk.max();
}
protected final synchronized void proposeNewNumber(int number) throws IllegalStateException { protected final synchronized void proposeNewNumber(int number) throws IllegalStateException {
if (numberOnDisk.isInRange(numberOnDisk.ceil(number))) { if (number <= maxNumberOnDisk()) {
throw new IllegalStateException("cannot create a build with number " + number + " since that (or higher) is already in use among " + numberOnDisk); throw new IllegalStateException("JENKINS-27530: cannot create a build with number " + number + " since that (or higher) is already in use among " + numberOnDisk);
} }
} }
......
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
package jenkins.model.lazy; package jenkins.model.lazy;
import hudson.Extension; import hudson.Extension;
import hudson.model.AbstractItem;
import hudson.model.Item; import hudson.model.Item;
import hudson.model.ItemGroup; import hudson.model.ItemGroup;
import hudson.model.Job; import hudson.model.Job;
...@@ -32,6 +33,7 @@ import hudson.model.Queue; ...@@ -32,6 +33,7 @@ import hudson.model.Queue;
import hudson.model.Run; import hudson.model.Run;
import hudson.model.RunMap; import hudson.model.RunMap;
import hudson.model.listeners.ItemListener; import hudson.model.listeners.ItemListener;
import hudson.model.queue.SubTask;
import hudson.widgets.BuildHistoryWidget; import hudson.widgets.BuildHistoryWidget;
import hudson.widgets.HistoryWidget; import hudson.widgets.HistoryWidget;
import java.io.File; import java.io.File;
...@@ -100,6 +102,12 @@ public abstract class LazyBuildMixIn<JobT extends Job<JobT,RunT> & Queue.Task & ...@@ -100,6 +102,12 @@ public abstract class LazyBuildMixIn<JobT extends Job<JobT,RunT> & Queue.Task &
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void onLoad(ItemGroup<? extends Item> parent, String name) throws IOException { public void onLoad(ItemGroup<? extends Item> parent, String name) throws IOException {
RunMap<RunT> _builds = createBuildRunMap(); RunMap<RunT> _builds = createBuildRunMap();
int max = _builds.maxNumberOnDisk();
int next = asJob().getNextBuildNumber();
if (next <= max) {
LOGGER.log(Level.WARNING, "JENKINS-27530: improper nextBuildNumber {0} detected in {1} with highest build number {2}; adjusting", new Object[] {next, asJob(), max});
asJob().updateNextBuildNumber(max + 1);
}
RunMap<RunT> currentBuilds = this.builds; RunMap<RunT> currentBuilds = this.builds;
if (parent != null) { if (parent != null) {
// are we overwriting what currently exist? // are we overwriting what currently exist?
...@@ -121,6 +129,7 @@ public abstract class LazyBuildMixIn<JobT extends Job<JobT,RunT> & Queue.Task & ...@@ -121,6 +129,7 @@ public abstract class LazyBuildMixIn<JobT extends Job<JobT,RunT> & Queue.Task &
if (r.isBuilding()) { if (r.isBuilding()) {
// Do not use RunMap.put(Run): // Do not use RunMap.put(Run):
_builds.put(r.getNumber(), r); _builds.put(r.getNumber(), r);
LOGGER.log(Level.FINE, "keeping reloaded {0}", r);
} }
} }
} }
......
...@@ -83,6 +83,10 @@ class SortedIntList extends AbstractList<Integer> { ...@@ -83,6 +83,10 @@ class SortedIntList extends AbstractList<Integer> {
return size; return size;
} }
public int max() {
return size > 0 ? data[size - 1] : 0;
}
@Override @Override
public boolean add(Integer i) { public boolean add(Integer i) {
return add(i.intValue()); return add(i.intValue());
......
...@@ -34,4 +34,15 @@ public class SortedIntListTest { ...@@ -34,4 +34,15 @@ public class SortedIntListTest {
assertFalse(l.isInRange(3)); assertFalse(l.isInRange(3));
} }
@Test public void max() {
SortedIntList l = new SortedIntList(5);
assertEquals(0, l.max());
l.add(1);
assertEquals(1, l.max());
l.add(5);
assertEquals(5, l.max());
l.add(10);
assertEquals(10, l.max());
}
} }
package hudson.model; package hudson.model;
import hudson.model.queue.QueueTaskFuture;
import javax.xml.transform.Source; import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource; import javax.xml.transform.stream.StreamSource;
import static org.junit.Assert.*; import static org.junit.Assert.*;
...@@ -12,6 +13,7 @@ import org.jvnet.hudson.test.SleepBuilder; ...@@ -12,6 +13,7 @@ import org.jvnet.hudson.test.SleepBuilder;
public class RunMapTest { public class RunMapTest {
@Rule public JenkinsRule r = new JenkinsRule(); @Rule public JenkinsRule r = new JenkinsRule();
// TODO https://github.com/jenkinsci/jenkins/pull/2438: @Rule public LoggerRule logs = new LoggerRule();
/** /**
* Makes sure that reloading the project while a build is in progress won't clobber that in-progress build. * Makes sure that reloading the project while a build is in progress won't clobber that in-progress build.
...@@ -42,6 +44,40 @@ public class RunMapTest { ...@@ -42,6 +44,40 @@ public class RunMapTest {
assertSame(b2.getPreviousBuild(), b1); assertSame(b2.getPreviousBuild(), b1);
} }
@Issue("JENKINS-27530")
@Test public void reloadWhileBuildIsInQueue() throws Exception {
//logs.record(Queue.class, Level.FINE);
FreeStyleProject p = r.createFreeStyleProject("p");
p.getBuildersList().add(new SleepBuilder(9999999));
r.jenkins.setNumExecutors(1);
assertEquals(1, p.scheduleBuild2(0).waitForStart().number);
p.scheduleBuild2(0);
// Note that the bug does not reproduce simply from p.doReload(), since in that case Job identity remains intact:
r.jenkins.reload();
p = r.jenkins.getItemByFullName("p", FreeStyleProject.class);
FreeStyleBuild b1 = p.getLastBuild();
assertEquals(1, b1.getNumber());
/* Currently fails since Run.project is final. But anyway that is not the problem:
assertEquals(p, b1.getParent());
*/
Queue.Item[] items = Queue.getInstance().getItems();
assertEquals(1, items.length);
assertEquals(p, items[0].task); // the real issue: assignBuildNumber was being called on the wrong Job
QueueTaskFuture<Queue.Executable> b2f = items[0].getFuture();
b1.getExecutor().interrupt();
r.assertBuildStatus(Result.ABORTED, r.waitForCompletion(b1));
FreeStyleBuild b2 = (FreeStyleBuild) b2f.waitForStart();
assertEquals(2, b2.getNumber());
assertEquals(p, b2.getParent());
b2.getExecutor().interrupt();
r.assertBuildStatus(Result.ABORTED, r.waitForCompletion(b2));
FreeStyleBuild b3 = p.scheduleBuild2(0).waitForStart();
assertEquals(3, b3.getNumber());
assertEquals(p, b3.getParent());
b3.getExecutor().interrupt();
r.assertBuildStatus(Result.ABORTED, r.waitForCompletion(b3));
}
/** /**
* Testing if the lazy loading can gracefully tolerate a RuntimeException during unmarshalling. * Testing if the lazy loading can gracefully tolerate a RuntimeException during unmarshalling.
*/ */
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册