diff --git a/core/src/main/java/hudson/model/AbstractProject.java b/core/src/main/java/hudson/model/AbstractProject.java
index b0a8a1936cb5b9eca5c8227e89c3938f8e3ce869..6b1448feff8309db0c7c60173f2f640a5f4a7fdd 100644
--- a/core/src/main/java/hudson/model/AbstractProject.java
+++ b/core/src/main/java/hudson/model/AbstractProject.java
@@ -162,7 +162,7 @@ public abstract class AbstractProject
,R extends A
* {@link Run#getPreviousBuild()}
*/
@Restricted(NoExternalUse.class)
- protected transient RunMap builds = new RunMap();
+ protected transient RunMap builds;
/**
* The quiet period. Null to delegate to the system default.
@@ -271,6 +271,12 @@ public abstract class AbstractProject,R extends A
super.onCreatedFromScratch();
// solicit initial contributions, especially from TransientProjectActionFactory
updateTransientActions();
+ assert builds==null;
+ builds = new RunMap(getBuildDir(), new Constructor() {
+ public R create(File dir) throws IOException {
+ return loadBuild(dir);
+ }
+ });
}
@Override
diff --git a/core/src/main/java/jenkins/model/lazy/AbstractLazyLoadRunMap.java b/core/src/main/java/jenkins/model/lazy/AbstractLazyLoadRunMap.java
index 21290307684d5352b04f460b0b96a1c3ad14638d..eec9532eaeb5ca3ba1ae51862830cc04c00bb860 100644
--- a/core/src/main/java/jenkins/model/lazy/AbstractLazyLoadRunMap.java
+++ b/core/src/main/java/jenkins/model/lazy/AbstractLazyLoadRunMap.java
@@ -203,8 +203,8 @@ public abstract class AbstractLazyLoadRunMap extends AbstractMap i
private void loadIdOnDisk() {
String[] buildDirs = dir.list(createDirectoryFilter());
if (buildDirs==null) {
+ // the job may just have been created
buildDirs=EMPTY_STRING_ARRAY;
- LOGGER.log(Level.WARNING, "failed to load list of builds from {0}", dir);
}
// wrap into ArrayList to enable mutation
Arrays.sort(buildDirs);
@@ -625,6 +625,7 @@ public abstract class AbstractLazyLoadRunMap extends AbstractMap i
protected R load(String id, Index editInPlace) {
+ assert dir != null;
R v = load(new File(dir, id), editInPlace);
if (v==null && editInPlace!=null) {
// remember the failure.
diff --git a/test/src/test/java/hudson/model/AbstractProjectTest.java b/test/src/test/java/hudson/model/AbstractProjectTest.java
index e44dc949a9538f9bd4ed26340c74e6dc0a384fae..c9333779b0e6c32b99972471d8338bcd7468557a 100644
--- a/test/src/test/java/hudson/model/AbstractProjectTest.java
+++ b/test/src/test/java/hudson/model/AbstractProjectTest.java
@@ -47,6 +47,9 @@ import org.jvnet.hudson.test.recipes.PresetData.DataSet;
import java.io.File;
import java.util.concurrent.Future;
import org.apache.commons.io.FileUtils;
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
/**
* @author Kohsuke Kawaguchi
@@ -280,4 +283,47 @@ public class AbstractProjectTest extends HudsonTestCase {
assertSymlinkForBuild(lastSuccessful, 1);
assertSymlinkForBuild(lastStable, 1);
}
+
+ // Force garbage collection of ref's referent. Taken from NetBeans code.
+ public static void forceGc(final Reference> ref) {
+ ArrayList alloc = new ArrayList();
+ int size = 100000;
+ System.out.print("forcing garbage collection...");
+ for (int i = 0; i < 50; i++) {
+ if (ref.get() == null) {
+ System.out.println("succeeded");
+ return;
+ }
+ try {
+ System.gc();
+ System.runFinalization();
+ } catch (OutOfMemoryError error) {
+ // OK
+ }
+ try {
+ alloc.add(new byte[size]);
+ size = (int)(((double)size) * 1.3);
+ } catch (OutOfMemoryError error) {
+ size = size / 2;
+ }
+ try {
+ if (i % 3 == 0) {
+ Thread.sleep(321);
+ }
+ } catch (InterruptedException t) {
+ // ignore
+ }
+ }
+ System.out.println("failed");
+ }
+
+ @Bug(15156)
+ public void testGetBuildAfterGc() throws Exception {
+ FreeStyleProject job = createFreeStyleProject();
+ job.scheduleBuild2(0, new Cause.UserCause()).get();
+ // If this fails to garbage-collect, the test run will not have any value, but it will
+ // at least not fail.
+ forceGc(new WeakReference(job.getLastBuild()));
+ assertTrue(job.getLastBuild() != null);
+ }
}