提交 88feabb4 编写于 作者: K Kohsuke Kawaguchi

[FIXED JENKINS-16089]

Remember the permalink target as symlink (or simple text file) so that
looking that up doesn't cause the walk of the build history.
I think this is more in line with our general preference of making
$JENKINS_HOME useful (than trying to persist cache into a blackbox.)

Having a general purpose in-memory cache could be useful, so I'll see if
I can add that, too, in a way that allows someone to plug different
backend.
上级 10914763
......@@ -55,7 +55,9 @@ Upcoming changes</a>
<!-- Record your changes in the trunk here. -->
<div id="trunk" style="display:none"><!--=TRUNK-BEGIN=-->
<ul class=image>
<li class=>
<li class=rfe>
Remember the lastStable/Failed/Successful/etc builds to avoid eager loading builds.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-16089">issue 16089</a>)
</ul>
</div><!--=TRUNK-END=-->
......
......@@ -1144,6 +1144,12 @@ public class Util {
* Resolves symlink, if the given file is a symlink. Otherwise return null.
* <p>
* If the resolution fails, report an error.
*
* @return
* null if the given file is not a symlink.
* If the symlink is absolute, the returned string is an absolute path.
* If the symlink is relative, the returned string is that relative representation.
* The relative path is meant to be resolved from the location of the symlink.
*/
public static String resolveSymlink(File link) throws InterruptedException, IOException {
try { // Java 7
......
......@@ -799,13 +799,7 @@ public abstract class Job<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R
@Exported
@QuickSilver
public RunT getLastSuccessfulBuild() {
RunT r = getLastBuild();
// temporary hack till we figure out what's causing this bug
while (r != null
&& (r.isBuilding() || r.getResult() == null || r.getResult()
.isWorseThan(Result.UNSTABLE)))
r = r.getPreviousBuild();
return r;
return (RunT)Permalink.LAST_SUCCESSFUL_BUILD.resolve(this);
}
/**
......
......@@ -23,6 +23,8 @@
*/
package hudson.model;
import jenkins.model.PeepholePermalink;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
......@@ -41,6 +43,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
* @see JobProperty
*/
public interface PermalinkProjectAction extends Action {
/**
* Gets the permalinks defined for this project.
*
......@@ -90,90 +93,99 @@ public interface PermalinkProjectAction extends Action {
*/
public static final List<Permalink> BUILTIN = new CopyOnWriteArrayList<Permalink>();
public static final Permalink LAST_BUILD = new Permalink() {
public String getDisplayName() {
return Messages.Permalink_LastBuild();
}
public String getId() {
return "lastBuild";
}
public Run<?,?> resolve(Job<?,?> job) {
return job.getLastBuild();
}
};
public static final Permalink LAST_STABLE_BUILD = new PeepholePermalink() {
public String getDisplayName() {
return Messages.Permalink_LastStableBuild();
}
public String getId() {
return "lastStableBuild";
}
@Override
public boolean apply(Run<?, ?> run) {
return !run.isBuilding() && run.getResult()==Result.SUCCESS;
}
};
public static final Permalink LAST_SUCCESSFUL_BUILD = new PeepholePermalink() {
public String getDisplayName() {
return Messages.Permalink_LastSuccessfulBuild();
}
public String getId() {
return "lastSuccessfulBuild";
}
@Override
public boolean apply(Run<?, ?> run) {
return !run.isBuilding() && run.getResult().isBetterOrEqualTo(Result.UNSTABLE);
}
};
public static final Permalink LAST_FAILED_BUILD = new PeepholePermalink() {
public String getDisplayName() {
return Messages.Permalink_LastFailedBuild();
}
public String getId() {
return "lastFailedBuild";
}
@Override
public boolean apply(Run<?, ?> run) {
return !run.isBuilding() && run.getResult()==Result.FAILURE;
}
};
public static final Permalink LAST_UNSTABLE_BUILD = new PeepholePermalink() {
public String getDisplayName() {
return Messages.Permalink_LastUnstableBuild();
}
public String getId() {
return "lastUnstableBuild";
}
@Override
public boolean apply(Run<?, ?> run) {
return !run.isBuilding() && run.getResult()==Result.UNSTABLE;
}
};
public static final Permalink LAST_UNSUCCESSFUL_BUILD = new PeepholePermalink() {
public String getDisplayName() {
return Messages.Permalink_LastUnsuccessfulBuild();
}
public String getId() {
return "lastUnsuccessfulBuild";
}
@Override
public boolean apply(Run<?, ?> run) {
return !run.isBuilding() && run.getResult()!=Result.SUCCESS;
}
};
static {
BUILTIN.add(new Permalink() {
public String getDisplayName() {
return Messages.Permalink_LastBuild();
}
public String getId() {
return "lastBuild";
}
public Run<?,?> resolve(Job<?,?> job) {
return job.getLastBuild();
}
});
BUILTIN.add(new Permalink() {
public String getDisplayName() {
return Messages.Permalink_LastStableBuild();
}
public String getId() {
return "lastStableBuild";
}
public Run<?,?> resolve(Job<?,?> job) {
return job.getLastStableBuild();
}
});
BUILTIN.add(new Permalink() {
public String getDisplayName() {
return Messages.Permalink_LastSuccessfulBuild();
}
public String getId() {
return "lastSuccessfulBuild";
}
public Run<?,?> resolve(Job<?,?> job) {
return job.getLastSuccessfulBuild();
}
});
BUILTIN.add(new Permalink() {
public String getDisplayName() {
return Messages.Permalink_LastFailedBuild();
}
public String getId() {
return "lastFailedBuild";
}
public Run<?,?> resolve(Job<?,?> job) {
return job.getLastFailedBuild();
}
});
BUILTIN.add(new Permalink() {
public String getDisplayName() {
return Messages.Permalink_LastUnstableBuild();
}
public String getId() {
return "lastUnstableBuild";
}
public Run<?,?> resolve(Job<?,?> job) {
return job.getLastUnstableBuild();
}
});
BUILTIN.add(new Permalink() {
public String getDisplayName() {
return Messages.Permalink_LastUnsuccessfulBuild();
}
public String getId() {
return "lastUnsuccessfulBuild";
}
public Run<?,?> resolve(Job<?,?> job) {
return job.getLastUnsuccessfulBuild();
}
});
BUILTIN.add(LAST_BUILD);
BUILTIN.add(LAST_STABLE_BUILD);
BUILTIN.add(LAST_SUCCESSFUL_BUILD);
BUILTIN.add(LAST_FAILED_BUILD);
BUILTIN.add(LAST_UNSTABLE_BUILD);
BUILTIN.add(LAST_UNSUCCESSFUL_BUILD);
}
}
}
......@@ -84,6 +84,7 @@ public final class Result implements Serializable, CustomExportedBean {
* Default ball color for this status.
*/
public final BallColor color;
private boolean stable;
private Result(String name, BallColor color, int ordinal) {
this.name = name;
......
package jenkins.model;
import com.google.common.base.Predicate;
import hudson.Functions;
import hudson.Util;
import hudson.model.Job;
import hudson.model.PermalinkProjectAction.Permalink;
import hudson.model.Run;
import hudson.util.AtomicFileWriter;
import hudson.util.StreamTaskListener;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Convenient base implementation for {@link Permalink}s that satisfy
* certain properties.
*
* <p>
* For a permalink to be able to use this, it has to satisfy the following:
*
* <blockquote>
* Given a job J, permalink is a function F that computes a build B.
* A peephole permalink is a subset of this function that can be
* deduced to the "peep-hole" function G(B)->bool:
*
* <pre>
* F(J) = { newest B | G(B)==true }
* </pre>
* </blockquote>
*
* <p>
* Intuitively speaking, a peep-hole permalink resolves to the latest build that
* satisfies a certain characteristics that can be determined solely by looking
* at the build and nothing else (most commonly its build result.)
*
* <p>
* This base class provides a file-based caching mechanism that avoids
* walking the long build history. The cache is a symlink to the build directory
* where symlinks are supported, and text file that contains the build number otherwise.
*
* <p>
* The implementation transparently tolerates G(B) that goes from true to false over time
* (it simply scans the history till find the new matching build.) To tolerate G(B)
* that goes from false to true, you need to be able to intercept whenever G(B) changes
* from false to true, then call {@link #resolve(Job)} to check the current permalink target
* is up to date, then call {@link #updateCache(Job, int)} if it needs updating.
*
* @author Kohsuke Kawaguchi
* @since 1.507
*/
public abstract class PeepholePermalink extends Permalink implements Predicate<Run<?,?>> {
/**
* Checks if the given build satifies the peep-hole criteria.
*
* This is the "G(B)" as described in the class javadoc.
*/
public abstract boolean apply(Run<?,?> run);
/**
* The file in which the permalink target gets recorded.
*/
protected File getPermalinkFile(Job<?,?> job) {
return new File(job.getRootDir(),"permalinks/"+getId());
}
/**
* Resolves the permalink by using the cache if possible.
*/
@Override
public Run<?, ?> resolve(Job<?, ?> job) {
File f = getPermalinkFile(job);
Run<?,?> b=null;
try {
String target = null;
if (USE_SYMLINK) { // f.exists() return false if symlink exists but point to a non-existent directory
target = Util.resolveSymlink(f);
if (target==null && f.exists()) {
// if this file isn't a symlink, it must be a regular file
target = FileUtils.readFileToString(f,"UTF-8").trim();
}
} else {
if (f.exists()) {
// if this file isn't a symlink, it must be a regular file
target = FileUtils.readFileToString(f,"UTF-8").trim();
}
}
if (target!=null) {
int n = Integer.parseInt(Util.getFileName(target));
if (n==RESOLVES_TO_NONE) return null;
b = job.getBuildByNumber(n);
if (b!=null && apply(b))
return b; // found it (in the most efficient way possible)
// the cache is stale. start the search
if (b==null)
b=job.getNearestOldBuild(n);
}
} catch (InterruptedException e) {
LOGGER.log(Level.WARNING, "Failed to read permalink cache:" + f, e);
// if we fail to read the cache, fall back to the re-computation
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Failed to read permalink cache:" + f, e);
// if we fail to read the cache, fall back to the re-computation
}
if (b==null) {
// no cache
b = job.getLastBuild();
}
int n;
// start from the build 'b' and locate the build that matches the criteria going back in time
while (true) {
if (b==null) {
n = RESOLVES_TO_NONE;
break;
}
if (apply(b)) {
n = b.getNumber();
break;
}
b=b.getPreviousBuild();
}
updateCache(job,n);
return b;
}
/**
* Remembers the value 'n' in the cache for future {@link #resolve(Job)}.
*/
protected void updateCache(Job<?,?> job, int n) {
File cache = getPermalinkFile(job);
cache.getParentFile().mkdirs();
try {
StringWriter w = new StringWriter();
StreamTaskListener listener = new StreamTaskListener(w);
if (USE_SYMLINK) {
Util.createSymlink(cache.getParentFile(),"../builds/"+n,cache.getName(),listener);
} else {
// symlink not supported. use a regular
AtomicFileWriter cw = new AtomicFileWriter(cache);
try {
cw.write(String.valueOf(n));
cw.commit();
} finally {
cw.abort();
}
}
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Failed to update permalink cache for " + job, e);
cache.delete();
} catch (InterruptedException e) {
LOGGER.log(Level.WARNING, "Failed to update permalink cache for "+job,e);
cache.delete();
}
}
private static final int RESOLVES_TO_NONE = -1;
private static final Logger LOGGER = Logger.getLogger(PeepholePermalink.class.getName());
/**
* True if we use the symlink as cache, false if plain text file.
*
* <p>
* On Windows, even with Java7, using symlinks require one to go through quite a few hoops
* (you need to change the security policy to specifically have this permission, then
* you better not be in the administrator group because this token gets filtered out
* on UAC-enabled Windows.)
*/
public static boolean USE_SYMLINK = !Functions.isWindows();
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册