提交 c68cfc9d 编写于 作者: J Jesse Glick

Introduced LazyBuildMixIn.

上级 2ea3d8f1
......@@ -248,7 +248,7 @@ public class MatrixConfiguration extends Project<MatrixConfiguration,MatrixRun>
lastBuild.number = lb.getNumber();
builds.put(lastBuild);
_getRuns().put(lastBuild);
return lastBuild;
}
......
......@@ -244,7 +244,7 @@ public abstract class AbstractBuild<P extends AbstractProject<P,R>,R extends Abs
if (r==null) {
// having two neighbors pointing to each other is important to make RunMap.removeValue work
R nb = getParent().builds.search(number+1, Direction.ASC);
R nb = getParent()._getRuns().search(number+1, Direction.ASC);
if (nb!=null) {
((AbstractBuild)nb).previousBuild = selfReference; // establish bi-di link
this.nextBuild = nb.selfReference;
......
......@@ -32,7 +32,6 @@ import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
import hudson.AbortException;
import hudson.CopyOnWrite;
import hudson.EnvVars;
import hudson.Extension;
import hudson.ExtensionPoint;
import hudson.FeedAdapter;
import hudson.FilePath;
......@@ -49,7 +48,6 @@ import hudson.model.Fingerprint.RangeSet;
import hudson.model.PermalinkProjectAction.Permalink;
import hudson.model.Queue.Executable;
import hudson.model.Queue.Task;
import hudson.model.RunMap.Constructor;
import hudson.model.labels.LabelAtom;
import hudson.model.labels.LabelExpression;
import hudson.model.listeners.ItemListener;
......@@ -86,11 +84,9 @@ import hudson.util.AlternativeUiTextProvider.Message;
import hudson.util.DescribableList;
import hudson.util.FormValidation;
import hudson.util.TimeUnit2;
import hudson.widgets.BuildHistoryWidget;
import hudson.widgets.HistoryWidget;
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;
......@@ -118,7 +114,7 @@ import jenkins.model.Jenkins;
import jenkins.model.JenkinsLocationConfiguration;
import jenkins.model.ModelObjectWithChildren;
import jenkins.model.Uptime;
import jenkins.model.lazy.AbstractLazyLoadRunMap.Direction;
import jenkins.model.lazy.LazyBuildMixIn;
import jenkins.scm.DefaultSCMCheckoutStrategyImpl;
import jenkins.scm.SCMCheckoutStrategy;
import jenkins.scm.SCMCheckoutStrategyDescriptor;
......@@ -129,7 +125,6 @@ import org.acegisecurity.context.SecurityContext;
import org.acegisecurity.context.SecurityContextHolder;
import org.jenkinsci.bytecode.AdaptField;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.CmdLineException;
......@@ -173,15 +168,16 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
*/
private volatile transient SCMRevisionState pollingBaseline = null;
private transient LazyBuildMixIn<P,R> buildMixIn;
/**
* All the builds keyed by their build number.
*
* Kept here for binary compatibility only; otherwise use {@link #buildMixIn}.
* External code should use {@link #getBuildByNumber(int)} or {@link #getLastBuild()} and traverse via
* {@link Run#getPreviousBuild()}
*/
@Restricted(NoExternalUse.class)
@SuppressWarnings("deprecation") // [JENKINS-15156] builds accessed before onLoad or onCreatedFromScratch called
protected transient RunMap<R> builds = new RunMap<R>();
protected transient RunMap<R> builds;
/**
* The quiet period. Null to delegate to the system default.
......@@ -274,6 +270,7 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
protected AbstractProject(ItemGroup parent, String name) {
super(parent,name);
initBuildMixIn();
if(Jenkins.getInstance()!=null && !Jenkins.getInstance().getNodes().isEmpty()) {
// if a new job is configured with Hudson that already has slave nodes
......@@ -282,6 +279,15 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
}
}
private void initBuildMixIn() {
buildMixIn = new LazyBuildMixIn<P,R>(this) {
@Override protected Class<R> getBuildClass() {
return AbstractProject.this.getBuildClass();
}
};
builds = buildMixIn.getRunMap();
}
@Override
public synchronized void save() throws IOException {
super.save();
......@@ -291,7 +297,8 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
@Override
public void onCreatedFromScratch() {
super.onCreatedFromScratch();
builds = createBuildRunMap();
buildMixIn.onCreatedFromScratch();
builds = buildMixIn.getRunMap();
// solicit initial contributions, especially from TransientProjectActionFactory
updateTransientActions();
}
......@@ -299,33 +306,7 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
@Override
public void onLoad(ItemGroup<? extends Item> parent, String name) throws IOException {
super.onLoad(parent, name);
RunMap<R> builds = createBuildRunMap();
RunMap<R> currentBuilds = this.builds;
if (currentBuilds==null && parent!=null) {
// are we overwriting what currently exist?
// this is primarily when Jenkins is getting reloaded
Item current;
try {
current = parent.getItem(name);
} catch (RuntimeException x) {
LOGGER.log(Level.WARNING, "failed to look up " + name + " in " + parent, x);
current = null;
}
if (current!=null && current.getClass()==getClass()) {
currentBuilds = ((AbstractProject)current).builds;
}
}
if (currentBuilds !=null) {
// if we are reloading, keep all those that are still building intact
for (R r : currentBuilds.getLoadedBuilds().values()) {
if (r.isBuilding())
builds.put(r);
}
}
this.builds = builds;
initBuildMixIn();
triggers().setOwner(this);
for (Trigger t : triggers()) {
t.start(this, Items.currentlyUpdatingByXml());
......@@ -338,14 +319,6 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
updateTransientActions();
}
private RunMap<R> createBuildRunMap() {
return new RunMap<R>(getBuildDir(), new Constructor<R>() {
public R create(File dir) throws IOException {
return loadBuild(dir);
}
});
}
@WithBridgeMethods(List.class)
protected DescribableList<Trigger<?>,TriggerDescriptor> triggers() {
if (triggers == null) {
......@@ -1039,18 +1012,12 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
@Override
public RunMap<R> _getRuns() {
if (builds == null) {
throw new IllegalStateException("no run map created yet for " + this);
}
assert builds.baseDirInitialized() : "neither onCreatedFromScratch nor onLoad called on " + this + " yet";
return builds;
return buildMixIn._getRuns();
}
@Override
public void removeRun(R run) {
if (!this.builds.remove(run)) {
LOGGER.log(Level.WARNING, "{0} did not contain {1} to begin with", new Object[] {this, run});
}
buildMixIn.removeRun(run);
}
/**
......@@ -1060,7 +1027,7 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
*/
@Override
public R getBuild(String id) {
return builds.getById(id);
return buildMixIn.getBuild(id);
}
/**
......@@ -1070,7 +1037,7 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
*/
@Override
public R getBuildByNumber(int n) {
return builds.getByNumber(n);
return buildMixIn.getBuildByNumber(n);
}
/**
......@@ -1080,22 +1047,22 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
*/
@Override
public R getFirstBuild() {
return builds.oldestBuild();
return buildMixIn.getFirstBuild();
}
@Override
public @CheckForNull R getLastBuild() {
return builds.newestBuild();
return buildMixIn.getLastBuild();
}
@Override
public R getNearestBuild(int n) {
return builds.search(n, Direction.ASC);
return buildMixIn.getNearestBuild(n);
}
@Override
public R getNearestOldBuild(int n) {
return builds.search(n, Direction.DESC);
return buildMixIn.getNearestOldBuild(n);
}
/**
......@@ -1106,61 +1073,19 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
*/
protected abstract Class<R> getBuildClass();
// keep track of the previous time we started a build
private transient long lastBuildStartTime;
/**
* Creates a new build of this project for immediate execution.
* Suitable for {@link SubTask#createExecutable}.
*/
protected synchronized R newBuild() throws IOException {
// make sure we don't start two builds in the same second
// so the build directories will be different too
long timeSinceLast = System.currentTimeMillis() - lastBuildStartTime;
if (timeSinceLast < 1000) {
try {
Thread.sleep(1000 - timeSinceLast);
} catch (InterruptedException e) {
}
}
lastBuildStartTime = System.currentTimeMillis();
try {
R lastBuild = getBuildClass().getConstructor(getClass()).newInstance(this);
builds.put(lastBuild);
return lastBuild;
} catch (InstantiationException e) {
throw new Error(e);
} catch (IllegalAccessException e) {
throw new Error(e);
} catch (InvocationTargetException e) {
throw handleInvocationTargetException(e);
} catch (NoSuchMethodException e) {
throw new Error(e);
}
}
private IOException handleInvocationTargetException(InvocationTargetException e) {
Throwable t = e.getTargetException();
if(t instanceof Error) throw (Error)t;
if(t instanceof RuntimeException) throw (RuntimeException)t;
if(t instanceof IOException) return (IOException)t;
throw new Error(t);
return buildMixIn.newBuild();
}
/**
* Loads an existing build record from disk.
*/
protected R loadBuild(File dir) throws IOException {
try {
return getBuildClass().getConstructor(getClass(),File.class).newInstance(this,dir);
} catch (InstantiationException e) {
throw new Error(e);
} catch (IllegalAccessException e) {
throw new Error(e);
} catch (InvocationTargetException e) {
throw handleInvocationTargetException(e);
} catch (NoSuchMethodException e) {
throw new Error(e);
}
return buildMixIn.loadBuild(dir);
}
/**
......@@ -1820,7 +1745,7 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
@Override
protected HistoryWidget createHistoryWidget() {
return new BuildHistoryWidget<R>(this,builds,HISTORY_ADAPTER);
return buildMixIn.createHistoryWidget();
}
public boolean isParameterized() {
......@@ -2426,14 +2351,4 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
public abstract FormValidation check(@Nonnull AbstractProject<?, ?> project, @Nonnull Label label);
}
@Restricted(DoNotUse.class)
@Extension public static final class ItemListenerImpl extends ItemListener {
@Override public void onLocationChanged(Item item, String oldFullName, String newFullName) {
if (item instanceof AbstractProject) {
AbstractProject p = (AbstractProject) item;
p.builds.updateBaseDir(p.getBuildDir());
}
}
}
}
......@@ -98,6 +98,7 @@ import java.util.*;
import java.util.List;
import static javax.servlet.http.HttpServletResponse.*;
import jenkins.model.lazy.LazyBuildMixIn;
/**
* A job is an runnable entity under the monitoring of Hudson.
......@@ -570,11 +571,14 @@ public abstract class Job<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R
return r;
}
/**
* @see LazyBuildMixIn#createHistoryWidget
*/
protected HistoryWidget createHistoryWidget() {
return new HistoryWidget<Job, RunT>(this, getBuilds(), HISTORY_ADAPTER);
}
protected static final HistoryWidget.Adapter<Run> HISTORY_ADAPTER = new Adapter<Run>() {
public static final HistoryWidget.Adapter<Run> HISTORY_ADAPTER = new Adapter<Run>() {
public int compare(Run record, String key) {
try {
int k = Integer.parseInt(key);
......@@ -674,6 +678,7 @@ public abstract class Job<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R
/**
* @deprecated since 2008-06-15.
* This is only used to support backward compatibility with old URLs.
* @see LazyBuildMixIn#getBuild
*/
@Deprecated
public RunT getBuild(String id) {
......@@ -689,6 +694,7 @@ public abstract class Job<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R
* The build number.
* @return null if no such build exists.
* @see Run#getNumber()
* @see LazyBuildMixIn#getBuildByNumber
*/
public RunT getBuildByNumber(int n) {
return _getRuns().get(n);
......@@ -724,6 +730,7 @@ public abstract class Job<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R
*
* This is useful when you'd like to fetch a build but the exact build might
* be already gone (deleted, rotated, etc.)
* @see LazyBuildMixIn#getNearestBuild
*/
public RunT getNearestBuild(int n) {
SortedMap<Integer, ? extends RunT> m = _getRuns().headMap(n - 1); // the map should
......@@ -738,6 +745,7 @@ public abstract class Job<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R
*
* This is useful when you'd like to fetch a build but the exact build might
* be already gone (deleted, rotated, etc.)
* @see LazyBuildMixIn#getNearestOldBuild
*/
public RunT getNearestOldBuild(int n) {
SortedMap<Integer, ? extends RunT> m = _getRuns().tailMap(n);
......@@ -787,6 +795,7 @@ public abstract class Job<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R
*
* The resulting map must be treated immutable (by employing copy-on-write
* semantics.) The map is descending order, with newest builds at the top.
* @see LazyBuildMixIn#_getRuns
*/
protected abstract SortedMap<Integer, ? extends RunT> _getRuns();
......@@ -795,11 +804,13 @@ public abstract class Job<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R
*
* The files are deleted already. So all the callee needs to do is to remove
* a reference from this {@link Job}.
* @see LazyBuildMixIn#removeRun
*/
protected abstract void removeRun(RunT run);
/**
* Returns the last build.
* @see LazyBuildMixIn#getLastBuild
*/
@Exported
@QuickSilver
......@@ -813,6 +824,7 @@ public abstract class Job<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R
/**
* Returns the oldest build in the record.
* @see LazyBuildMixIn#getFirstBuild
*/
@Exported
@QuickSilver
......
/*
* The MIT License
*
* Copyright 2014 Jesse Glick.
*
* 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 jenkins.model.lazy;
import hudson.Extension;
import hudson.model.AbstractItem;
import hudson.model.Item;
import hudson.model.ItemGroup;
import hudson.model.Job;
import hudson.model.Queue.Task;
import hudson.model.Run;
import hudson.model.RunMap;
import hudson.model.listeners.ItemListener;
import hudson.widgets.BuildHistoryWidget;
import hudson.widgets.HistoryWidget;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
/**
* Makes it easier to use a lazy {@link RunMap} from a {@link Job} implementation.
* Provides method implementations for some abstract {@link Job} methods,
* as well as some methods which are not abstract but which you should override.
* <p>Should be kept in a {@code transient} field in the job.
* @since TODO
*/
public abstract class LazyBuildMixIn<P extends Job<P,R>,R extends Run<P,R>> {
private static final Logger LOGGER = Logger.getLogger(LazyBuildMixIn.class.getName());
private final Job<?,?> job;
@SuppressWarnings("deprecation") // [JENKINS-15156] builds accessed before onLoad or onCreatedFromScratch called
private @Nonnull RunMap<R> builds = new RunMap<R>();
// keep track of the previous time we started a build
private long lastBuildStartTime;
/**
* Initializes this mixin based on a job.
* Call this from a constructor and {@link AbstractItem#onLoad} to make sure it is always initialized.
* @param job the owning job (should be of type {@code P} and assignable to {@link Task})
*/
public LazyBuildMixIn(Job<?,?> job) {
this.job = job;
}
/**
* Gets the raw model.
* Normally should not be called as such.
* Note that the initial value is replaced during {@link #onCreatedFromScratch} or {@link #onLoad}.
*/
public final @Nonnull RunMap<R> getRunMap() {
return builds;
}
/**
* Same as {@link #getRunMap} but suitable for {@link Job#_getRuns}, which you <em>must override to be public</em>.
*/
public final RunMap<R> _getRuns() {
assert builds.baseDirInitialized() : "neither onCreatedFromScratch nor onLoad called on " + job + " yet";
return builds;
}
/**
* Something to be called from {@link AbstractItem#onCreatedFromScratch}.
*/
public final void onCreatedFromScratch() {
builds = createBuildRunMap();
}
/**
* Something to be called from {@link AbstractItem#onLoad}.
*/
@SuppressWarnings("unchecked")
public void onLoad(ItemGroup<? extends Item> parent, String name) throws IOException {
RunMap<R> _builds = createBuildRunMap();
RunMap<R> currentBuilds = this.builds;
if (parent != null) {
// are we overwriting what currently exist?
// this is primarily when Jenkins is getting reloaded
Item current;
try {
current = parent.getItem(name);
} catch (RuntimeException x) {
LOGGER.log(Level.WARNING, "failed to look up " + name + " in " + parent, x);
current = null;
}
if (current != null && current.getClass() == job.getClass()) {
try {
currentBuilds = (RunMap<R>) current.getClass().getMethod("_getRuns").invoke(current);
} catch (Exception x) {
assert false : "you should have made _getRuns public in " + job.getClass();
}
}
}
if (currentBuilds != null) {
// if we are reloading, keep all those that are still building intact
for (R r : currentBuilds.getLoadedBuilds().values()) {
if (r.isBuilding()) {
_builds.put(r);
}
}
}
this.builds = _builds;
}
private RunMap<R> createBuildRunMap() {
return new RunMap<R>(job.getBuildDir(), new RunMap.Constructor<R>() {
@Override public R create(File dir) throws IOException {
return loadBuild(dir);
}
});
}
/**
* Type token for the build type.
* The build class must have two constructors:
* one taking the project type ({@code P});
* and one taking {@code P}, then {@link File}.
*/
protected abstract Class<R> getBuildClass();
/**
* Loads an existing build record from disk.
* The default implementation just calls the ({@link Job}, {@link File}) constructor of {@link #getBuildClass}.
*/
public R loadBuild(File dir) throws IOException {
try {
return getBuildClass().getConstructor(job.getClass(), File.class).newInstance(job, dir);
} catch (InstantiationException e) {
throw new Error(e);
} catch (IllegalAccessException e) {
throw new Error(e);
} catch (InvocationTargetException e) {
throw handleInvocationTargetException(e);
} catch (NoSuchMethodException e) {
throw new Error(e);
}
}
/**
* Creates a new build of this project for immediate execution.
* Calls the ({@link Job}) constructor of {@link #getBuildClass}.
*/
@SuppressWarnings("SleepWhileHoldingLock")
@edu.umd.cs.findbugs.annotations.SuppressWarnings("SWL_SLEEP_WITH_LOCK_HELD")
public final synchronized R newBuild() throws IOException {
// make sure we don't start two builds in the same second
// so the build directories will be different too
long timeSinceLast = System.currentTimeMillis() - lastBuildStartTime;
if (timeSinceLast < 1000) {
try {
Thread.sleep(1000 - timeSinceLast);
} catch (InterruptedException e) {
}
}
lastBuildStartTime = System.currentTimeMillis();
try {
R lastBuild = getBuildClass().getConstructor(job.getClass()).newInstance(job);
builds.put(lastBuild);
return lastBuild;
} catch (InstantiationException e) {
throw new Error(e);
} catch (IllegalAccessException e) {
throw new Error(e);
} catch (InvocationTargetException e) {
throw handleInvocationTargetException(e);
} catch (NoSuchMethodException e) {
throw new Error(e);
}
}
private IOException handleInvocationTargetException(InvocationTargetException e) {
Throwable t = e.getTargetException();
if (t instanceof Error) {
throw (Error) t;
}
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
}
if (t instanceof IOException) {
return (IOException) t;
}
throw new Error(t);
}
/**
* Suitable for {@link Job#removeRun}.
*/
public final void removeRun(R run) {
if (!builds.remove(run)) {
LOGGER.log(Level.WARNING, "{0} did not contain {1} to begin with", new Object[] {job, run});
}
}
/**
* Suitable for {@link Job#getBuild}.
*/
public final R getBuild(String id) {
return builds.getById(id);
}
/**
* Suitable for {@link Job#getBuildByNumber}.
*/
public final R getBuildByNumber(int n) {
return builds.getByNumber(n);
}
/**
* Suitable for {@link Job#getFirstBuild}.
*/
public final R getFirstBuild() {
return builds.oldestBuild();
}
/**
* Suitable for {@link Job#getLastBuild}.
*/
public final @CheckForNull R getLastBuild() {
return builds.newestBuild();
}
/**
* Suitable for {@link Job#getNearestBuild}.
*/
public final R getNearestBuild(int n) {
return builds.search(n, AbstractLazyLoadRunMap.Direction.ASC);
}
/**
* Suitable for {@link Job#getNearestOldBuild}.
*/
public final R getNearestOldBuild(int n) {
return builds.search(n, AbstractLazyLoadRunMap.Direction.DESC);
}
/**
* Suitable for {@link Job#createHistoryWidget}.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public final HistoryWidget createHistoryWidget() {
return new BuildHistoryWidget((Task) job, builds, Job.HISTORY_ADAPTER);
}
@Restricted(DoNotUse.class)
@Extension public static final class ItemListenerImpl extends ItemListener {
@Override public void onLocationChanged(Item item, String oldFullName, String newFullName) {
if (item instanceof Job) {
RunMap<?> builds;
try {
builds = (RunMap<?>) item.getClass().getMethod("_getRuns").invoke(item);
} catch (NoSuchMethodException x) {
// OK, did not override this to be public
return;
} catch (ClassCastException x) {
// override it to be public but of a different type, fine
return;
} catch (Exception x) {
LOGGER.log(Level.WARNING, null, x);
return;
}
builds.updateBaseDir(((Job) item).getBuildDir());
}
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册